如何对各种形状使用同一组修饰符

How to use same set of modifiers for various shapes

作为我学习 SwiftUI 项目的一部分,我做了一些形状旋转,下面有代码。我想知道如何避免为每个形状使用相同的三行修饰符。

func getShape(shape: Int, i: Int) -> AnyView {
    
    switch shape {
    case 0:
        return AnyView(Rectangle()
                        .stroke(colors[Int(shapeColor)])
                        .frame(width: CGFloat(shapeWidth), height: CGFloat(shapeHeight))
                        .rotationEffect(Angle(degrees: Double(i) * Double(angleStep))))
    case 1:
        return AnyView(Capsule()
                        .stroke(colors[Int(shapeColor)])
                        .frame(width: CGFloat(shapeWidth), height: CGFloat(shapeHeight))
                        .rotationEffect(Angle(degrees: Double(i) * Double(angleStep))))
    case 2:
        return AnyView(Ellipse()
                        .stroke(colors[Int(shapeColor)])
                        .frame(width: CGFloat(shapeWidth), height: CGFloat(shapeHeight))
                        .rotationEffect(Angle(degrees: Double(i) * Double(angleStep))))
    default:
        return AnyView(Rectangle()
                        .stroke(colors[Int(shapeColor)])
                        .frame(width: CGFloat(shapeWidth), height: CGFloat(shapeHeight))
                        .rotationEffect(Angle(degrees: Double(i) * Double(angleStep))))
        
    }
}

您可以通过使用辅助函数和扩展来抽象一些重复。

  1. 在下面的简化示例中,我使用 @ViewBuilder 来清理我们正在 returning 的代码。无需使用 AnyView,它使代码更易于阅读。

    如果可以的话就太好了returnsome Shape然而这是 目前不可能并导致错误。这就是为什么中风 必须为 getShape 中的每个 Shape 重复值 功能,否则我们可以在 Shape 上进行扩展 而不是 View.

  2. 我在 View 上创建了一个扩展,允许我们将修饰符组合到一个函数中,使其更具可读性和易用性。老实说,这部分是可选的,您可以使用两个修饰符 framerotationEffect.

  3. @ViewBuilder getShape(shape:index:) returns 是您选择的带有所选颜色的形状,然后由函数 createShape(shape:index:) 使用,您可以在其中添加自定义我们在 View.

    上作为扩展创建的修饰符
  4. 最后我们创建了我们的形状

这应该给你一个起点。

struct ShapeView: View {

    @ViewBuilder // 1
    func getShape(shape: Int, i: Int) -> some View {
        switch shape {
        case 0:
            Rectangle().stroke(Color.red)
        case 1:
            Capsule().stroke(Color.red)
        case 2:
            Ellipse().stroke(Color.red)
        default:
            Rectangle().stroke(Color.red)
        }
    }

    func createShape(shape: Int, index: Int) -> some View { // 3
        getShape(shape: shape, i: index)
            .myModifier(width: 200, height: 100, index: index, angleStep: 30)
    }

    var body: some View {
        createShape(shape: 2, index: 1) // 4
    }
}
  
// 2
extension View {
    func myModifier(width: CGFloat, height: CGFloat, index: Int, angleStep: Double) -> some View {
        self
            .frame(width: width, height: height)
            .rotationEffect(Angle(degrees: Double(index) * Double(angleStep)))
    }
}

struct ShapeView_Previews: PreviewProvider {
    static var previews: some View {
        ShapeView()
    }
}

很遗憾我们不能从 @ViewBuilder return some Shape 或者如果存在 @ShapeBuilder 因为这意味着我们不会有将笔画单独添加到每个形状,因为 View 不能有笔画。

使用助手 AnyShape 键入橡皮擦

struct AnyShape: Shape {
    private let builder: (CGRect) -> Path

    init<S: Shape>(_ shape: S) {
        builder = { rect in
            let path = shape.path(in: rect)
            return path
        }
    }

    func path(in rect: CGRect) -> Path {
        return builder(rect)
    }
}

你的函数可以写成

func getShape(shape: Int, i: Int) -> some View {
    let selectedShape: AnyShape = {
        switch shape {
            case 0:
                return AnyShape(Rectangle())
            case 1:
                return AnyShape(Capsule())
            case 2:
                return AnyShape(Ellipse())
            default:
                return AnyShape(Rectangle())
        }
    }()
    return selectedShape
        .stroke(colors[Int(shapeColor)])
        .frame(width: CGFloat(shapeWidth), height: CGFloat(shapeHeight))
        .rotationEffect(Angle(degrees: Double(i) * Double(angleStep))))
}

嵌套函数可以帮助清理代码:

func getShape(shape: Int, i: Int) -> some View {
    
    func adjustedView<S: Shape>(shape: S) -> some View {
        shape
            .stroke(colors[Int(shapeColor)])
            .frame(width: CGFloat(shapeWidth), height: CGFloat(shapeHeight))
            .rotationEffect(Angle(degrees: Double(i) * Double(angleStep))))
    }
    
    return Group {
        switch shape {
        case 0:
            adjustedView(shape: Rectangle())
        case 1:
            adjustedView(shape: Capsule())
        case 2:
            adjustedView(shape: Ellipse())
        default:
            adjustedView(shape: Rectangle())
        }
    }
}

另一种选择是使用便利功能扩展 Shape。即

extension Shape {
    func adjust(shapeWidth: Double, shapeHeight: Double, angle: Angle) -> some View {
        self.stroke()
            //.stroke(colors[Int(shapeColor)]) // for brevity
            .frame(width: CGFloat(shapeWidth), height: CGFloat(shapeHeight))
            .rotationEffect(angle)
    }
}

它简化了代码。也不需要擦除类型。

func getShape(shape: Int, i: Int) -> some View {
    Group {
        switch shape {
        case 0:
             Rectangle().adjust(shapeWidth: shapeWidth, shapeHeight: shapeHeight, angle: Angle(degrees: Double(i) * Double(angleStep))))
        case 1:
             Capsule().adjust(shapeWidth: shapeWidth, shapeHeight: shapeHeight, angle: Angle(degrees: Double(i) * Double(angleStep))))
        case 2:
            Ellipse().adjust(shapeWidth: shapeWidth, shapeHeight: shapeHeight, angle: Angle(degrees: Double(i) * Double(angleStep))))
        default:
            Rectangle().adjust(shapeWidth: shapeWidth, shapeHeight: shapeHeight, angle: Angle(degrees: Double(i) * Double(angleStep))))
        }
    }
}