SwiftUI 如果让内部视图
Posted
技术标签:
【中文标题】SwiftUI 如果让内部视图【英文标题】:SwiftUI if let inside View 【发布时间】:2020-02-13 07:53:18 【问题描述】:我想在 View
中添加一个 if let 语句。
@ObservedObject var person: Person?
var body: some View
if person != nil
// this works
if let p = person
// Compiler error
包含控制流语句的闭包不能与函数生成器“ViewBuilder”一起使用
【问题讨论】:
不,目前不支持。您可以在视图构建器中拥有的唯一一种控制流是if...else
。
所以我应该强制转换这些情况?
使用强制转换不是一个好的解决方案。最好用 nil 检查并在 if-else 中处理
目前您可以强制转换/解包或调用不在@ViewBuilder
范围内的其他属性或函数,这样您就可以使用像if let
这样的正常控制流。
【参考方案1】:
是的,代码快照中请求的符号是不允许的,至少目前是这样,但是通过以下非常简单的方法 - 将控制流提取到函数中,可以实现预期的结果::: p>
var person: Person? // actually @ObservedObject does not allowed optional
var body: some View
VStack
if person != nil
// same as before
personViewIfExists() // << just extract it in helper function
private func personViewIfExists() -> some View // generates view conditionally
if let p = person
return ExistedPersonView(person: p) // << just for demo
在某些情况下,可能还需要以下函数变体(尽管它产生相同的结果)
private func personViewIfExists() -> some View
if let p = person
return AnyView(ExistedPersonView(person: p))
return AnyView(EmptyView())
【讨论】:
如果可能,应避免在 SwiftUI 中使用AnyView
,而应使用 @ViewBuilder
包装器。
以上评论见Demystify SwiftUI:developer.apple.com/videos/play/wwdc2021/10022【参考方案2】:
或者您可以创建自己的IfLet
视图生成器:
import SwiftUI
struct IfLet<Value, Content, NilContent>: View where Content: View, NilContent: View
let value: Value?
let contentBuilder: (Value) -> Content
let nilContentBuilder: () -> NilContent
init(_ optionalValue: Value?, @ViewBuilder whenPresent contentBuilder: @escaping (Value) -> Content, @ViewBuilder whenNil nilContentBuilder: @escaping () -> NilContent)
self.value = optionalValue
self.contentBuilder = contentBuilder
self.nilContentBuilder = nilContentBuilder
var body: some View
Group
if value != nil
contentBuilder(value!)
else
nilContentBuilder()
extension IfLet where NilContent == EmptyView
init(_ optionalValue: Value?, @ViewBuilder whenPresent contentBuilder: @escaping (Value) -> Content)
self.init(optionalValue, whenPresent: contentBuilder, whenNil: EmptyView() )
使用它,您现在可以执行以下操作:
var body: some View
IfLet(pieceOfData) realData in
// realData is no longer optional
如果您的选项是nil
,想要回复吗?
var body: some View
IfLet(pieceOfData, whenPresent: realData in
// realData is no longer optional
DataView(realData)
, whenNil:
EmptyDataView()
)
【讨论】:
【参考方案3】:更简单的方法是只使用.map()
如果user
有值,它将显示UserProfile
,如果user
为nil,则不显示:
var user: User?
var body: some View
user.map UserProfile(user: $0)
如果你想为 nil 值提供一个默认视图,你可以使用 nil-coalescing 操作符:
var user: User?
var body: some View
user.map UserProfile(user: $0) ?? UserProfile(user: .default)
但是,这要求两个视图属于同一类型,实际上,如果它们属于同一类型,最好只写UserProfile(user: $0 ?? .default)
。
有趣的是两个视图不是同一类型的情况。您可以通过将它们包装在 AnyView
中来擦除它们的类型,但此时它确实有点麻烦且难以阅读:
var user: User?
var body: some View
user.map AnyView(UserProfile(user: $0)) ?? AnyView(Text("Not logged in"))
因此,在这种情况下,我首选的方法是使用自定义 IfLet
结构(与 Procrastin8 的名称相同,但实现和语法不同),它允许我编写类似这样的东西,我发现它的意图和易于打字。
var user: User?
var body: some View
IfLet(user) UserProfile(user: $0)
.else Text("Not logged in")
我的IfLet
组件的代码如下。
struct IfLet<Wrapped, IfContent> : View where IfContent: View
let optionalValue: Wrapped?
let contentBuilder: (Wrapped) -> IfContent
init(_ optionalValue: Wrapped?, @ViewBuilder contentBuilder: @escaping (Wrapped) -> IfContent)
self.optionalValue = optionalValue
self.contentBuilder = contentBuilder
var body: some View
optionalValue.map contentBuilder($0)
func `else`<ElseContent:View>(@ViewBuilder contentBuilder: @escaping () -> ElseContent) -> some View
if optionalValue == nil
return AnyView(contentBuilder())
else
return AnyView(self)
享受吧!
【讨论】:
以上是关于SwiftUI 如果让内部视图的主要内容,如果未能解决你的问题,请参考以下文章