在SwiftUI中有条件地使用视图

Mic*_*air 12 swift swiftui

我正在尝试找出正确的方法来有条件地包含swiftui视图。我无法直接在视图内部使用if,而不得不使用堆栈视图来做到这一点。

这可行,但似乎会有更清洁的方法。

    var body: some View {
        HStack() {
            if keychain.get("api-key") != nil {
                TabView()
            } else {
                LoginView()
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

gab*_*ata 54

我需要有条件地在另一个视图中嵌入一个视图,所以我最终创建了一个方便的if函数:

extension View {
   @ViewBuilder
   func `if`<Content: View>(_ conditional: Bool, content: (Self) -> Content) -> some View {
        if conditional {
            content(self)
        } else {
            self
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这确实返回了一个 AnyView,这并不理想,但感觉它在技术上是正确的,因为您在编译时并不真正知道结果。

就我而言,我需要将视图嵌入到 ScrollView 中,因此它看起来像这样:

var body: some View {
    VStack() {
        Text("Line 1")
        Text("Line 2")
    }
    .if(someCondition) { content in
        ScrollView(.vertical) { content }
    }
}
Run Code Online (Sandbox Code Playgroud)

但是您也可以使用它来有条件地应用修饰符:

var body: some View {
    Text("Some text")
    .if(someCondition) { content in
        content.foregroundColor(.red)
    }
}
Run Code Online (Sandbox Code Playgroud)

更新:请在使用之前阅读使用条件修饰符的缺点:https : //www.objc.io/blog/2021/08/24/conditional-view-modifiers/

  • 反引号 ` 破坏了 SwiftUI 预览,我只是将 \`if\` 更改为 ifConditional,它工作得很好。 (7认同)

小智 20

避免使用诸如这样的额外容器的最简单方法HStack是将body属性注释为@ViewBuilder,如下所示:

@ViewBuilder
var body: some View {
    if user.isLoggedIn {
        MainView()
    } else {
        LoginView()
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 正在使用它不喜欢的三元运算符‍♂️ (2认同)

Mik*_*hov 13

无论如何,问题仍然存在。像 mvvm 一样思考该页面上的所有示例都会破坏它。UI 的逻辑包含在 View 中。在所有情况下都不可能编写单元测试来覆盖逻辑。

附注。我仍然无法解决这个问题。

更新

我以解决方案结束,

查看文件:

import SwiftUI


struct RootView: View {

    @ObservedObject var viewModel: RatesListViewModel

    var body: some View {
        viewModel.makeView()
    }
}


extension RatesListViewModel {

    func makeView() -> AnyView {
        if isShowingEmpty {
            return AnyView(EmptyListView().environmentObject(self))
        } else {
            return AnyView(RatesListView().environmentObject(self))
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 在最初为 WPF 开发的 MVVM 中,视图模型是视图的抽象,因此我认为创建特定视图的“makeView()”不应该属于视图模型。视图不应包含域逻辑,但它可以包含表示逻辑。您可以将 `makeView()` 放入 `RootView` 中。 (2认同)

Mis*_*cha 11

您没有在问题中包括它,但我想以下是您在没有堆栈时遇到的错误?

函数声明了一个不透明的返回类型,但是在其主体中没有用于从其推断基础类型的返回语句

该错误为您提供了一个很好的提示,但要了解它,您需要了解不透明返回类型的概念。这就是您以some关键字开头的类型的调用方式。我没有看到任何Apple工程师在WWDC上深入探讨该主题(也许我错过了各自的演讲?),这就是为什么我自己进行了大量研究并撰写了有关这些类型的工作方式以及为什么将它们用作原型的文章的原因。在SwiftUI中返回类型。

SwiftUI中的“一些”是什么?

另一个中还有详细的技术说明

不透明结果类型上的Stackoverflow发布

如果您想完全了解正在发生的事情,建议您同时阅读两者。


作为此处的简要说明:

一般规则:

结果类型不透明(some Type)的函数或属性
必须始终返回相同的具体类型

在您的示例中,您的body属性根据条件返回不同的类型:

var body: some View {
    if someConditionIsTrue {
        TabView()
    } else {
        LoginView()
    }
}
Run Code Online (Sandbox Code Playgroud)

如果someConditionIsTrue返回,则返回TabView,否则返回LoginView。这违反了规则,这就是编译器抱怨的原因。

如果将条件包装在堆栈视图中,则堆栈视图将以其自己的通用类型包括两个条件分支的具体类型:

HStack<ConditionalContent<TabView, LoginView>>
Run Code Online (Sandbox Code Playgroud)

结果,无论实际上返回哪个视图,堆栈的结果类型将始终相同,因此编译器不会抱怨。


补充说明:

实际上,有一个SwiftUI为此用例专门提供的视图组件,实际上是栈内部使用的视图组件,如您在上面的示例中看到的那样:

条件内容

它具有以下通用类型,其中通用占位符是根据您的实现自动推断的:

ConditionalContent<TrueContent, FalseContent>
Run Code Online (Sandbox Code Playgroud)

我建议使用该视图容器,而不要使用堆栈,因为它在语义上对其他开发人员来说很明确。

  • `ConditionalContent` 还存在吗?您的链接返回 404。 (4认同)

Mic*_*air 6

根据评论,我最终使用了这个解决方案,当 api 键使用@EnvironmentObject 更改时,它将重新生成视图。

用户数据.swift

import SwiftUI
import Combine
import KeychainSwift

final class UserData: BindableObject  {
    let didChange = PassthroughSubject<UserData, Never>()
    let keychain = KeychainSwift()

    var apiKey : String? {
        get {
            keychain.get("api-key")
        }
        set {
            if let newApiKey : String = newValue {
                keychain.set(newApiKey, forKey: "api-key")
            } else {
                keychain.delete("api-key")
            }

            didChange.send(self)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

内容视图.swift

import SwiftUI

struct ContentView : View {

    @EnvironmentObject var userData: UserData

    var body: some View {
        Group() {
            if userData.apiKey != nil {
                TabView()
            } else {
                LoginView()
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)