-
-
Save pd95/4193df6e54a754bc84aa0c239f65c16f to your computer and use it in GitHub Desktop.
A very basic sign-in flow written in SwiftUI
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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:
Add
Associated Domains
capability to your app and set "Domains" towebcredentials:example.com
(replacingexample.com
with a domain you actually own and have access to!).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 commandcurl https://www.example.com/.well-known/apple-app-site-association
. (Yes, it must use HTTPS!)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")textContentType
modifier.I have written a small example in this gist which I successfully tested. It contains a dumb
ViewModel
, aContentView
(as top level container view) andSignIn
andAuthenticated
views which represent the corresponding screens.There is actually not much in the
ViewModel
except itsstate
(anonymous
orauthenticated
) and the basicsignIn()
andsignOut()
methods. The methods do not actually do something beside delaying/simulating the process and updatingstate
.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 theTextField
/SecureField
stextContentType
.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