首页 > 解决方案 > 如何在 ForEach 中转换不同的 SwiftUI View 结构来显示?

问题描述

我想根据分配给项目的内容显示不同类型的视图。每个相关视图都符合此协议:

public protocol ItemDisplayer {
    init(item: ItemObject)
}

此结构存储 Item 对象及其关联的外观 View 对象。

public struct ItemWithDisplay {
    private let item:  ItemObject
    private let itemDisplayer : ItemDisplayer
    
    public init(item : ItemObject, displayer : ItemDisplayer) {
        self.item = item
        self.itemDisplayer = displayer
    }
    
    public func getDisplayer() -> TimeCounterDisplayer {
        return itemDisplayer
    }

    public func getItem() -> ItemObject {
        return item
    }
}

有几种视图以某种方式显示数据,取决于分配给 ItemObject 的内容。

import SwiftUI

struct OtherItemView: View, ItemDisplayer {
    private var item: ItemObject

    init(item: ItemObject) { self.item = item }

    var body: some View { ... }
}

struct DefaultItemView: View, ItemDisplayer {
    private var item: ItemObject

    init(item: ItemObject) { self.item = item }

    var body: some View { ... }
}
// other ItemViews ...

我想在 modelView 类中将项目与视图分开,如下所示:

switch displayerType {
case .other:
    self.itemWithDisplayArray.append(ItemWithDisplay(item: item, displayer: OtherItemView(itemToDisplay: item)))
case .another:
    self.itemWithDisplayArray.append(ItemWithDisplay(item: item, displayer: AnotherItemView(itemToDisplay: item)))
default:
    self.itemWithDisplayArray.append(ItemWithDisplay(item: item, displayer: DefaultItemView(itemToDisplay: item)))
}

这是以下问题所需的信息。我的问题涉及以下部分:

ScrollView {
    ForEach(viewModel.itemWithDisplayArray, id: \.itemId) { item in
        VStack {
            //item.getDisplayer() as! DefaultItemView // It works
           item.getDisplayer() as! AnyView // Cast from 'ItemDisplayer' to unrelated type 'AnyView' always fails // how could I fix this?
        }
    }
}

如果 ItemDisplayer 直接转换为 DefaultTimerView 或 OtherItemView ,则转换有效...但我想转换为任何 View 类型。

编辑:我在 UIKit 中做了这样的:

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let itemViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "ItemViewCell", for: indexPath) as! ItemViewCell
    let itemWithDisplay = itemWithDisplayArray[indexPath.row]
    itemViewCell.containerView = UIView(frame: itemViewCell.contentView.frame)
    itemViewCell.containerView!.addSubview(itemWithDisplay.getDisplayer() as! UIView) // <--the POINT is here: "as! UIView"
    itemViewCell.contentView.addSubview(itemViewCell.containerView!)
    itemWithDisplay.display()
    
    return itemViewCell
}

或在可可中:

class ItemGridNSView : NSView {
    private let itemWithDisplay : ItemWithDisplay
    
    init(frame frameRect: NSRect, itemWithDisplay: ItemWithDisplay) {
        self.itemWithDisplay = itemWithDisplay
        super.init(frame:frameRect);
        self.addSubview(itemWithDisplay.getDisplayer() as! NSView) // <-- Here too
        itemWithDisplay.display()
    }
...

我正在 SwiftUI 中寻找相同的解决方案。

我想保持领域层与视图逻辑的独立性,即任何视图特定信息(导入 SwiftUI)。我想在视图层中避免这种排序逻辑。

你能帮我解决这个问题吗?

提前感谢您提供的任何帮助!

标签: swiftui

解决方案


使ItemDisplayer符合View. 这样您就可以像普通的View.

public protocol ItemDisplayer: View {
    init(item: ItemObject)
}

然后进行更改ItemWithDisplay以便您可以使用ItemDisplayer(因为它现在具有相关的类型要求):

public struct ItemWithDisplay<Displayer: ItemDisplayer> {
    private let item:  ItemObject
    private let itemDisplayer : Displayer

    public init(item : ItemObject, displayer : Displayer) {
        self.item = item
        self.itemDisplayer = displayer
    }

    public func getDisplayer() -> Displayer {
        return itemDisplayer
    }

    public func getItem() -> ItemObject {
        return item
    }
}

最后,您现在可以使用getDisplayer()来获取视图,甚至无需将其转换为AnyView

VStack {
    item.getDisplayer()
}

但是,要使某些东西成为AnyView,您将执行以下操作:

VStack {
    AnyView(item.getDisplayer())
}

推荐阅读