Предполагая, что у вас есть два основных вида (например, LoginView
и MainView
), вы можете переходить между ними несколькими способами. Что вам понадобится:
- Некоторое состояние, которое определяет, что будет отображаться
- Некоторое представление обтекания, которое будет переходить между двумя раскладками при изменении # 1
- Некоторый способ передачи данных между представлениями
В этом ответе я объединю # 1 & # 3 в объекте модели и покажу два примера для # 2. Есть много способов сделать это, так что поиграйте и посмотрите, что работает лучше для вас.
Обратите внимание, что есть много кода только для стилизации представлений, чтобы вы могли видеть, что происходит. Я прокомментировал критические биты.
Изображения (метод непрозрачности слева, метод смещения справа)

Модель (это удовлетворяет # 1 и # 3)
class LoginStateModel: ObservableObject {
// changing this will change the main view
@Published var loggedIn = false
// will store the username typed on the LoginView
@Published var username = ""
func login() {
// simulating successful API call
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
// when we log in, animate the result of setting loggedIn to true
// (i.e., animate showing MainView)
withAnimation(.default) {
self.loggedIn = true
}
}
}
}
Представление верхнего уровня (это удовлетворяет #2)
struct ContentView: View {
@ObservedObject var model = LoginStateModel()
var body: some View {
ZStack {
// just here for background
Color(UIColor.cyan).opacity(0.3)
.edgesIgnoringSafeArea(.all)
// we show either LoginView or MainView depending on our model
if model.loggedIn {
MainView()
} else {
LoginView()
}
}
// this passes the model down to descendant views
.environmentObject(model)
}
}
Переход по умолчанию для добавления и удаления представлений из иерархии представлений заключается в изменении их непрозрачности. Так как мы изменили наши изменения на model.loggedIn
в withAnimation(.default)
, это изменение непрозрачности будет происходить медленно (лучше на реальном устройстве, чем на сжатых GIF ниже).
В качестве альтернативы, вместо того, чтобы представления исчезали в /Мы могли бы заставить их перемещаться по экрану с помощью смещения. Для второго примера замените блок if
/ else
выше (включая сам if
) на
MainView()
.offset(x: model.loggedIn ? 0 : UIScreen.main.bounds.width, y: 0)
LoginView()
.offset(x: model.loggedIn ? -UIScreen.main.bounds.width : 0, y: 0)
Вид входа
struct LoginView: View {
@EnvironmentObject var model: LoginStateModel
@State private var usernameString = ""
@State private var passwordString = ""
var body: some View {
VStack(spacing: 15) {
HStack {
Text("Username")
Spacer()
TextField("Username", text: $usernameString)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
HStack {
Text("Password")
Spacer()
SecureField("Password", text: $passwordString)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
Button(action: {
// save the entered username, and try to log in
self.model.username = self.usernameString
self.model.login()
}, label: {
Text("Login")
.font(.title)
.inExpandingRectangle(Color.blue.opacity(0.6))
})
.buttonStyle(PlainButtonStyle())
}
.padding()
.inExpandingRectangle(Color.gray)
.frame(width: 300, height: 200)
}
}
Обратите внимание, что в реальной функциональной форме входа в систему вам нужно выполнить базовую очистку ввода и отключить / ограничить скорость кнопкой входа, чтобы не получать миллиард запросов к серверу, если кто-то спамит кнопку.
Для вдохновения см .:
Представление комбайна (сессия WWDC)
Практическое объединение (сессия WWDC)
Использование комбайна (UIKitпример, но показывает, как регулировать сетевые запросы)
Основной вид
struct MainView: View {
@EnvironmentObject var model: LoginStateModel
var body: some View {
VStack(spacing: 15) {
ZStack {
Text("Hello \(model.username)!")
.font(.title)
.inExpandingRectangle(Color.blue.opacity(0.6))
.frame(height: 60)
HStack {
Spacer()
Button(action: {
// when we log out, animate the result of setting loggedIn to false
// (i.e., animate showing LoginView)
withAnimation(.default) {
self.model.loggedIn = false
}
}, label: {
Text("Logout")
.inFittedRectangle(Color.green.opacity(0.6))
})
.buttonStyle(PlainButtonStyle())
.padding()
}
}
Text("Content")
.inExpandingRectangle(.gray)
}
.padding()
}
}
Некоторые дополнительные расширения
extension View {
func inExpandingRectangle(_ color: Color) -> some View {
ZStack {
RoundedRectangle(cornerRadius: 15)
.fill(color)
self
}
}
func inFittedRectangle(_ color: Color) -> some View {
self
.padding(5)
.background(RoundedRectangle(cornerRadius: 15)
.fill(color))
}
}