Sign in with Apple Using SwiftUI

Learn how to implement Sign in with Apple using SwiftUI, to give users more privacy and control in your iOS apps. By Scott Grosch.

4.4 (27) · 1 Review

Download materials
Save for later
Share
You are currently viewing page 3 of 3 of this article. Click here to view the first page.

The Environment

A new concept with SwiftUI is the Environment. It’s an area wherein you can store data that needs to be available to many of your SwiftUI views. To some degree, you can think of it like dependency injection.

Take a look at EnvironmentWindowKey.swift, and you’ll see the code necessary to store a value in the SwiftUI environment. It’s boilerplate code wherein you define the key to pass to the @Environment property wrapper and the value to be stored. Note how, since a class type is being stored, it explicitly marks the reference as weak to prevent a retain cycle.

Note: The environment code was provided by an Apple engineer in the WWDC labs

Jump back to ContentView.swift and add another property to the top of ContentView:

@Environment(\.window) var window: UIWindow?

iOS will automatically populate the window variable with the value stored in the environment.

In performSignIn(using:), modify the constructor call to pass the window property:

appleSignInDelegates = SignInWithAppleDelegates(window: window) { success in

You’ll also want to tell ASAuthorizationController to use your class for the presentationContextProvider delegate, so add this code right after assigning the controller.delegate:

controller.presentationContextProvider = appleSignInDelegates

Open SignInWithAppleDelegates.swift to handle the new property and constructor changes to the class. Replace the class definition, but not the extension with all the registration and delegate methods, with the following:

class SignInWithAppleDelegates: NSObject {
  private let signInSucceeded: (Bool) -> Void
  // 1
  private weak var window: UIWindow!

  // 2
  init(window: UIWindow?, onSignedIn: @escaping (Bool) -> Void) {
    // 3
    self.window = window
    self.signInSucceeded = onSignedIn
  }
}

Just a few changes:

  1. Store a new weak reference to the window.
  2. Add the UIWindow parameter as the first argument to the initializer.
  3. Store the passed-in value to the property.

Finally, implement the new delegate type:

extension SignInWithAppleDelegates: 
    ASAuthorizationControllerPresentationContextProviding {
  func presentationAnchor(for controller: ASAuthorizationController) 
      -> ASPresentationAnchor {
    return self.window
  }
}

The delegate just has a single method to implement that is expected to return the window, which shows the Sign In with Apple modal dialog.

There’s just one piece left to getting the UIWindow into the environment. Open SceneDelegate.swift and replace this line:

window.rootViewController = UIHostingController(rootView: ContentView())

With these lines:

// 1
let rootView = ContentView().environment(\.window, window)

// 2
window.rootViewController = UIHostingController(rootView: rootView)

Two small steps do it all:

  1. You create the ContentView and append the value of the window variable.
  2. You pass that rootView variable to the UIHostingController instead of the old initializer.

The environment method returns some View which basically means it’s taking your ContentView, shoving the value you pass into that view’s environment, and then returning the ContentView back to you. Any SwiftUI View which is presented from the ContentView will now also hold that value in its environment.

If you create a new root view anywhere else, that root will not contain the environment values unless you explicitly pass them back in as well.

Logins Do not Scroll

One downside to Sign In with Apple to keep in mind is that the window that iOS displays will not scroll! For most users that won’t matter, but it’s important to note. As the owner of the site that my app uses, for example, I have numerous logins. Not only do I have the app login itself, but I’ve got a login for the SQL database, for the PHP admin site, etc.

If you’ve got too many logins, it’s possible end users won’t see what they actually need. Try to ensure that if you’re linking an app to a site that the site only has logins which will matter to the app. Don’t just bundle all your apps under a single domain.

Where to Go From Here?

You can download the completed project files by clicking the Download Materials button at the top or bottom of the tutorial.

SignInWithAppleDelegates.swift currently returns a Boolean success, but you’ll likely want to use something more like Swift 5’s Result type so that you can return not only data from your server, but also custom error types on failure. Please see our video course, What’s New in Swift 5: Types if you’re not familiar with the Result type.

We hope you enjoyed this tutorial, and if you have any questions or comments, please join the forum discussion below!