你如何创建一个带有可选辅助 View 参数的 SwiftUI 视图?

jcd*_*cdl 12 ios swift swiftui

我正在尝试创建一个自定义 SwiftUI 视图,它的作用类似于默认视图,我可以在其中使用方法或可选的初始值设定项参数向视图添加额外的内容。

SomeCustomView(title: "string argument") {
    // some view
}

SomeCustomView(title: "hello") {
    // some view
}.sideContent {
    // another view
}

// This style is acceptable too
SomeCustomView(title: "hello", sideContent: { /* another view */ }) {
    // some view
}
Run Code Online (Sandbox Code Playgroud)

如何修改此视图结构以使其行为类似于上述示例?

struct SomeCustomView<Content>: View where Content: View {
    let title: String
    let content: Content

    init(title: String, @ViewBuilder content: () -> Content) {
        self.title = title
        self.content = content()
    }

    var body: some View {
        VStack {
            Text(title)
            content
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

理想情况下,我有两个不同的主体“模板”,我可以根据sideContent调用方法或sideContent设置参数在它们之间进行切换。例如,

var body: some View {
    VStack {
        Text(title)
        content
    }
}

// or

var otherBody: some View {
    HStack {
        VStack {
            Text(title)
            content
        }
        sideContent
    }
}
Run Code Online (Sandbox Code Playgroud)

jcd*_*cdl 18

编辑为在 Xcode 11.6 中工作

经过一些思考和一些反复试验,我想通了。事后看来似乎有点明显。

struct SomeCustomView<Content>: View where Content: View {
    let title: String
    let content: Content

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

    func sideContent<SideContent: View>(@ViewBuilder side: @escaping () -> SideContent) -> some View {
        HStack {
            body // body is just a View, so we can compose with this View
            side() 
        }
    }

    var body: some View {
        VStack {
            Text(title)
            content
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

它可以在有或没有方法调用的情况下工作。

SomeCustomView(title: "string argument") {
    // some view
}

SomeCustomView(title: "hello") {
    // some view
}.sideContent {
    // another view
}
Run Code Online (Sandbox Code Playgroud)

  • 这里有一个微妙但重要的错误:通过使用“body”,我们在当前时间点运行主体视图生成器,并忽略创建主体的视图。对于像此处所示的静态视图,这很好 - 但对于任何更动态的视图(例如,也许您有一些 @State 变量或首选项键),返回的视图将不再按预期运行,因为它不再封装任何状态与视图相关联。我建议不要使用主体,而是使用视图本身,它会继承任何状态等。 (3认同)
  • 优秀的解决方案。工作起来很有魅力,而且非常符合 SwiftUI 范式。 (2认同)

Joe*_* C. 6

我对容器视图遵循的模式是使用条件扩展一致性来支持不同变体的初始化程序。

这是一个带有可选页脚的简单面板视图的示例。

struct Panel<Content: View, Footer: View>: View {
    
    let content: Content
    let footer: Footer?
    
    init(@ViewBuilder content: () -> Content, footer: (() -> Footer)? = nil) {
        self.content = content()
        self.footer = footer?()
    }
    
    var body: some View {
        VStack(spacing: 0) {
            content

            // Conditionally check if footer has a value, if desirable.
            footer
        }
    }
}

// Support optional footer
extension Panel where Footer == EmptyView {
    init(@ViewBuilder content: () -> Content) {
        self.content = content()
        self.footer = nil
    }
}
Run Code Online (Sandbox Code Playgroud)

我相信这类似于 Apple 为支持内置类型的所有变体所做的工作。例如,这是Button.

@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension Button where Label == PrimitiveButtonStyleConfiguration.Label {

    /// Creates an instance representing the configuration of a
    /// `PrimitiveButtonStyle`.
    @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
    public init(_ configuration: PrimitiveButtonStyleConfiguration)
}

@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension Button where Label == Text {

    /// Creates an instance with a `Text` label generated from a localized title
    /// string.
    ///
    /// - Parameters:
    ///     - titleKey: The key for the localized title of `self`, describing
    ///       its purpose.
    ///     - action: The action to perform when `self` is triggered.
    public init(_ titleKey: LocalizedStringKey, action: @escaping () -> Void)

    /// Creates an instance with a `Text` label generated from a title string.
    ///
    /// - Parameters:
    ///     - title: The title of `self`, describing its purpose.
    ///     - action: The action to perform when `self` is triggered.
    public init<S>(_ title: S, action: @escaping () -> Void) where S : StringProtocol
}
Run Code Online (Sandbox Code Playgroud)

  • 这是传承儿童内容并以附加内容作为修饰符的好方法,感谢分享! (2认同)