在 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
语句,Button
、Text
等在else
的主体内)。
一个缺点是你必须显式地解包选项,这应该不是问题,因为 if 语句负责确保它不是 nil,但我仍然发现可选绑定通常是解包的更清晰途径选项。
编辑:
如果你真的不想显式地解开可选项,你可以使用ViewBuilder
s:
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 视图中展开可选的主要内容,如果未能解决你的问题,请参考以下文章
如何创建一个带有可选辅助 View 参数的 SwiftUI 视图?