Условно используйте представление в SwiftUI - PullRequest
4 голосов
/ 09 июня 2019

Я пытаюсь найти правильный способ условно включить представление с помощью swiftui. Я не смог использовать if непосредственно внутри представления, и мне пришлось использовать представление стека для этого.

Это работает, но кажется, что есть более чистый путь.

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

Ответы [ 5 ]

5 голосов
/ 10 июня 2019

Вы не включили его в свой вопрос, но я полагаю, ошибка, которую вы получаете при работе без стека, следующая?

Функция объявляет непрозрачный тип возвращаемого значения, но не имеет в своем теле операторов возврата, из которых можно вывести базовый тип

Ошибка дает вам хороший намек на то, что происходит, но чтобы понять это, вам необходимо понять концепцию непрозрачных типов возврата . Вот как вы называете типы с префиксом some. Я не видел, чтобы инженеры Apple углублялись в эту тему на WWDC (может быть, я пропустил соответствующий доклад?), Поэтому я сам провел много исследований и написал статью о том, как эти типы работают и почему они используются как типы возврата в SwiftUI .

? Что это за «некоторые» в SwiftUI?

Существует также подробное техническое объяснение в другом

? Пост Stackoverflow для непрозрачных типов результатов

Если вы хотите полностью понять, что происходит, я рекомендую прочитать оба.


В качестве быстрого объяснения здесь:

Общее правило:

Функции или свойства с непрозрачным типом результата (some Type)
всегда должен возвращать тот же конкретный тип .

В вашем примере ваше свойство body возвращает другой тип , в зависимости от условия:

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

Если someConditionIsTrue, он вернет TabView, иначе LoginView. Это нарушает правило, поэтому компилятор жалуется.

Если вы заключите свое условие в представление стека, представление стека будет включать конкретные типы обеих условных веток в свой собственный универсальный тип:

HStack<ConditionalContent<TabView, LoginView>>

Как следствие, независимо от того, какое представление фактически возвращено, тип результата стека всегда будет одинаковым, и, следовательно, компилятор не будет жаловаться.


? Дополнительно:

На самом деле есть компонент вида SwiftUI предоставляет специально для этого варианта использования, и это фактически то, что стеки используют внутри, как вы можете видеть в примере выше:

ConditionalContent

Он имеет следующий универсальный тип, а универсальный заполнитель автоматически выводится из вашей реализации:

ConditionalContent<TrueContent, FalseContent>

Я рекомендую использовать этот контейнер представления, а не стек, потому что он делает его назначение семантически понятным для других разработчиков.

3 голосов
/ 10 июня 2019

Основываясь на комментариях, я остановился на этом решении, которое будет восстанавливать представление при изменении ключа 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()
            }
        }
    }
}
0 голосов
/ 25 июля 2019

Предыдущие ответы были правильными, однако я хотел бы отметить, что вы можете использовать дополнительные представления внутри ваших HStacks. Допустим, у вас есть дополнительные данные, например. адрес пользователей. Вы можете вставить следующий код:

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

Вместо другого подхода:

// same logic, won't work
if let address = userViewModel.user.address {
    Text(address)
}

Поскольку он будет возвращать необязательный текст, фреймворк прекрасно с этим справится. Это также означает, что использование выражения вместо оператора if тоже нормально, например:

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

В вашем случае, оба могут быть объединены:

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

Использование бета 4

0 голосов
/ 16 июля 2019

Другой подход с использованием ViewBuilder (который опирается на упомянутое ConditionalContent)

buildEither + необязательно

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 , но я пока не могу понять его синтаксис. ¯\_(ツ)_/¯)


Можно также обернуть результат Viewв 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

Но это как-то не так ...


Оба примера дают одинаковый результат:

playground

0 голосов
/ 12 июля 2019

После поиска в Google в течение примерно 20 минут я закончил написание этого контейнера с условным инициализатором. Он пишет очень чисто, и его определение короткое, но оно не обновляется автоматически. Для этого поведения используйте ответ Michael .

Это работает, потому что SwiftUI автоматически выбрасывает ноль просмотров.

Для однократных условий

Использование

If(someBoolean) {
    Text("someBoolean is true!")
}

Источник

struct If<Output : View> : View {
    init?(_ value: Bool, product: @escaping () -> Output) {
        if value {
            self.product = product
        } else {
            return nil
        }
    }

    private let product: () -> Output

    var body: some View {
        product()
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...