Skip to content

Instantly share code, notes, and snippets.

@pd95
Created July 9, 2022 11:18
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pd95/4193df6e54a754bc84aa0c239f65c16f to your computer and use it in GitHub Desktop.
Save pd95/4193df6e54a754bc84aa0c239f65c16f to your computer and use it in GitHub Desktop.
A very basic sign-in flow written in SwiftUI
import SwiftUI
@MainActor
class ViewModel: ObservableObject {
enum State: Equatable {
case anonymous
case authenticated(username: String)
}
@Published var state = State.anonymous
var isSignedIn: Bool {
state != .anonymous
}
func signIn(username: String, password: String) async {
try? await Task.sleep(nanoseconds: 1_000_000_000)
state = .authenticated(username: username)
}
func signOut() {
state = .anonymous
}
}
struct ContentView: View {
@StateObject private var model = ViewModel()
var body: some View {
NavigationView {
Group {
if model.isSignedIn {
Authenticated(model: model)
} else {
SignIn(model: model)
}
}
}
}
}
struct SignIn: View {
@ObservedObject var model: ViewModel
enum Field {
case username, password
}
@FocusState private var focusedField: Field?
@State private var username = ""
@State private var password = ""
@State private var isSignInProgress = false
var body: some View {
VStack(spacing: 20) {
TextField("Username", text: $username)
.textContentType(.username)
.focused($focusedField, equals: .username)
.submitLabel(.next)
SecureField("Password", text: $password)
.textContentType(.password)
.focused($focusedField, equals: .password)
.submitLabel(.send)
Button(action: submit) {
HStack(spacing: 10) {
Text("Sign in")
.font(.headline)
if isSignInProgress {
ProgressView()
}
}
.frame(maxWidth: .infinity, minHeight: 30)
}
.buttonStyle(.borderedProminent)
Spacer()
}
.padding()
.navigationTitle("Sign in to ABC")
.textFieldStyle(.roundedBorder)
.autocorrectionDisabled(true)
.textInputAutocapitalization(.never)
.onSubmit(submit)
.disabled(isSignInProgress)
.task {
try? await Task.sleep(nanoseconds: 200_000_000)
focusedField = .username
}
}
private func submit() {
if username.isEmpty {
focusedField = .username
} else if password.isEmpty {
focusedField = .password
} else {
focusedField = nil
isSignInProgress = true
Task {
await model.signIn(username: username, password: password)
isSignInProgress = false
}
}
}
}
struct Authenticated: View {
@ObservedObject var model: ViewModel
var body: some View {
VStack {
Text("Welcome")
.font(.largeTitle)
.toolbar {
Button("Sign out") {
model.signOut()
}
}
Spacer()
}
}
}
@pd95
Copy link
Author

pd95 commented Jul 9, 2022

The problem with password autofill is not the UIKit/SwiftUI implementation. To get support in your app, the most important things to get right are:

  1. Add Associated Domains capability to your app and set "Domains" to webcredentials:example.com (replacing example.com with a domain you actually own and have access to!).

  2. On your website (accessible under the given domain) place a file named apple-app-site-association into your public accessible root directory in a folder .well-known. You can test the access using the following command curl https://www.example.com/.well-known/apple-app-site-association. (Yes, it must use HTTPS!)

  3. The content of the apple-app-site-association must reference your Apple Developer Team ID and the bundle identifier of your app as follows: (Team ID is "A1BC23REUG" and bundle identifier is "com.example.signin-playground")

{
  "webcredentials": {
    "apps": [ 
      "A1BC23REUG.com.example.signin-playground"
    ]
  }
}
  1. Mark the input fields in SwiftUI using the appropriate textContentType modifier.

I have written a small example in this gist which I successfully tested. It contains a dumb ViewModel, a ContentView (as top level container view) and SignIn and Authenticated views which represent the corresponding screens.
There is actually not much in the ViewModel except its state (anonymous or authenticated) and the basic signIn() and signOut() methods. The methods do not actually do something beside delaying/simulating the process and updating state.

The SignIn view has been created to test password autofill functionality. According to Apples documentation, we do not have much control over the heuristics, except specifying the TextField/SecureFields textContentType.

If autocomplete is not working (e.g. you get only an unspecific password completion dialog), you normally have to check the bundle/team identifier and the in the apple-app-site-association file. If you changed something in the file recently, it's possible that the content is cached either on your device (so you can delete the app and reinstall it again) or it is cached in Apples CDN network (in which case you can activate an alternate mode as documented here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment