在 SwiftUI 视图中展开可选

Posted

技术标签:

【中文标题】在 SwiftUI 视图中展开可选【英文标题】:Unwrapping optional in SwiftUI View 【发布时间】:2019-10-13 22:06:34 【问题描述】:

我尝试解开可选属性,但收到以下错误消息:

包含控制流语句的闭包不能与函数生成器“ViewBuilder”一起使用

我看不出我的代码有什么问题

HStack 
    if let height = profile.height 
        TagBox(field: "height", value: String(height))
    
    TagBox(field: "nationality", value: profile.nationality)
    Spacer()
.padding(.horizontal)

【问题讨论】:

“我看不出我的代码有什么问题” 第一个词是错误的。你不能说HStack if let。这些花括号不能包含可执行代码。它们只能包含视图。 没关系。你根本不能在那里说if let 那我该怎么做呢?在哪里解包? 我不知道您要解决什么问题。您没有显示任何上下文。什么是简介?你的目标是什么? 我的目标是:如果给出了“profile.height”(这是一个可选的),那么给我看一下包裹在大括号内的 TagBox(..)。 【参考方案1】:

在这种情况下,有两种使用选项的方法:

第一个,如果你不想在 profile.height 为 nil 的情况下显示这个视图:

profile.height.map( TagBox(field: "height", value: String($0)))

第二个,如果您希望显示此视图,但使用默认值:

TagBox(field: "height", value: String(profile.height ?? 0))

【讨论】:

但是解决方案 2 的问题是:标签框有背景颜色和其他东西。所以如果我通过 0 代替。将有一个空白的蓝色容器。我试图考虑在 TagBox 视图中执行 if 语句,但我不知道如何返回 void,或者什么都不返回。 第一种情况适用于当值为 nil 时您根本不想要此视图的情况。使用地图,如果高度为零,则视图不会被初始化。我不太明白你的反对意见。 如果所需的值是数据并且我们使用图像怎么办?您有什么建议?我有存储数据的数据模型?属性,但它是可选的,那么如何解包呢?谢谢! 嗨@MatrosovAlexander,你能把它作为一个单独的问题发布吗?输入评论有点太多了。谢谢 嗨@LuLuGaGa,是的,我把它贴在这里link谢谢!【参考方案2】:

Swift 5.3 - Xcode 12

ViewBuilder 中使用条件绑定现在非常好:

HStack 
    if let height = profile.height  // <- This works now in Xcode 12
        TagBox(field: "height", value: String(height))
    
    TagBox(field: "nationality", value: profile.nationality)
    Spacer()
.padding(.horizontal)

【讨论】:

【参考方案3】:

您可以在 Group 的视图构建器中包含 if 语句。我做了一个例子来说明:

var label1 = "label 1"
@State var label2: String?
var label3: String? = "label 3"

var body: some View 
    VStack 
        Text(label1)
        Group 
            if label2 != nil 
                Text(label2!)
            
            if label3 != nil 
                Text(label3!)
            
        
        Button(action: 
            self.label2 = "Button pressed!"
        ) 
            Text("Press me!")
        
    

当然,在这个例子中,你可以只做Text(label2 ?? ""),但在Text 有一个值之前会有一个间隙,而且在更复杂的情况下,这允许更多灵活性。如果您运行该代码,您将在“标签 3”正上方看到“标签 1”,如果您按下按钮,则“按下按钮!”将出现在“标签 1”和“标签 3”之间。

基本上,如果label2为nil,则标签有值时不会有空格,而是没有间隙,当label2被赋值时,以上所有内容并在它应该在的位置下方移动以适应它。

在您提供的代码中,您可以将其更改为:

HStack 
    Group 
        if profile.height != nil 
            TagBox(field: "height", value: String(profile.height!))
        
    
    TagBox(field: "nationality", value: profile.nationality)
    Spacer()
.padding(.horizontal)

这个解决方案的好处是,您可以在值为 nil(或不是)的情况下做更复杂的事情,甚至在值为 nil 的情况下提供默认视图(在我的示例中,这将是相当于添加else 对于if 语句,ButtonText 等在else 的主体内)。

一个缺点是你必须显式地解包选项,这应该不是问题,因为 if 语句负责确保它不是 nil,但我仍然发现可选绑定通常是解包的更清晰途径选项。

编辑

如果你真的不想显式地解开可选项,你可以使用ViewBuilders:

var label1 = "label 1"
@State var label2: String?
var label3: String? = "label 3"
var body: some View 
    VStack 
        Text(label1)
        Group 
            self.conditionalText(self.label2)
            if label3 != nil 
                Text(label3!)
            
        
        Button(action: 
            self.label2 = "Button pressed!"
        ) 
            Text("Press me!")
        
    

func conditionalText(_ text: String?) -> some View 
    if let text = text 
        return ViewBuilder.buildIf(Text(text))
     else 
        print("Text is nil")
    
    let view: Text? = nil
    return ViewBuilder.buildIf(view)

此方法还强调了如何在 Group 中包含可执行代码,只要它位于返回视图的函数中。

【讨论】:

我也来到了那个独奏,但我认为这不是一个很好的独奏,因为你可以使用展开,这对我来说看起来更好。不过还是非常感谢你 @seref 重要的是要记住我在评论中所说的话。你不能在这里执行可执行代码! Group 中的if 不是普通的if;这是一个特殊的分配,允许您有条件地显示视图。所以这是一个很好的解决方案。 @matt 您实际上可以通过一种变通方法执行可执行代码 - 如果您创建一个返回视图的函数,并在该函数中有可执行代码,然后在if 语句,一切都应该正确编译。 我个人认为这应该是公认的答案,因为它解释了为什么原始问题的 syntax 失败以及如何处理。 查看编辑以获取在组中为可执行代码使用函数的示例,以及如何使用该函数来解开选项,就像 OP 在他们的问题帖子中所做的那样。【参考方案4】:

因此,如果强制展开让您感到不安,您可以使用我制作的以下自定义视图:

struct OptionalView<Value, Content>: View where Content: View 
    private var content: Content

    init?(_ value: Value?, @ViewBuilder content: @escaping (Value) -> Content) 
        guard let value = value else  return nil 
        self.content = content(value)
    

    var body: some View 
        content
    

并像这样使用它:

OptionalView(profile.height)  height in
    TagBox(field: "height", value: String(height))

我写了一篇关于这个的帖子并制作了一系列可选视图here

【讨论】:

? swiftbysundell.com/tips/optional-swiftui-views

以上是关于在 SwiftUI 视图中展开可选的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI:防止视图在 VStack 内展开

如何在 SwiftUI 中制作“显示”式的折叠/展开动画?

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

SwiftUI - 视图从框架底部展开

水平 ScrollView (SwiftUI) 中的动画展开/折叠组

如何将可选视图传递给 SwiftUI 中的另一个视图?