Reputation: 341
I'm struggling in a big way to get some basic code working that allows me to change a view in a Swiftui project.
I have 3 views: my default ContentView, a login screen and a main menu.
The project loads to ContentView which is just a logo. I have a boolean value which defaults to false and an extension function which either loads the login screen, or main menu dependent on the value of that boolean.
This part is working fine, project loads and i see the login page. The login button calls a function which does a URLsession, and depending on the returned value from that, sets the boolean flag to true or leaves it as false in the case of a failed login.
The bit im struggling with is getting the function to change the view. I can toggle the boolean flag in the function fine, but if I include a statement such as MainMenu() to load my main menu view, nothing happens.
I have experimented with observable objects and "subscribers" to try to get this working but i'm not sure if this is actually needed and I had no joy getting it working.
any help is greatly appreciated
Full code:
import SwiftUI
var isLoggedin = false
var authenticationFailure = false
func DoLogin(username: inout String, password: inout String){
print(isLoggedin)
let url = URL(string: "https://www.example.com/mobile/ios/test.php")!
var request = URLRequest(url: url)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
let parameters: [String: Any] = [
"username": username,
"password": password]
request.httpBody = parameters.percentEncoded()
let task = URLSession.shared.dataTask(with: request) { data,response,error in
guard let data = data,
let response = response as? HTTPURLResponse,
error == nil else{
print("error", error ?? "Unknown error")
return
}
guard (200 ... 299) ~= response.statusCode else {
print("statuscode should be 2xx, got \(response.statusCode)")
print("response = \(response)")
return
}
let responseString = String(data: data, encoding: .utf8)
if responseString == "1"{
print("Logged in")
isLoggedin = true
print(isLoggedin)
MainMenu()
}
else{
print("NO LOGIN")
isLoggedin = false
}
}
task.resume()
}
extension View {
@ViewBuilder func changeView(_ isLoggedin: Bool) -> some View {
switch isLoggedin {
case false: LoginView()
case true: MainMenu()
}
}
}
struct ContentView: View {
@State var isLoggedin = false
var body: some View {
Color.clear
.changeView(isLoggedin)
VStack{
Image("logo")
.padding(.bottom, 40)
}
}
}
struct LoginView: View {
@State var username: String = ""
@State var password: String = ""
@State var isLoggedin = false
var body: some View {
VStack{
Form{
TextField("Username: ", text:$username)
.frame(maxWidth: .infinity, alignment: .center)
.autocapitalization(.none)
SecureField("Password: ",text:$password)
Button("Login"){
DoLogin(username: &username, password: &password)
}
}
.padding(.top, 100)
}
}
}
struct MainMenu: View{
@State var isLoggedin = true
var body: some View{
VStack{
Text("Main Menu")
}
}
}
/*struct ContentView_Previews: PreviewProvider {
static var previews: some View {
/*ContentView() */
}
}*/
Upvotes: 1
Views: 1256
Reputation: 150565
You have some problems with your code.
In your Content view
@State var isLoggedin = false
isn't being changed by anything inside the body of the struct, so it is always going to be false.
Your LoginView calls doLogin
but it doesn't change any variables that the views use to render themselves. In the body of your doLogin
method it is returning views, but it isn't returning them to anything.
Here is an example that does sort of what you want. shows different screens depending on state. SwiftUI shows views depending on states, so you need to change states to show different views. I've done this in one file so it's easier to show here:
import SwiftUI
class ContentViewModel: ObservableObject {
enum ViewState {
case initial
case loading
case login
case menu
}
@Published var username = ""
@Published var password = ""
@Published var viewState = ViewState.initial
var loginButtonDisabled: Bool {
username.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ||
password.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
}
func goToLogin() {
viewState = .login
}
func login() {
viewState = .loading
// I'm not actually logging in, just randomly simulating either a successful or unsuccessful login after a short delay
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
if Bool.random() {
self.viewState = .menu
} else {
self.viewState = .login
}
}
}
}
struct ContentView: View {
@StateObject var viewModel = ContentViewModel()
var body: some View {
ZStack {
initialView
loginView
loadingView
menuView
}
}
private var initialView: InitialView? {
guard .initial == viewModel.viewState else { return nil }
return InitialView(viewModel: viewModel)
}
private var loginView: LoginView? {
guard .login == viewModel.viewState else { return nil }
return LoginView(viewModel: viewModel)
}
private var loadingView: LoadingView? {
guard .loading == viewModel.viewState else { return nil }
return LoadingView()
}
private var menuView: MenuView? {
guard .menu == viewModel.viewState else { return nil }
return MenuView()
}
}
struct InitialView: View {
@ObservedObject var viewModel: ContentViewModel
var body: some View {
VStack {
Text("Initial View")
.font(.largeTitle)
.padding()
Button("Login") { viewModel.goToLogin() }
}
}
}
struct LoginView: View {
@ObservedObject var viewModel: ContentViewModel
var body: some View {
VStack {
Text("Login View")
.font(.largeTitle)
.padding()
TextField("Username", text: $viewModel.username)
.padding()
TextField("Password", text: $viewModel.password)
.padding()
Button("Login") {viewModel.login() }
.padding()
.disabled(viewModel.loginButtonDisabled)
}
}
}
struct LoadingView: View {
var body: some View {
Text("Loading View")
.font(.largeTitle)
}
}
struct MenuView: View {
var body: some View {
Text("Menu View")
.font(.largeTitle)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
This is all driven off one view model which publishes an enum state that is used by ContentView
to show the different views. This is possible because in groups (such as the ZStack), a nil
view is not rendered.
You can clone a project with this from https://github.com/Abizern/SO-68407322
Upvotes: 1