SwiftUI 视图:两个不同的初始值设定项:无法将类型 'Text' 的值转换为闭包结果类型 'Content'

SwiftUI View: two different initializers: cannot convert value of type 'Text' to closure result type 'Content'

代码:

import SwiftUI

public struct Snackbar<Content>: View where Content: View {
    private var content: Content

// Works OK
    public init(@ViewBuilder content: () -> Content) {
        self.content = content()
    }

    init(_ text: String) {
        self.init {
            Text(text) // cannot convert value of type 'Text' to closure result type 'Content'
                .font(.subheadline)
                .foregroundColor(.white)
                .multilineTextAlignment(.leading)
        }
    }

    public var body: some View {
        HStack {
            VStack(alignment: .leading, spacing: 4) {
                content
            }
            Spacer()
        }
        .frame(maxWidth: .infinity,
               minHeight: 26)
        .padding(.fullPadding)
        .background(Color.black)
        .clipShape(RoundedRectangle(cornerRadius: .defaultCornerRadius))
        .shadow(color: Color.black.opacity(0.125), radius: 4, y: 4)
        .padding()
    }
}

我收到这个错误:

cannot convert value of type 'Text' to closure result type 'Content'

我想要实现的目标是有 2 个单独的初始化器,一个用于 View 类型的内容,另一个是字符串的快捷方式,它将放置一个预定义的 Text 用一些样式代替 Content.

的组件

如果 Textsome View 并且我认为它应该编译,为什么我会收到此错误。

一种方法是使内容可选并使用另一个文本变量并基于 nil 值显示视图。

public struct Snackbar<Content>: View where Content: View {
    private var content: Content? // <= Here
    private var text: String = "" // <= Here
    
    // Works OK
    public init(@ViewBuilder content: () -> Content) {
        self.content = content()
    }
    
    init(_ text: String) {
        self.text = text // <= Here
    }
    
    public var body: some View {
        HStack {
            VStack(alignment: .leading, spacing: 4) {
                if let content = content { // <= Here
                    content
                } else {
                    Text(text)
                        .font(.subheadline)
                        .foregroundColor(.white)
                        .multilineTextAlignment(.leading)
                }
            }
            Spacer()
        }
        // Other code



您也可以使用AnyView

public struct Snackbar: View {
    private var content: AnyView // Here
    
    // Works OK
    public init<Content: View>(@ViewBuilder content: () -> Content) {
        self.content = AnyView(content()) // Here
    }
    
    init(_ text: String) {
        self.content = AnyView(Text(text)
                            .font(.subheadline)
                            .foregroundColor(.white)
                            .multilineTextAlignment(.leading)
        ) // Here
    }
    
    public var body: some View {
        HStack {
            VStack(alignment: .leading, spacing: 4) {
                self.content
            }
            Spacer()
        }

可以指定Content的类型。

代码:

public struct Snackbar<Content>: View where Content: View {
    private var content: Content

// Works OK
    public init(@ViewBuilder content: () -> Content) {
        self.content = content()
    }

    init(_ text: String) where Content == ModifiedContent<Text, _EnvironmentKeyWritingModifier<TextAlignment>> {
        self.init {
            Text(text)
                .font(.subheadline)
                .foregroundColor(.white)
                .multilineTextAlignment(.leading) as! ModifiedContent<Text, _EnvironmentKeyWritingModifier<TextAlignment>>
        }
    }

    /* ... */
}

这里唯一的区别是 init 之后的 whereinit 里面的类型的 force-cast。

为了避免特定的类型,你可以把它抽象成一个单独的视图:

init(_ text: String) where Content == ModifiedText {
    self.init {
        ModifiedText(text: text)
    }
}

/* ... */

struct ModifiedText: View {
    let text: String

    var body: some View {
        Text(text)
            .font(.subheadline)
            .foregroundColor(.white)
            .multilineTextAlignment(.leading)
    }
}

general-purpose 的解决方案是提供一个在语义上等同于 some View 的包装器。 AnyView 是内置的,可用于此目的。

init(_ text: String) where Content == AnyView {
  self.init {
    AnyView(
      Text(text)
        .font(.subheadline)
        .foregroundColor(.white)
        .multilineTextAlignment(.leading)
    )
  }
}

此外,将您的代码更改为

private let content: () -> Content

public init(@ViewBuilder content: @escaping () -> Content) {
  self.content = content
}

这样您就不必将 content 的结果包装在另一个闭包中。

VStack(alignment: .leading, spacing: 4, content: content)