SwiftUI-根据API字段选择正确的视图类型

时间:2020-07-23 05:58:47

标签: ios swift generics swiftui

很抱歉,这个问题有点抽象,但是在这里:

API返回由几个简单字段组成的资源列表。字段之一(示例中为“ view-id”)告诉客户端在呈现数据时使用哪个SwiftUI View

[
  {
    "title": "Example Title"
    "image": "https://www.example.com/some/image.png"
    "view-id": "text-row" // tells the client to use, say, `TextRowView`
  },
  {
    "title": "A Second Title"
    "image": "https://www.example.com/some/other/image.png"
    "view-id": "image-row" // tells the client to use, say, `ImageRowView`
  }
]

我的目标是找到一种可扩展的方法,以允许在运行时根据API中的View选择view-id类型。

为解决这个问题,我定义了以下协议来描述可以创建视图的类型

protocol MyViewBuilder {
    
    associatedtype Content: View
    
    func buildView(data: Data) -> Content
}

/// concrete implementation of a `MyViewBuilder`
struct MyExampleViewBuilder {
    
    func buildView(data: Data) -> Text {
        Text(data.string)
    }
}

我定义了以下协议,以将API中的“ view-id”映射到特定类型的视图:

protocol MyViewDescriptor {

    associatedtype Content: View
    
    var id: String { get }
}

/// concrete implementation for a `MyViewDescriptor`
struct MyExampleDescriptor {

    associatedtype Content: Text
    
    var id: String = "text-row"
} 

现在,我建立了一个类型,可以像这样注册和访问这些片段:

class MyGlueCode: MyViewBuilder {
    func register<D, B>(_ descriptor: D, viewBuilder: B) where D: MyViewDescriptor,
                                                                   B: MyViewBuilder,
                                                                   D.Content == B.Content {...}


    func buildView<D, Content>(descriptor: D, data: Data) -> Content where D: MyViewDescriptor, Content == D.Content { ... }

    
}

现在可以使上面的代码进行一些类型的擦除,但是我苦苦挣扎的是如何为buildView(descriptor:data:)保留/生成'descriptor'参数。但是我无法设计出一种方法可以直接在呼叫站点上做到这一点,理想情况下,我可以在该站点上做一些简单的事情

ForEach(data) { 
   SomeClass().view(for: $0)
}

我被困住了:(帮助!

1 个答案:

答案 0 :(得分:0)

这是可行的方法。使用Xcode 11.4 / iOS 13.4进行了测试

enum ViewType: String {
    case text = "text-row"
    case image = "image-row"

    static func view(for data: Data) -> some View {
        let type = Self(rawValue: data.viewId)

        return Group {
            if type == nil {
                Text("Unsupported View Type")
            } else if type == .text {
                TexDataView(data: data)
            } else if type == .image {
                ImageDataView(data: data)
            }
        }
    }
}

struct DemoDataView: View {
    let data: Data
    var body: some View {
        ViewType.view(for: data)
    }
}

假设已复制

struct Data {
    let title: String
    let image: String
    let viewId: String
}