在SwiftUI中为圆角矩形创建Escape分布式虚线轮廓

o0lyfsai  于 12个月前  发布在  Swift
关注(0)|答案(2)|浏览(112)

编辑:代码示例中的破折号大小为10是任意的。破折号大小是看起来不错的。
假设在SwiftUI中,您希望有一个带有虚线轮廓的自定义列表项--用作“添加项提示”。
所以你可以像这样使用strokeBorder:

struct ContentView: View {
    
    let items:[String] = ["a","b","c"]
    @State var itemsEmpty = false
    
    var body: some View {
        Text("toggle").onTapGesture {
            itemsEmpty.toggle()
        }
        VStack {
            Spacer()
            if !itemsEmpty {
                List{
                    ForEach(items, id: \.self) {item in
                        Text(item)
                    }
                }
            } else {
                // The "list item hint" view
                List{
                    Text("ADD")
                        .listRowBackground(
                            RoundedRectangle(cornerSize: CGSize(width: 15, height: 15))
                                .strokeBorder(style: StrokeStyle(lineWidth: 4, dash: [10]))
                            
                        )
                }
            }
            Spacer()
        }
        .padding()
    }
}

字符串
结果是:
x1c 0d1x的数据
this Apple sample中也使用了同样的技术,但我并不满意,因为行(可以理解)不是均匀分布的,而且在环绕点处我们得到了一个讨厌的截断。
我能想到的解决这个问题的唯一(也是不令人满意的)方法是:
A)使用两个镜像的形状来创建视图.(但这仍然会导致截断.只是这次是对称截断)。
B)制作某种“太阳光线”径向渐变,并以某种方式堆叠两个圆角矩形,使我们有一个虚线轮廓的错觉.(但这些线的“头和尾”不会垂直于轮廓路径,因为“太阳光线”都起源于一个点)
C)使用方波的参数方程(squircle,因为超级椭圆看起来像垃圾海事组织,如果它是矩形)(这将需要被切成两半,像豪华轿车一样伸展),并从中创建一条路径,然后使用t = 2 *(pi)/某个除数沿着该路径将虚线绘制为沿着间断的线以建立分辨率。(limo-fied)形状将是分段函数,因此不能由t驱动。即使可以,破折号的大小也不会规则,原因很明显,我无法用语言表达。
这怎么可能呢?

f3temu5u

f3temu5u1#

如果笔划的长度(形状的周长)是破折号大小的整数倍,则不会截断破折号。
因此,您需要根据周长动态调整虚线大小。请参见thisthis,分别查找参数曲线和非参数曲线的弧长。如果这太难积分,还可以考虑计算形状的基础CGPath中每个CGPathElement s的长度。请参见this gist(尽管它是用Swift 2编写的),或者this repo来实现。
在圆角半径的圆角矩形的情况下,您可以简单地将直线部分和圆形部分的长度相加。

.listRowBackground(
    GeometryReader { geo in
        let strokeWidth: CGFloat = 4
        let radius: CGFloat = 15
        let insettedDiameter = radius * 2 - strokeWidth
        let desiredDash: CGFloat = 10
        // note that we need to take the inset of strokeBorder into account
        // or just use stroke instead of strokeBorder to make this simpler
        let perimeter = (geo.size.width - strokeWidth / 2 - insettedDiameter) * 2 + // horizontal straight edges
                        (geo.size.height - strokeWidth / 2 - insettedDiameter) * 2 + // vertical straight edges
                        (insettedDiameter * .pi) // the circular parts

        // this finds the smallest adjustedDash such that
        // - perimeter is an integer multiple of adjustedDash
        // - adjustedDash > desiredDash
        let floored = floor(perimeter / desiredDash)
        let adjustedDash = (perimeter - desiredDash * floored) / floored + desiredDash
        
        RoundedRectangle(cornerRadius: radius)
            .strokeBorder(style: StrokeStyle(lineWidth: strokeWidth, dash: [adjustedDash]))
    }
)

字符串
输出示例:


的数据

h6my8fg2

h6my8fg22#

对于任何感兴趣的人...
这是另一个可重用的版本,我们设置了所需的破折号数量,而不是定义所需的破折号长度。
它还防止了有问题的奇数破折号。

struct DottedRoundedRectangle: View {
    
    enum BorderPosition {
        case inset, centered
    }
    
    let evenNumberOfDashes: Int
    let color: Color
    let strokeWidth: Double
    let radius: Double
    let borderPosition: BorderPosition
    
    private var insetDistance: Double {
            switch borderPosition {
            case .centered:
                return 0
            case .inset:
                return strokeWidth / 2
            }
        }
    
    var body: some View {
        GeometryReader { geo in
            let w = geo.size.width
            let h = geo.size.height
            
            // an odd number of dashes is bad -- results in back-to-back dashes at wrap around point
            let _evenNumDashes = evenNumberOfDashes + (evenNumberOfDashes % 2)
            
            let horizontalSideLength = (w - (2 * insetDistance)) - (2 * (radius - insetDistance))
            let verticalSideLength = (h - (2 * insetDistance)) - (2 * (radius - insetDistance))
            let cornerLength = Double.pi/2 * (radius - insetDistance)
           
            let perimeter = (horizontalSideLength * 2) + (verticalSideLength * 2) + (cornerLength * 4)
           
            let dashLength:Double = perimeter/Double(_evenNumDashes)
                    
            switch borderPosition {
            case .inset:
                RoundedRectangle(cornerRadius: radius)
                    .fill(Material.ultraThin)
                    .strokeBorder(style: StrokeStyle(lineWidth: strokeWidth, dash: [dashLength]))
                    .foregroundColor(color)
            case .centered:
                RoundedRectangle(cornerRadius: radius)
                    .fill(Material.ultraThin)
                    .stroke(color, style: StrokeStyle(lineWidth: strokeWidth, dash: [dashLength]))
            }
           
        }
    }
    
}

字符串

相关问题