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

#11
Anyway, the issue still exists.
Thinking mvvm-like all examples on that page breaks it.
Logic of UI contains in View.
In all cases is not possible to write unit-test to cover logic.

PS. I am still can't solve this.

**UPDATE**

I am ended with solution,

View file:

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

#12
The simplest way to avoid using an extra container like `HStack` is to annotate your `body` property as `@ViewBuilder`, like this:

@ViewBuilder
var body: some View {
if user.isLoggedIn {
MainView()
} else {
LoginView()
}
}
Reply

#13
Previous answers were correct, however, I would like to mention, you may use optional views inside you HStacks. Lets say you have an optional data eg. the users address. You may insert the following code:

````swift
// works!!
userViewModel.user.address.map { Text($0) }
````

Instead of the other approach:
````swift
// same logic, won't work
if let address = userViewModel.user.address {
Text(address)
}
````

Since it would return an Optional text, the framework handles it fine. This also means, using an expression instead of the if statement is also fine, like:

````swift
// works!!!
keychain.get("api-key") != nil ? TabView() : LoginView()
````

In your case, the two can be combined:

```swift
keychain.get("api-key").map { _ in TabView() } ?? LoginView()
```

*Using beta 4*
Reply

#14
Another approach using [ViewBuilder](

[To see links please register here]

) (which relies on the mentioned `ConditionalContent`)

> [buildEither](

[To see links please register here]

) + 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
```

(There's also [buildIf](

[To see links please register here]

), but I couldn't figure out its syntax yet. `¯\_(ツ)_/¯`)

----------

> 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

```

But it kinda feels wrong...

----------

Both examples produce the same result:

[![playground][1]][1]


[1]:
Reply

#15
Based on the comments I ended up going with this solution that will regenerate the view when the api key changes by using @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()
}
}
}
}
```
Reply

#16
You didn't include it in your question but I guess the error you're getting when going without the stack is the following?

> Function declares an opaque return type, but has no return statements in its body from which to infer an underlying type

The error gives you a good hint of what's going on but in order to understand it, you need to understand the concept of *opaque return types*. That's how you call the types prefixed with the `some` keyword. I didn't see any Apple engineers going deep into that subject at WWDC (maybe I missed the respective talk?), which is why I did a lot of research myself and wrote an article on how these types work and why they are used as return types in *SwiftUI*.

### 🔗 [What’s this “some” in SwiftUI?][1]

There is also a detailed technical explanation in another

### 🔗 [Stackoverflow post on opaque result types][2]


If you want to fully understand what's going on I recommend reading both.

----------

As a quick explanation here:

> ## General Rule:
>
> Functions or properties with an opaque result type (`some Type`)<br />
> **must always return the *same* concrete type**.

In your example, your `body` property returns a *different* type, depending on the condition:

var body: some View {
if someConditionIsTrue {
TabView()
} else {
LoginView()
}
}

If `someConditionIsTrue`, it would return a `TabView`, otherwise a `LoginView`. This violates the rule which is why the compiler complains.

If you wrap your condition in a stack view, the stack view will include the concrete types of both conditional branches in its own generic type:

HStack<ConditionalContent<TabView, LoginView>>

As a consequence, no matter which view is actually returned, the result type of the stack will always be the same and hence the compiler won't complain.


----------

### 💡 Supplemental:

There is actually a view component *SwiftUI* provides specifically for this use case and it's actually what stacks use internally as you can see in the example above:

### [ConditionalContent][3]

It has the following generic type, with the generic placeholder automatically being inferred from your implementation:

ConditionalContent<TrueContent, FalseContent>

I recommend using that view container rather that a stack because it makes its purpose semantically clear to other developers.


[1]:

[To see links please register here]

[2]:

[To see links please register here]

[3]:

[To see links please register here]

Reply



Forum Jump:


Users browsing this thread:
2 Guest(s)

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