Create an account

Very important

  • To access the important data of the forums, you must be active in each forum and especially in the leaks and database leaks section, send data and after sending the data and activity, data and important content will be opened and visible for you.
  • You will only see chat messages from people who are at or below your level.
  • More than 500,000 database leaks and millions of account leaks are waiting for you, so access and view with more activity.
  • Many important data are inactive and inaccessible for you, so open them with activity. (This will be done automatically)


Thread Rating:
  • 307 Vote(s) - 3.6 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Conditionally use view in SwiftUI

#1
I'm trying to figure out the correct way to conditionally include a view with swiftui. I wasn't able to use the `if` directly inside of a view and had to use a
stack view to do it.

This works but there seems like there would be a cleaner way.

```swift
var body: some View {
HStack() {
if keychain.get("api-key") != nil {
TabView()
} else {
LoginView()
}
}
}
```
Reply

#2
Here’s a very simple to use modifier which uses a boolean test to decide if a view will be rendered. Unlike other solutions posted here it doesn’t rely on the use of `ÀnyView`. This is how to use it:

var body: some View {
VStack {
FooView()
.onlyIf(someCondition)
}
}

This reads nicer than the default `if` … `then` construct as it removes the additional indentation.

To replace an `if` … `then` … `else` construct, this is the obvious solution:

var body: some View {
VStack {
FooView()
.onlyIf(someCondition)

BarView()
.onlyIf(!someCondition)
}
}

This is the definition of the `onlyIf` modifier:

struct OnlyIfModifier: ViewModifier {
var condition: Bool

func body(content: Content) -> some View {
if condition {
content
}
}
}

extension View {
func onlyIf(_ condition: Bool) -> some View {
modifier(OnlyIfModifier(condition: condition))
}
}

Give it a try – it will surely clean up your code and improve overall readability.
Reply

#3
If the error message is

> Closure containing control flow statement cannot be used with function builder 'ViewBuilder'

Just hide the complexity of the control flow from the ViewBuilder:

This works:

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")
}
}
}
}
Reply

#4
I needed to embed a view inside another conditionally, so I ended up creating a convenience `if` function:

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

This does return an AnyView, which is not ideal but feels like it is technically correct because you don't really know the result of this during compile time.

In my case, I needed to embed the view inside a ScrollView, so it looks like this:

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

But you could also use it to conditionally apply modifiers too:

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

**UPDATE:** Please read the drawbacks of using conditional modifiers before using this:

[To see links please register here]

Reply

#5
If you want to navigate to two different views using NavigationLink, you can navigate using ternary operator.



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")
}
}
Reply

#6
Extension with the condition param works well for me (iOS 14):

import SwiftUI

extension View {
func showIf(condition: Bool) -> AnyView {
if condition {
return AnyView(self)
}
else {
return AnyView(EmptyView())
}

}
}

Example usage:

ScrollView { ... }.showIf(condition: shouldShow)
Reply

#7
Use **Group** instead of HStack

var body: some View {
Group {
if keychain.get("api-key") != nil {
TabView()
} else {
LoginView()
}
}
}
Reply

#8
I extended @gabriellanata's answer for up to two conditions. You can add more if needed. You use it like this:

```swift
Text("Hello")
.if(0 == 1) { $0 + Text("World") }
.elseIf(let: Int("!")?.description) { $0 + Text($1) }
.else { $0.bold() }
```

The code:

```swift
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($0, item)
})
} else {
return self.if(false, transform: {
transform($0, 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($0, item)
}
} else {
optionalConditional = Conditional(condition: false) {
transform($0, 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 { $0 }
}
}
```
Reply

#9
I chose to solve this by creating a modifier that makes a view "visible" or "invisible". The implementation looks like the following:

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()
}
}
}
}

Then to use it to solve your example, you would simply invert the `isVisible` value as seen here:

var body: some View {
HStack() {
TabView().visible(keychain.get("api-key") != nil)
LoginView().visible(keychain.get("api-key") == nil)
}
}

I have considered wrapping this into some kind of an "If" view that would
take two views, one when the condition is true and one when the condition is
false, but I decided that my present solution is both more general and more
readable.
Reply

#10
**How about that?**

I have a conditional *contentView*, which either is a **text** or an **icon**. I solved the problem like this. Comments are very appreciated, since I don't know if this is really "swifty" or just a "hack", but it works:


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)
}
}
Reply



Forum Jump:


Users browsing this thread:
1 Guest(s)

©0Day  2016 - 2023 | All Rights Reserved.  Made with    for the community. Connected through