Search code examples
iosswiftuiaws-amplify

Communicating between AppDelegate and SwiftUI View


I am attempting to implement a custom login UI with the AWS Amplify framework. I have successfully implemented the signIn() function, as follows:

public func signIn(username: String, password: String) {
        _ = Amplify.Auth.signIn(username: username, password: password) { result in
            switch result {
            case .success(_):
                print("Sign in succeeded")
                // nothing else required, the event HUB will trigger the UI refresh
            case .failure(let error):
                print("Sign in failed \(error)")
                
                // in real life present a message to the user
            }
        }
    }

The above code has been placed in my AppDelegate file. Now what I would like to be able to do is to display a message to the user in my Login view when the sign has failed, but I do not know how to pass the error from the signIn() function into the login view. Here is the relevant part of my Login view class:

  let app = UIApplication.shared.delegate as! AppDelegate
                          
                            self.phoneField?.bindUserInputToPhoneNumber()
                            self.deleteWhitespaceAndParensFromUserInput()
                            
                            app.signIn(username: self.phoneNumber, password: self.password)

Any help would be greatly appreciated.


Solution

  • The easiest way is to create some kind of model object that will hold this data for you. You can start with an ObservableObject that you will pass to your root ContentView (if you're continuining to use this from the template):

    final class Model: ObservableObject {
    
      struct AuthStatus {
        case unknown
        case loggedIn(User) // or whatever your auth object/struct etc. is
        case error(Error)
      }
    
      @Published var authStatus: AuthStatus = .unknown
    }
    

    Now you can pass this to your ContentView a couple different ways, but one of the nicest for this kind of global state, is EnvironmentObject. Since you mentioned an AppDelegate, i'm assuming you're not using the new SwiftUI only app lifecycle code, which means you're using UIHostingController. Add the following in AppDelegate.

    var window: UIWindow?
    var model: Model = .init()
    

    And then this should go in applicationDidFinishLaunching where you set up your root view controller.

    window?.rootViewController = UIHostingController(root:
       ContentView()
         .environmentObject(model)
    )
    

    Now your content view can just declare it wants this environment object:

    struct ContentView: View {
      @EnvironmentObject private var model: Model // swiftui will resolve this for you
    
      var body: some View {
        Text("Auth status: \(model.authStatus)")
      }
    }
    

    If you want to more easily differentiate errors etc, you may want to create an additional @Published var in your Model instead of combining then into an enum as I have done here.