SwiftUI - 条件闭包的if let替代方案

20

我正在尝试在SwiftUI中实现以下内容:

struct PersonView: View {

    @State private var age: Int? = 0

    var body: some View {
        VStack {
            Text("Just a test")
            if let self.age > 0 {
                Text("Display Age: \(age)")
            } else {
                Text("Age must be greater than 0!")
            }
        }
    }
}

但是,在SwiftUI中,if let会导致以下错误:

包含控制流语句的闭包不能与函数构建器 'ViewBuilder'一起使用

因此,在研究了这个问题之后,我发现建议使用.map来展开age可选项。 因此,我已经修改了VStack中的代码如下:

Text("Just a test")
self.age.map {elem in
    if elem > 0 {
        Text("Display Age: \(elem)")
    } else {
        Text("Age must be greater than 0!")
    }
}

然而,在.map闭包中包含一个条件语句,会导致在调用VStack的行处出现以下错误:

'(ViewBuilder.Type) -> (C0, C1) -> TupleView<(C0, C1)>' 要求 '()' 符合 'View'

类型 '()' 不符合协议 'View'

有没有什么建议可以解决第二组错误?或者,是否有其他方法可以在SwiftUI中拆开可选项并对其进行评估?我真的很喜欢SwiftUI,但不敢相信拆开可选项会成为一个头疼的问题!

4个回答

14

对于这种情况,我更喜欢采用以下方法

struct PersonView: View {

    @State private var age: Int? = 0

    var body: some View {
        VStack {
            Text("Just a test")
            AgeText
        }
    }

    private var AgeText: some View {
        if let age = self.age, age > 0 {
            return Text("Display Age: \(age)")
        } else {
            return Text("Age must be greater than 0!")
        }
    }
}

请注意,如果您想在if let条件失败时尝试提供EmptyView(),则此方法无效。因此,建议使用.map可能更可取,如@LuLuGaGa所建议的那样。 - Ever Uribe
我很好奇为什么初始版本不起作用,以及这实际上是如何解决问题的?似乎(https://jasonzurita.com/swiftui-if-statement/)涵盖了一些主题,但它相当复杂。 - Zorayr
如果只是根据某些条件返回不同的String,那么最简单的方法(在实现和结果视图层次结构方面)就是只有一个var根据条件返回字符串,并且只有一个Text来显示它。例如Mojtaba的答案中的第二个解决方案。 - shim

12

Swift 5.3(Xcode 12)

现在您可以直接在视图构建器中使用条件绑定:

if let age = age {
    if age > 0 {
        Text("Display Age: \(age)")
    } else {
        Text("Age must be greater than 0!")
    }
} else {
    Text("Age not found")
}

重构(也适用于旧版Swift)

您可以通过使用函数将代码重构为更基本的形式:

var body: some View {
    VStack {
        Text("Just a test")
        Text(text(age: age)) // Using the function
    }
}

func text(age: Int?) -> String { // Defining the function
    guard let age = age else { return "Age not found" }
    if age > 0 { return "Display Age: \(age)" }
    else { return "Age must be greater than 0!" }
}

通常情况下,当您需要清理代码时,请使用函数。我们希望未来的Swift版本能够直接支持此功能。

感谢@Mojtaba进行的重构工作。在更好地组织代码的同时,也克服了SwiftUI不支持if letguard let语句的局限性。最终,我接受了Asperi的解决方案,因为它还重构了View对象。 - Vee

6
您正在尝试对年龄的值进行两次检查:首先确保它不是nil,然后检查它是否大于0。您可以使用map来消除可能的nil,然后使用三元运算符有条件地更改显示的文本。
var body: some View {
    VStack {
        Text("Just a test")
        age.map { Text( $0 > 0 ? "Display Age: \($0)" : "Age must be greater than 0!") }
    }
}

谢谢,@LuLuGaGa!使用三元运算符帮助我解决了问题。我最终接受了Asperi的解决方案,因为它是更通用的方法,有利于更好的代码组织,并且可以克服SwiftUI不支持if let的缺点。 - Vee

0
在我看来,这种情况下最易读的解决方案也是最短的,我使用了三元运算符:
if let age = age {
    Text(age > 0 ? "Display Age: \(age)" : "Age must be greater than 0!")
} else {
    Text("Age not found")
}

我甚至会去掉else部分


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接