代码之家  ›  专栏  ›  技术社区  ›  swiftPunk

如何为SwiftUI定义ShapeBuilder

  •  0
  • swiftPunk  · 技术社区  · 4 年前

    正如我们所知,我们有一个非常有用的视图包装器@ViewBuilder,但我缺少Shape协议的相同包装器,由于没有可用的包装器,我希望尝试构建一个用于学习多孔的包装器。

    以下是视图的工作代码,并希望为Shape构建包装器:

    @ViewBuilder func testForView(value: Bool) -> some View {
        if value {
             Text("Hello")
        }
        else {
             Button("world!") {}
        }
    }
    
    // The Goal:
    @ShapeBuilder func testForShape(value: Bool) -> some Shape {
        if value {
             Circle()
        }
        else {
             Rectangle()
        }
    }
    

    我有使用包装器甚至定义自定义包装器的经验,但我所有的尝试都是包装一个值,而不是函数!我从来没有试过包装一个函数,坦率地说,我不知道它是Swift或SwiftUI中的东西。那么,我如何才能让@ShapeBuilder像@ViewBuilder一样工作呢?我知道我必须使用Tuple或更正确的TupleShape,所以我认为我也应该定义TupleShape。

    0 回复  |  直到 4 年前
        1
  •  7
  •   rob mayoff    2 年前

    首先,我们应该问是否值得写一篇 ShapeBuilder 而不是写 AnyShape 键入如下内容:

    public struct AnyShape: Shape {
        public let makePath: (CGRect) -> Path
    
        public init(makePath: @escaping (CGRect) -> Path) {
            self.makePath = makePath
        }
    
        public init<Wrapped: Shape>(_ shape: Wrapped) {
            self.makePath = { shape.path(in: $0) }
        }
    
        public func path(in rect: CGRect) -> Path {
            return makePath(rect)
        }
    }
    

    我们可以使用 AnyShape 写你的 testForShape 功能如下:

    func testForShape(value: Bool) -> AnyShape {
        if value {
            return AnyShape(Circle())
        }
        else {
            return AnyShape(Rectangle())
        }
    }
    

    我用过 AnyShape 在各种项目中。

    更新

    苹果添加了 AnyShape 在2022年发布的iOS 16、macOS 13等中键入SwiftUI。苹果版本没有 makePath 初始化器,就像我上面的实现一样,但是 an equivalent Wrapped 初始化器,这足以解决问题中所述的问题。

    现在回到原来

    不管怎样,如果我们想写 ShapeBuilder ,我们可以从最简单的实现开始,它只支持包含单个无条件子形状的实体:

    @resultBuilder
    public struct ShapeBuilder {
        public static func buildBlock<C0: Shape>(_ c0: C0) -> C0 { c0 }
    }
    

    我们现在可以这样使用它:

    @ShapeBuilder
    func shape1() -> some Shape {
        Circle()
    }
    

    好极了

    我们可以扩展它来支撑包含 通过添加零参数形成子形状 buildBlock EmptyShape 键入以使其返回:

    extension ShapeBuilder {
        public static func buildBlock() -> EmptyShape { EmptyShape() }
    }
    
    public struct EmptyShape: Shape {
        public func path(in rect: CGRect) -> Path { Path() }
    }
    

    现在我们可以毫无错误地编写以下函数:

    @ShapeBuilder
    func shape0() -> some Shape {
    }
    

    支持 if 不带 else 条款,我们添加 buildOptional 方法和 OptionalShape 键入以使其返回:

    extension ShapeBuilder {
        public static func buildOptional<C0: Shape>(_ c0: C0?) -> OptionalShape<C0> {
            return OptionalShape(c0)
        }
    }
    
    public struct OptionalShape<Content: Shape>: Shape {
        public let content: Content?
    
        public init(_ content: Content?) {
            self.content = content
        }
    
        public func path(in rect: CGRect) -> Path {
            return content?.path(in: rect) ?? Path()
        }
    }
    

    现在我们可以这样使用它:

    @ShapeBuilder
    func shape2(flag: Bool) -> some Shape {
        if flag {
            Circle()
        }
    }
    

    支持 如果 陈述 具有 其他的 子句,并支持 switch 声明,我们添加 buildEither EitherShape 键入以使其返回:

    extension ShapeBuilder {
        public static func buildEither<First: Shape, Second: Shape>(first: First) -> EitherShape<First, Second> {
            return .first(first)
        }
    
        public static func buildEither<First: Shape, Second: Shape>(second: Second) -> EitherShape<First, Second> {
            return .second(second)
        }
    }
    
    public enum EitherShape<First: Shape, Second: Shape>: Shape {
        case first(First)
        case second(Second)
    
        public func path(in rect: CGRect) -> Path {
            switch self {
            case .first(let first): return first.path(in: rect)
            case .second(let second): return second.path(in: rect)
            }
        }
    }
    

    我们现在可以编写此函数:

    @ShapeBuilder
    func shape3(_ n: Int) -> some Shape {
        if n < 0 {
            Rectangle()
        } else {
            switch n {
            case 0:
                EmptyShape()
            case 1:
                Rectangle()
            default:
                Capsule()
            }
        }
    }
    

    我们可以通过编写 构建块 方法,该方法接受两个参数和 Tuple2Shape 以便返回:

    extension ShapeBuilder {
        public static func buildBlock<C0: Shape, C1: Shape>(_ c0: C0, _ c1: C1) -> Tuple2Shape<C0, C1> {
            return Tuple2Shape(c0, c1)
        }
    }
    
    public struct Tuple2Shape<C0: Shape, C1: Shape>: Shape {
        public let tuple: (C0, C1)
    
        public init(_ c0: C0, _ c1: C1) {
            tuple = (c0, c1)
        }
    
        public func path(in rect: CGRect) -> Path {
            var path = tuple.0.path(in: rect)
            path.addPath(tuple.1.path(in: rect))
            return path
        }
    }
    

    现在我们可以编写这个函数:

    @ShapeBuilder
    func shape4() -> some Shape {
        Circle()
        Rectangle()
    }
    

    我们可以通过编写 构建块 方法,该方法接受三个参数和 Tuple3Shape 以便返回:

    extension ShapeBuilder {
        public static func buildBlock<C0: Shape, C1: Shape, C2: Shape>(_ c0: C0, _ c1: C1, _ c2: C2) -> Tuple3Shape<C0, C1, C2> {
            return Tuple3Shape(c0, c1, c2)
        }
    }
    
    public struct Tuple3Shape<C0: Shape, C1: Shape, C2: Shape>: Shape {
        public let tuple: (C0, C1, C2)
    
        public init(_ c0: C0, _ c1: C1, _ c2: C2) {
            tuple = (c0, c1, c2)
        }
    
        public func path(in rect: CGRect) -> Path {
            var path = tuple.0.path(in: rect)
            path.addPath(tuple.1.path(in: rect))
            path.addPath(tuple.2.path(in: rect))
            return path
        }
    }
    

    这样我们就可以编写这个函数:

    @ShapeBuilder
    func shape5() -> some Shape {
        Circle()
        Rectangle()
        Capsule()
    }
    

    ViewBuilder 构建块 方法达到arity 10,但我不会再写了。如果你需要,你可以自己做。

    不管怎样,这是 ShapeBuilder 实现在一起,便于复制“粘贴”:

    @resultBuilder
    public struct ShapeBuilder {
        public static func buildBlock<C0: Shape>(_ c0: C0) -> C0 { c0 }
    }
    
    extension ShapeBuilder {
        public static func buildBlock() -> EmptyShape { EmptyShape() }
    }
    
    public struct EmptyShape: Shape {
        public func path(in rect: CGRect) -> Path { Path() }
    }
    
    extension ShapeBuilder {
        public static func buildOptional<C0: Shape>(_ c0: C0?) -> OptionalShape<C0> {
            return OptionalShape(c0)
        }
    }
    
    public struct OptionalShape<Content: Shape>: Shape {
        public let content: Content?
    
        public init(_ content: Content?) {
            self.content = content
        }
    
        public func path(in rect: CGRect) -> Path {
            return content?.path(in: rect) ?? Path()
        }
    }
    
    extension ShapeBuilder {
        public static func buildEither<First: Shape, Second: Shape>(first: First) -> EitherShape<First, Second> {
            return .first(first)
        }
    
        public static func buildEither<First: Shape, Second: Shape>(second: Second) -> EitherShape<First, Second> {
            return .second(second)
        }
    }
    
    public enum EitherShape<First: Shape, Second: Shape>: Shape {
        case first(First)
        case second(Second)
    
        public func path(in rect: CGRect) -> Path {
            switch self {
            case .first(let first): return first.path(in: rect)
            case .second(let second): return second.path(in: rect)
            }
        }
    }
    
    extension ShapeBuilder {
        public static func buildBlock<C0: Shape, C1: Shape>(_ c0: C0, _ c1: C1) -> Tuple2Shape<C0, C1> {
            return Tuple2Shape(c0, c1)
        }
    }
    
    public struct Tuple2Shape<C0: Shape, C1: Shape>: Shape {
        public let tuple: (C0, C1)
    
        public init(_ c0: C0, _ c1: C1) {
            tuple = (c0, c1)
        }
    
        public func path(in rect: CGRect) -> Path {
            var path = tuple.0.path(in: rect)
            path.addPath(tuple.1.path(in: rect))
            return path
        }
    }
    
    extension ShapeBuilder {
        public static func buildBlock<C0: Shape, C1: Shape, C2: Shape>(_ c0: C0, _ c1: C1, _ c2: C2) -> Tuple3Shape<C0, C1, C2> {
            return Tuple3Shape(c0, c1, c2)
        }
    }
    
    public struct Tuple3Shape<C0: Shape, C1: Shape, C2: Shape>: Shape {
        public let tuple: (C0, C1, C2)
    
        public init(_ c0: C0, _ c1: C1, _ c2: C2) {
            tuple = (c0, c1, c2)
        }
    
        public func path(in rect: CGRect) -> Path {
            var path = tuple.0.path(in: rect)
            path.addPath(tuple.1.path(in: rect))
            path.addPath(tuple.2.path(in: rect))
            return path
        }
    }