Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions sample-native-app.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "sample-native-app/sample-native-app.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = VMBY3VYW4U;
Expand Down Expand Up @@ -284,6 +285,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "sample-native-app/sample-native-app.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = VMBY3VYW4U;
Expand Down
103 changes: 97 additions & 6 deletions sample-native-app/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,108 @@
import SwiftUI

struct ContentView: View {
@State private var signedInUser: AppleIDCredential?
@State private var errorMessage: String?

var body: some View {
Group {
if let user = signedInUser {
SignedInView(user: user) {
signedInUser = nil
}
} else {
LoginView(
onSignIn: { credential in
errorMessage = nil
signedInUser = credential
},
onFailure: { error in
let message = error.localizedDescription
errorMessage = message
Task { @MainActor in
try? await Task.sleep(for: .seconds(4))
if errorMessage == message {
errorMessage = nil
}
}
},
onRequest: {
errorMessage = nil
}
)
.overlay(alignment: .top) {
if let errorMessage {
Text(errorMessage)
.font(.footnote)
.foregroundStyle(.white)
.padding(.horizontal, 16)
.padding(.vertical, 8)
.background(.red.opacity(0.85), in: Capsule())
.padding(.top, 24)
.accessibilityIdentifier("loginErrorBanner")
}
}
}
}
.animation(.easeInOut, value: signedInUser)
}
}

struct SignedInView: View {
let user: AppleIDCredential
var onSignOut: () -> Void

var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
VStack(spacing: 24) {
Image(systemName: "checkmark.seal.fill")
.resizable()
.scaledToFit()
.frame(width: 72, height: 72)
.foregroundStyle(.green)

Text("Signed in")
.font(.title.weight(.semibold))

VStack(spacing: 8) {
Text(user.displayName)
.font(.headline)
if let email = user.email, !email.isEmpty {
Text(email)
.font(.subheadline)
.foregroundStyle(.secondary)
}
Text("User ID: \(user.userIdentifier)")
.font(.caption)
.foregroundStyle(.secondary)
.lineLimit(1)
.truncationMode(.middle)
.padding(.horizontal, 24)
}

Button("Sign out", action: onSignOut)
.buttonStyle(.borderedProminent)
.accessibilityIdentifier("signOutButton")
}
.padding()
}
}

#Preview {
#Preview("Login") {
ContentView()
}

#Preview("Signed in") {
SignedInView(
user: AppleIDCredential(
userIdentifier: "001234.abcdef.5678",
email: "jane@example.com",
fullName: {
var components = PersonNameComponents()
components.givenName = "Jane"
components.familyName = "Appleseed"
return components
}()
),
onSignOut: {}
)
}
127 changes: 127 additions & 0 deletions sample-native-app/LoginView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
//
// LoginView.swift
// sample-native-app
//

import AuthenticationServices
import SwiftUI

struct LoginView: View {
@Environment(\.colorScheme) private var colorScheme

var onSignIn: (AppleIDCredential) -> Void
var onFailure: (Error) -> Void
var onRequest: () -> Void = {}

var body: some View {
ZStack {
LinearGradient(
colors: [
Color(red: 0.06, green: 0.07, blue: 0.12),
Color(red: 0.12, green: 0.13, blue: 0.22),
],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
.ignoresSafeArea()

VStack(spacing: 32) {
Spacer()

VStack(spacing: 16) {
Image(systemName: "applelogo")
.resizable()
.scaledToFit()
.frame(width: 64, height: 64)
.foregroundStyle(.white)
.accessibilityHidden(true)

Text("Welcome")
.font(.largeTitle.weight(.bold))
.foregroundStyle(.white)

Text("Sign in to continue to Sample App")
.font(.body)
.multilineTextAlignment(.center)
.foregroundStyle(.white.opacity(0.75))
.padding(.horizontal, 32)
}

Spacer()

VStack(spacing: 16) {
SignInWithAppleButton(
.signIn,
onRequest: { request in
request.requestedScopes = [.fullName, .email]
onRequest()
},
onCompletion: { result in
switch result {
case .success(let authorization):
if let credential = authorization.credential as? ASAuthorizationAppleIDCredential {
onSignIn(AppleIDCredential(credential: credential))
}
case .failure(let error):
if let authError = error as? ASAuthorizationError {
switch authError.code {
case .canceled, .unknown:
return
default:
break
}
}
onFailure(error)
}
}
)
.signInWithAppleButtonStyle(colorScheme == .dark ? .white : .black)
.frame(height: 52)
.clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous))
.accessibilityIdentifier("signInWithAppleButton")

Text("By continuing you agree to our Terms and Privacy Policy.")
.font(.footnote)
.multilineTextAlignment(.center)
.foregroundStyle(.white.opacity(0.6))
.padding(.horizontal, 24)
}
.padding(.horizontal, 24)
.padding(.bottom, 24)
}
.safeAreaPadding(.bottom, 24)
}
}
}

struct AppleIDCredential: Equatable {
let userIdentifier: String
let email: String?
let fullName: PersonNameComponents?

init(credential: ASAuthorizationAppleIDCredential) {
self.userIdentifier = credential.user
self.email = credential.email
self.fullName = credential.fullName
}

init(userIdentifier: String, email: String?, fullName: PersonNameComponents?) {
self.userIdentifier = userIdentifier
self.email = email
self.fullName = fullName
}

var displayName: String {
if let fullName, let formatted = PersonNameComponentsFormatter().string(for: fullName), !formatted.isEmpty {
return formatted
}
if let email, !email.isEmpty {
return email
}
return "Apple User"
}
}

#Preview {
LoginView(onSignIn: { _ in }, onFailure: { _ in })
}
10 changes: 10 additions & 0 deletions sample-native-app/sample-native-app.entitlements
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.applesignin</key>
<array>
<string>Default</string>
</array>
</dict>
</plist>
Loading