如何使用 SwiftUI 递归渲染?

1 swift swiftui

我收到这么长的错误:

Function opaque return type was inferred as '_ConditionalContent<_ConditionalContent<some View, VStack<ForEach<[Dictionary<String, JSON>.Keys.Element], Dictionary<String, JSON>.Keys.Element, TupleView<(some View, some View)>?>>>, some View>' (aka '_ConditionalContent<_ConditionalContent<some View, VStack<ForEach<Array<String>, String, Optional<TupleView<(some View, some View)>>>>>, some View>'), which defines the opaque type in terms of itself
Run Code Online (Sandbox Code Playgroud)

我认为问题在于渲染的“递归”特征,因为错误日志本身也说:

它根据自身定义了不透明类型

如果我替换第二个 if-else-语句(如您在评论中看到的那样)并仅返回文本,那么我就不会收到错误。

Function opaque return type was inferred as '_ConditionalContent<_ConditionalContent<some View, VStack<ForEach<[Dictionary<String, JSON>.Keys.Element], Dictionary<String, JSON>.Keys.Element, TupleView<(some View, some View)>?>>>, some View>' (aka '_ConditionalContent<_ConditionalContent<some View, VStack<ForEach<Array<String>, String, Optional<TupleView<(some View, some View)>>>>>, some View>'), which defines the opaque type in terms of itself
Run Code Online (Sandbox Code Playgroud)

Method是一个递归方法,它应该渲染一个json树。

还有JSON

@ViewBuilder
func showNode(json: JSON) -> some View {
    if let v = json.get() as? String {
        Text("\(v)").padding()
    } else if let d = json.get() as? [String: JSON] {
        VStack {
            ForEach(d.keys.sorted(), id: \.self) { k in
                if let v = d[k] {
                    Text("\(k)").padding()
                    showNode(json: v)
                }
            }
        }
        // Text("").padding()
    } else {
        Text("").padding()
    }
}
Run Code Online (Sandbox Code Playgroud)

我所做的一切都将其渲染在屏幕上:

public enum JSON {
    case string(String)
    case number(Float)
    case object([String:JSON])
    case array([JSON])
    case bool(Bool)
    
    func get() -> Any {
        switch self {
        case .string(let v):
            return v
        case .number(let v):
            return v
        case .object(let v):
            return v
        case .array(let v):
            return v
        case .bool(let v):
            return v
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Swe*_*per 5

some View只是一种语言功能,允许您不必写出方法的整个返回类型。

如果您实际上尝试严格遵循所有语言规则来写出该方法的整个返回类型,您会发现遇到了问题。因为你需要知道 的返回类型showNode是什么,才能知道showNode! 这部分是由于其递归性质,部分是由于@ViewBuilder.

如果您想知道它们到底是什么_ConditionalContent,那么它们来自哪里buildEither,这就是您的 if 语句放入@ViewBuilder. TupleView来自buildBlock,它们的所有类型参数都由您放入其中的表达式的类型决定,其中之一是调用showNode,我们正在计算其类型。

您可以通过使用AnyView类型擦除视图来修复此问题:

func showNode(json: JSON, depth: Int = 1) -> AnyView {
    if let v = json.get() as? String {
        return AnyView(Text("\(v)").padding())
    } else if let d = json.get() as? [String: JSON] {
        return AnyView(VStack {
            ForEach(d.keys.sorted(), id: \.self) { k in
                if let v = d[k] {
                    Text("\(k)").padding()
                    showNode(json: v)
                }
            }
        })
    } else {
        return AnyView(Text("").padding())
    }
}
Run Code Online (Sandbox Code Playgroud)

或者创建一个新View类型来停止无限递归:

struct NodeView: View {
    let json: JSON
    
    var body: some View {
        if let v = json.get() as? String {
            Text("\(v)").padding()
        } else if let d = json.get() as? [String: JSON] {
            VStack {
                ForEach(d.keys.sorted(), id: \.self) { k in
                    if let v = d[k] {
                        Text("\(k)").padding()
                        NodeView(json: v)
                    }
                }
            }
        } else {
            Text("").padding()
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

一些附加说明:

  • 您现在解析 JSON 的方式不正确。JSONSerialization不会给你你自己的JSON类型。您可能应该使用自定义Codable实现,但具体如何做到这一点属于另一个问题。
  • 此代码绘制的视图实际上并不显示 JSON 的“级别”。不确定这是有意还是无意
  • if let v = json.get() as? String {不处理Bools 或Floats 或数组。如果你想处理这些,你也需要为它们写支票。