在 SwiftUI 中有条件地使用视图
Conditionally use view in SwiftUI
我正在尝试找出使用 swiftui 有条件地包含视图的正确方法。我无法直接在视图中使用 if
,必须使用
堆栈视图来做到这一点。
这可行,但似乎会有更简洁的方法。
var body: some View {
HStack() {
if keychain.get("api-key") != nil {
TabView()
} else {
LoginView()
}
}
}
你没有把它包括在你的问题中,但我猜你在没有堆栈的情况下得到的错误如下?
Function declares an opaque return type, but has no return statements in its body from which to infer an underlying type
该错误很好地提示了正在发生的事情,但为了理解它,您需要了解 不透明 return 类型 的概念。这就是您如何调用以 some
关键字为前缀的类型。我没有看到任何 Apple 工程师在 WWDC 上深入研究这个主题(也许我错过了相应的演讲?),这就是为什么我自己做了很多研究并写了一篇文章来介绍这些类型的工作原理以及为什么它们被用作return 键入 SwiftUI。
What’s this “some” in SwiftUI?
另外一篇也有详细的技术讲解
如果您想完全了解发生了什么,我建议您同时阅读这两本书。
这里做一个简单的解释:
General Rule:
Functions or properties with an opaque result type (some Type
)
must always return the same concrete type.
在您的示例中,您的 body
属性 return 是 不同的 类型,具体取决于条件:
var body: some View {
if someConditionIsTrue {
TabView()
} else {
LoginView()
}
}
如果someConditionIsTrue
,它将return一个TabView
,否则一个LoginView
。这违反了编译器抱怨的规则。
如果将条件包装在堆栈视图中,堆栈视图将在其自己的通用类型中包含两个条件分支的具体类型:
HStack<ConditionalContent<TabView, LoginView>>
因此,无论实际 returned 哪个视图,堆栈的结果类型将始终相同,因此编译器不会报错。
补充:
实际上有一个视图组件 SwiftUI 专门为此用例提供,它实际上是堆栈在内部使用的组件,如您在上面的示例中所见:
ConditionalContent
它具有以下通用类型,通用占位符会自动从您的实现中推断出来:
ConditionalContent<TrueContent, FalseContent>
我建议使用该视图容器而不是堆栈,因为它使其他开发人员在语义上清楚其目的。
根据评论,我最终采用了此解决方案,该解决方案将在 api 键通过使用@EnvironmentObject 更改时重新生成视图。
UserData.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)
}
}
}
ContentView.swift
import SwiftUI
struct ContentView : View {
@EnvironmentObject var userData: UserData
var body: some View {
Group() {
if userData.apiKey != nil {
TabView()
} else {
LoginView()
}
}
}
}
另一种使用ViewBuilder的方法(依赖于提到的ConditionalContent
)
buildEither + optional
import PlaygroundSupport
import SwiftUI
var isOn: Bool?
struct TurnedOnView: View {
var body: some View {
Image(systemName: "circle.fill")
}
}
struct TurnedOffView: View {
var body: some View {
Image(systemName: "circle")
}
}
struct ContentView: View {
var body: some View {
ViewBuilder.buildBlock(
isOn == true ?
ViewBuilder.buildEither(first: TurnedOnView()) :
ViewBuilder.buildEither(second: TurnedOffView())
)
}
}
let liveView = UIHostingController(rootView: ContentView())
PlaygroundPage.current.liveView = liveView
(还有buildIf,但我还没弄清楚它的语法。¯\_(ツ)_/¯
)
One could also wrap the result View
into AnyView
import PlaygroundSupport
import SwiftUI
let isOn: Bool = false
struct TurnedOnView: View {
var body: some View {
Image(systemName: "circle.fill")
}
}
struct TurnedOffView: View {
var body: some View {
Image(systemName: "circle")
}
}
struct ContentView: View {
var body: AnyView {
isOn ?
AnyView(TurnedOnView()) :
AnyView(TurnedOffView())
}
}
let liveView = UIHostingController(rootView: ContentView())
PlaygroundPage.current.liveView = liveView
但感觉有点不对...
两个例子产生相同的结果:
之前的答案是正确的,但是,我想提一下,您可以在 HStacks 中使用可选视图。假设您有一个可选数据,例如。用户地址。您可以插入以下代码:
// works!!
userViewModel.user.address.map { Text([=10=]) }
代替其他方法:
// same logic, won't work
if let address = userViewModel.user.address {
Text(address)
}
因为它会 return 可选文本,所以框架可以很好地处理它。这也意味着,使用表达式而不是 if 语句也可以,例如:
// works!!!
keychain.get("api-key") != nil ? TabView() : LoginView()
在你的情况下,两者可以结合使用:
keychain.get("api-key").map { _ in TabView() } ?? LoginView()
使用测试版 4
避免使用像 HStack
这样的额外容器的最简单方法是将 body
属性 注释为 @ViewBuilder
,如下所示:
@ViewBuilder
var body: some View {
if user.isLoggedIn {
MainView()
} else {
LoginView()
}
}
我需要有条件地将一个视图嵌入到另一个视图中,所以我最终创建了一个方便的 if
函数:
extension View {
@ViewBuilder
func `if`<Content: View>(_ conditional: Bool, content: (Self) -> Content) -> some View {
if conditional {
content(self)
} else {
self
}
}
}
这会 return AnyView,这并不理想,但感觉它在技术上是正确的,因为在编译期间您并不真正知道它的结果。
在我的例子中,我需要将视图嵌入到 ScrollView 中,所以它看起来像这样:
var body: some View {
VStack() {
Text("Line 1")
Text("Line 2")
}
.if(someCondition) { content in
ScrollView(.vertical) { content }
}
}
但您也可以使用它来有条件地应用修饰符:
var body: some View {
Text("Some text")
.if(someCondition) { content in
content.foregroundColor(.red)
}
}
更新:请在使用之前阅读使用条件修饰符的缺点:https://www.objc.io/blog/2021/08/24/conditional-view-modifiers/
无论如何,问题依然存在。
像该页面上的所有示例一样思考 mvvm 会破坏它。
UI 的逻辑包含在视图中。
在所有情况下都不可能编写单元测试来覆盖逻辑。
PS。我还是解决不了。
更新
我的解决方案结束了,
查看文件:
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))
}
}
}
怎么样?
我有一个条件 contentView,它是 text 或 icon。我这样解决了这个问题。非常感谢评论,因为我不知道这是真的 "swifty" 还是只是 "hack",但它有效:
private var contentView : some View {
switch kind {
case .text(let text):
let textView = Text(text)
.font(.body)
.minimumScaleFactor(0.5)
.padding(8)
.frame(height: contentViewHeight)
return AnyView(textView)
case .icon(let iconName):
let iconView = Image(systemName: iconName)
.font(.title)
.frame(height: contentViewHeight)
return AnyView(iconView)
}
}
我选择通过创建一个生成视图 "visible" 或 "invisible" 的修饰符来解决这个问题。实现如下所示:
import Foundation
import SwiftUI
public extension View {
/**
Returns a view that is visible or not visible based on `isVisible`.
*/
func visible(_ isVisible: Bool) -> some View {
modifier(VisibleModifier(isVisible: isVisible))
}
}
fileprivate struct VisibleModifier: ViewModifier {
let isVisible: Bool
func body(content: Content) -> some View {
Group {
if isVisible {
content
} else {
EmptyView()
}
}
}
}
然后使用它来解决您的示例,您只需反转 isVisible
值,如下所示:
var body: some View {
HStack() {
TabView().visible(keychain.get("api-key") != nil)
LoginView().visible(keychain.get("api-key") == nil)
}
}
我考虑过将其包装成某种 "If" 视图
采取两种观点,一种是条件为真,一种是条件为真
错误,但我认为我目前的解决方案更通用,也更
可读。
如果报错信息是
Closure containing control flow statement cannot be used with function builder 'ViewBuilder'
只需从 ViewBuilder 中隐藏控制流的复杂性:
这个有效:
struct TestView: View {
func hiddenComplexControlflowExpression() -> Bool {
// complex condition goes here, like "if let" or "switch"
return true
}
var body: some View {
HStack() {
if hiddenComplexControlflowExpression() {
Text("Hello")
} else {
Image("test")
}
if hiddenComplexControlflowExpression() {
Text("Without else")
}
}
}
}
我将@gabriellanata 的回答扩展到最多两个条件。如果需要,您可以添加更多。你这样使用它:
Text("Hello")
.if(0 == 1) { [=10=] + Text("World") }
.elseIf(let: Int("!")?.description) { [=10=] + Text() }
.else { [=10=].bold() }
代码:
extension View {
func `if`<TrueContent>(_ condition: Bool, @ViewBuilder transform: @escaping (Self) -> TrueContent)
-> ConditionalWrapper1<Self, TrueContent> where TrueContent: View {
ConditionalWrapper1<Self, TrueContent>(content: { self },
conditional: Conditional<Self, TrueContent>(condition: condition,
transform: transform))
}
func `if`<TrueContent: View, Item>(`let` item: Item?, @ViewBuilder transform: @escaping (Self, Item) -> TrueContent)
-> ConditionalWrapper1<Self, TrueContent> {
if let item = item {
return self.if(true, transform: {
transform([=11=], item)
})
} else {
return self.if(false, transform: {
transform([=11=], item!)
})
}
}
}
struct Conditional<Content: View, Trans: View> {
let condition: Bool
let transform: (Content) -> Trans
}
struct ConditionalWrapper1<Content: View, Trans1: View>: View {
var content: () -> Content
var conditional: Conditional<Content, Trans1>
func elseIf<Trans2: View>(_ condition: Bool, @ViewBuilder transform: @escaping (Content) -> Trans2)
-> ConditionalWrapper2<Content, Trans1, Trans2> {
ConditionalWrapper2(content: content,
conditionals: (conditional,
Conditional(condition: condition,
transform: transform)))
}
func elseIf<Trans2: View, Item>(`let` item: Item?, @ViewBuilder transform: @escaping (Content, Item) -> Trans2)
-> ConditionalWrapper2<Content, Trans1, Trans2> {
let optionalConditional: Conditional<Content, Trans2>
if let item = item {
optionalConditional = Conditional(condition: true) {
transform([=11=], item)
}
} else {
optionalConditional = Conditional(condition: false) {
transform([=11=], item!)
}
}
return ConditionalWrapper2(content: content,
conditionals: (conditional, optionalConditional))
}
func `else`<ElseContent: View>(@ViewBuilder elseTransform: @escaping (Content) -> ElseContent)
-> ConditionalWrapper2<Content, Trans1, ElseContent> {
ConditionalWrapper2(content: content,
conditionals: (conditional,
Conditional(condition: !conditional.condition,
transform: elseTransform)))
}
var body: some View {
Group {
if conditional.condition {
conditional.transform(content())
} else {
content()
}
}
}
}
struct ConditionalWrapper2<Content: View, Trans1: View, Trans2: View>: View {
var content: () -> Content
var conditionals: (Conditional<Content, Trans1>, Conditional<Content, Trans2>)
func `else`<ElseContent: View>(@ViewBuilder elseTransform: (Content) -> ElseContent) -> some View {
Group {
if conditionals.0.condition {
conditionals.0.transform(content())
} else if conditionals.1.condition {
conditionals.1.transform(content())
} else {
elseTransform(content())
}
}
}
var body: some View {
self.else { [=11=] }
}
}
使用Group代替HStack
var body: some View {
Group {
if keychain.get("api-key") != nil {
TabView()
} else {
LoginView()
}
}
}
带有条件参数的扩展对我来说效果很好 (iOS 14):
import SwiftUI
extension View {
func showIf(condition: Bool) -> AnyView {
if condition {
return AnyView(self)
}
else {
return AnyView(EmptyView())
}
}
}
用法示例:
ScrollView { ... }.showIf(condition: shouldShow)
如果您想使用 NavigationLink 导航到两个不同的视图,您可以使用三元运算符进行导航。
let profileView = ProfileView()
.environmentObject(profileViewModel())
.navigationBarTitle("\(user.fullName)", displayMode: .inline)
let otherProfileView = OtherProfileView(data: user)
.environmentObject(profileViewModel())
.navigationBarTitle("\(user.fullName)", displayMode: .inline)
NavigationLink(destination: profileViewModel.userName == user.userName ? AnyView(profileView) : AnyView(otherProfileView)) {
HStack {
Text("Navigate")
}
}
我正在尝试找出使用 swiftui 有条件地包含视图的正确方法。我无法直接在视图中使用 if
,必须使用
堆栈视图来做到这一点。
这可行,但似乎会有更简洁的方法。
var body: some View {
HStack() {
if keychain.get("api-key") != nil {
TabView()
} else {
LoginView()
}
}
}
你没有把它包括在你的问题中,但我猜你在没有堆栈的情况下得到的错误如下?
Function declares an opaque return type, but has no return statements in its body from which to infer an underlying type
该错误很好地提示了正在发生的事情,但为了理解它,您需要了解 不透明 return 类型 的概念。这就是您如何调用以 some
关键字为前缀的类型。我没有看到任何 Apple 工程师在 WWDC 上深入研究这个主题(也许我错过了相应的演讲?),这就是为什么我自己做了很多研究并写了一篇文章来介绍这些类型的工作原理以及为什么它们被用作return 键入 SwiftUI。
What’s this “some” in SwiftUI?
另外一篇也有详细的技术讲解
如果您想完全了解发生了什么,我建议您同时阅读这两本书。
这里做一个简单的解释:
General Rule:
Functions or properties with an opaque result type (
some Type
)
must always return the same concrete type.
在您的示例中,您的 body
属性 return 是 不同的 类型,具体取决于条件:
var body: some View {
if someConditionIsTrue {
TabView()
} else {
LoginView()
}
}
如果someConditionIsTrue
,它将return一个TabView
,否则一个LoginView
。这违反了编译器抱怨的规则。
如果将条件包装在堆栈视图中,堆栈视图将在其自己的通用类型中包含两个条件分支的具体类型:
HStack<ConditionalContent<TabView, LoginView>>
因此,无论实际 returned 哪个视图,堆栈的结果类型将始终相同,因此编译器不会报错。
补充:
实际上有一个视图组件 SwiftUI 专门为此用例提供,它实际上是堆栈在内部使用的组件,如您在上面的示例中所见:
ConditionalContent
它具有以下通用类型,通用占位符会自动从您的实现中推断出来:
ConditionalContent<TrueContent, FalseContent>
我建议使用该视图容器而不是堆栈,因为它使其他开发人员在语义上清楚其目的。
根据评论,我最终采用了此解决方案,该解决方案将在 api 键通过使用@EnvironmentObject 更改时重新生成视图。
UserData.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)
}
}
}
ContentView.swift
import SwiftUI
struct ContentView : View {
@EnvironmentObject var userData: UserData
var body: some View {
Group() {
if userData.apiKey != nil {
TabView()
} else {
LoginView()
}
}
}
}
另一种使用ViewBuilder的方法(依赖于提到的ConditionalContent
)
buildEither + optional
import PlaygroundSupport
import SwiftUI
var isOn: Bool?
struct TurnedOnView: View {
var body: some View {
Image(systemName: "circle.fill")
}
}
struct TurnedOffView: View {
var body: some View {
Image(systemName: "circle")
}
}
struct ContentView: View {
var body: some View {
ViewBuilder.buildBlock(
isOn == true ?
ViewBuilder.buildEither(first: TurnedOnView()) :
ViewBuilder.buildEither(second: TurnedOffView())
)
}
}
let liveView = UIHostingController(rootView: ContentView())
PlaygroundPage.current.liveView = liveView
(还有buildIf,但我还没弄清楚它的语法。¯\_(ツ)_/¯
)
One could also wrap the result
View
intoAnyView
import PlaygroundSupport
import SwiftUI
let isOn: Bool = false
struct TurnedOnView: View {
var body: some View {
Image(systemName: "circle.fill")
}
}
struct TurnedOffView: View {
var body: some View {
Image(systemName: "circle")
}
}
struct ContentView: View {
var body: AnyView {
isOn ?
AnyView(TurnedOnView()) :
AnyView(TurnedOffView())
}
}
let liveView = UIHostingController(rootView: ContentView())
PlaygroundPage.current.liveView = liveView
但感觉有点不对...
两个例子产生相同的结果:
之前的答案是正确的,但是,我想提一下,您可以在 HStacks 中使用可选视图。假设您有一个可选数据,例如。用户地址。您可以插入以下代码:
// works!!
userViewModel.user.address.map { Text([=10=]) }
代替其他方法:
// same logic, won't work
if let address = userViewModel.user.address {
Text(address)
}
因为它会 return 可选文本,所以框架可以很好地处理它。这也意味着,使用表达式而不是 if 语句也可以,例如:
// works!!!
keychain.get("api-key") != nil ? TabView() : LoginView()
在你的情况下,两者可以结合使用:
keychain.get("api-key").map { _ in TabView() } ?? LoginView()
使用测试版 4
避免使用像 HStack
这样的额外容器的最简单方法是将 body
属性 注释为 @ViewBuilder
,如下所示:
@ViewBuilder
var body: some View {
if user.isLoggedIn {
MainView()
} else {
LoginView()
}
}
我需要有条件地将一个视图嵌入到另一个视图中,所以我最终创建了一个方便的 if
函数:
extension View {
@ViewBuilder
func `if`<Content: View>(_ conditional: Bool, content: (Self) -> Content) -> some View {
if conditional {
content(self)
} else {
self
}
}
}
这会 return AnyView,这并不理想,但感觉它在技术上是正确的,因为在编译期间您并不真正知道它的结果。
在我的例子中,我需要将视图嵌入到 ScrollView 中,所以它看起来像这样:
var body: some View {
VStack() {
Text("Line 1")
Text("Line 2")
}
.if(someCondition) { content in
ScrollView(.vertical) { content }
}
}
但您也可以使用它来有条件地应用修饰符:
var body: some View {
Text("Some text")
.if(someCondition) { content in
content.foregroundColor(.red)
}
}
更新:请在使用之前阅读使用条件修饰符的缺点:https://www.objc.io/blog/2021/08/24/conditional-view-modifiers/
无论如何,问题依然存在。 像该页面上的所有示例一样思考 mvvm 会破坏它。 UI 的逻辑包含在视图中。 在所有情况下都不可能编写单元测试来覆盖逻辑。
PS。我还是解决不了。
更新
我的解决方案结束了,
查看文件:
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))
}
}
}
怎么样?
我有一个条件 contentView,它是 text 或 icon。我这样解决了这个问题。非常感谢评论,因为我不知道这是真的 "swifty" 还是只是 "hack",但它有效:
private var contentView : some View {
switch kind {
case .text(let text):
let textView = Text(text)
.font(.body)
.minimumScaleFactor(0.5)
.padding(8)
.frame(height: contentViewHeight)
return AnyView(textView)
case .icon(let iconName):
let iconView = Image(systemName: iconName)
.font(.title)
.frame(height: contentViewHeight)
return AnyView(iconView)
}
}
我选择通过创建一个生成视图 "visible" 或 "invisible" 的修饰符来解决这个问题。实现如下所示:
import Foundation
import SwiftUI
public extension View {
/**
Returns a view that is visible or not visible based on `isVisible`.
*/
func visible(_ isVisible: Bool) -> some View {
modifier(VisibleModifier(isVisible: isVisible))
}
}
fileprivate struct VisibleModifier: ViewModifier {
let isVisible: Bool
func body(content: Content) -> some View {
Group {
if isVisible {
content
} else {
EmptyView()
}
}
}
}
然后使用它来解决您的示例,您只需反转 isVisible
值,如下所示:
var body: some View {
HStack() {
TabView().visible(keychain.get("api-key") != nil)
LoginView().visible(keychain.get("api-key") == nil)
}
}
我考虑过将其包装成某种 "If" 视图 采取两种观点,一种是条件为真,一种是条件为真 错误,但我认为我目前的解决方案更通用,也更 可读。
如果报错信息是
Closure containing control flow statement cannot be used with function builder 'ViewBuilder'
只需从 ViewBuilder 中隐藏控制流的复杂性:
这个有效:
struct TestView: View {
func hiddenComplexControlflowExpression() -> Bool {
// complex condition goes here, like "if let" or "switch"
return true
}
var body: some View {
HStack() {
if hiddenComplexControlflowExpression() {
Text("Hello")
} else {
Image("test")
}
if hiddenComplexControlflowExpression() {
Text("Without else")
}
}
}
}
我将@gabriellanata 的回答扩展到最多两个条件。如果需要,您可以添加更多。你这样使用它:
Text("Hello")
.if(0 == 1) { [=10=] + Text("World") }
.elseIf(let: Int("!")?.description) { [=10=] + Text() }
.else { [=10=].bold() }
代码:
extension View {
func `if`<TrueContent>(_ condition: Bool, @ViewBuilder transform: @escaping (Self) -> TrueContent)
-> ConditionalWrapper1<Self, TrueContent> where TrueContent: View {
ConditionalWrapper1<Self, TrueContent>(content: { self },
conditional: Conditional<Self, TrueContent>(condition: condition,
transform: transform))
}
func `if`<TrueContent: View, Item>(`let` item: Item?, @ViewBuilder transform: @escaping (Self, Item) -> TrueContent)
-> ConditionalWrapper1<Self, TrueContent> {
if let item = item {
return self.if(true, transform: {
transform([=11=], item)
})
} else {
return self.if(false, transform: {
transform([=11=], item!)
})
}
}
}
struct Conditional<Content: View, Trans: View> {
let condition: Bool
let transform: (Content) -> Trans
}
struct ConditionalWrapper1<Content: View, Trans1: View>: View {
var content: () -> Content
var conditional: Conditional<Content, Trans1>
func elseIf<Trans2: View>(_ condition: Bool, @ViewBuilder transform: @escaping (Content) -> Trans2)
-> ConditionalWrapper2<Content, Trans1, Trans2> {
ConditionalWrapper2(content: content,
conditionals: (conditional,
Conditional(condition: condition,
transform: transform)))
}
func elseIf<Trans2: View, Item>(`let` item: Item?, @ViewBuilder transform: @escaping (Content, Item) -> Trans2)
-> ConditionalWrapper2<Content, Trans1, Trans2> {
let optionalConditional: Conditional<Content, Trans2>
if let item = item {
optionalConditional = Conditional(condition: true) {
transform([=11=], item)
}
} else {
optionalConditional = Conditional(condition: false) {
transform([=11=], item!)
}
}
return ConditionalWrapper2(content: content,
conditionals: (conditional, optionalConditional))
}
func `else`<ElseContent: View>(@ViewBuilder elseTransform: @escaping (Content) -> ElseContent)
-> ConditionalWrapper2<Content, Trans1, ElseContent> {
ConditionalWrapper2(content: content,
conditionals: (conditional,
Conditional(condition: !conditional.condition,
transform: elseTransform)))
}
var body: some View {
Group {
if conditional.condition {
conditional.transform(content())
} else {
content()
}
}
}
}
struct ConditionalWrapper2<Content: View, Trans1: View, Trans2: View>: View {
var content: () -> Content
var conditionals: (Conditional<Content, Trans1>, Conditional<Content, Trans2>)
func `else`<ElseContent: View>(@ViewBuilder elseTransform: (Content) -> ElseContent) -> some View {
Group {
if conditionals.0.condition {
conditionals.0.transform(content())
} else if conditionals.1.condition {
conditionals.1.transform(content())
} else {
elseTransform(content())
}
}
}
var body: some View {
self.else { [=11=] }
}
}
使用Group代替HStack
var body: some View {
Group {
if keychain.get("api-key") != nil {
TabView()
} else {
LoginView()
}
}
}
带有条件参数的扩展对我来说效果很好 (iOS 14):
import SwiftUI
extension View {
func showIf(condition: Bool) -> AnyView {
if condition {
return AnyView(self)
}
else {
return AnyView(EmptyView())
}
}
}
用法示例:
ScrollView { ... }.showIf(condition: shouldShow)
如果您想使用 NavigationLink 导航到两个不同的视图,您可以使用三元运算符进行导航。
let profileView = ProfileView()
.environmentObject(profileViewModel())
.navigationBarTitle("\(user.fullName)", displayMode: .inline)
let otherProfileView = OtherProfileView(data: user)
.environmentObject(profileViewModel())
.navigationBarTitle("\(user.fullName)", displayMode: .inline)
NavigationLink(destination: profileViewModel.userName == user.userName ? AnyView(profileView) : AnyView(otherProfileView)) {
HStack {
Text("Navigate")
}
}