在 SwiftUI 中动态获取形状以供查看

Get Shape for view dynamically in SwiftUI

使用 Swift 5.2 我想创建一个函数来动态改变 Shape

我有这样的看法

import SwiftUI

struct CardView: View {
    let suit : Suite
    let rank : Rank
    var body: some View {
        getShape(suite: .heart)
        .fill(Color.red)  // .fill(suit.color)
        .frame(width: 100, height: 100)
     }
}

我想创建一个具有协议 return 形状类型的函数,我在下面的示例中用自定义形状替换了通用形状

func getShape(suite:Suite) -> Shape {
    switch suite {
    case .heart:
        return Circle() // Heart()
    case .diamond:
        return Rectangle() // Diamond()
    case .spade:
        return Circle() // Heart()
    case .club:
        return Circle() // Club()

    }
}

我不能将不透明类型与 some 一起使用,因为我 return 使用不同的类型并且出现编译错误

Function declares an opaque return type, but the return statements in its body do not have matching underlying types 

我也不能将它与协议类型保持原样,因为我得到了错误

Protocol 'Shape' can only be used as a generic constraint because it has Self or associated type requirements

有什么办法可以优雅地实现这个目标吗?

以下是可能的解决方案。

更新:使用 Xcode 13.4 / iOS 15.5

测试

从现在开始,IMO 最好放置所有这些模型并使用 ViewBuilder 然后不需要任何自定义 wrappers/erasures:

enum Suite {
    case heart, diamond, spade, club

    // Generate complete view and return opaque type
    @ViewBuilder
    var shape: some View {    // << here !!
         switch self {
              case .heart:
                    Heart().fill(.red)    // or make it self.color
              case .diamond:
                    Diamond().fill(.red)
              case .spade:
                    Spade().fill(.black)
              case .club:
                    Club().fill(.black)
        }
    }
}


struct CardView: View {
    let suit : Suite
    let rank : Rank

    var body: some View {
        suit.shape          // << as simple as !!
            .frame(width: 100, height: 100)
     }
}

原文:使用 Xcode 11.4.

测试
struct CardView: View {
    let suit : Suite
    let rank : Rank
    var body: some View {
        // pass all dependencies to generate view
        getShape(suite: .heart, fill: suit.color) 
            .frame(width: 100, height: 100)
     }
}

// Generate complete view and return opaque type
func getShape(suite: Suite, fill color: Color) -> some View {
    switch suite {
        case .heart:
            return AnyView(Heart().fill(color))
        case .diamond:
            return AnyView(Diamond().fill(color))
        case .spade:
            return AnyView(Spade().fill(color))
        case .club:
            return AnyView(Club().fill(color))
   }
}

backup

通过将@Asperi 的回答与

相结合
struct AnyShape: Shape {
    init<S: Shape>(_ wrapped: S) {
        _path = { rect in
            let path = wrapped.path(in: rect)
            return path
        }
    }

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

    private let _path: (CGRect) -> Path
}

我可以改成

func getShape(suite:Suite) -> some Shape {
    switch suite {
    case .club:
        return AnyShape(Club())
    case .diamond:
        return AnyShape(Diamond())
    case .heart:
        return AnyShape(Heart())

    case .spade:
        return AnyShape(Spade())
    }
}


struct CardView: View {
    let suit : Suite
    let rank : Rank
    var body: some View {

    getShape(suite: suit)
      .fill(Color.red)
      .frame(width: 100, height: 100)
 }

只想把这个留在这里:

https://github.com/ohitsdaniel/ShapeBuilder

我最近开源了一个 ShapeBuilder,它允许将计算属性和函数标记为 @ShapeBuilder@InsettableShapeBuilder 通过利用结果构建器避免类型擦除。

这将允许您编写以下代码:

import ShapeBuilder

@ShapeBuilder func getShape(suite:Suite) -> some Shape {
  switch suite {
    case .heart:
     Heart()
    case .diamond:
     Diamond()
    case .spade:
     Heart()
    case .club:
     Club()
  }
}

我还建议不要擦除到 AnyView,如上一个答案中所述。相反,标记您可以使用 @ViewBuilder 标记您的 getShape 函数。这将函数主体变成了视图构建器,就像 SwiftUI 视图主体 属性 并避免了类型擦除,这允许 SwiftUI 更轻松地维护您的结构视图标识。