Section 7/161 menit

7. Conditional UI dan AutoFill Integration

7. Conditional UI dan AutoFill Integration

Cara Kerja AutoFill Passkey

AutoFill passkey muncul di atas keyboard saat field login aktif. Ini adalah "passive" request — tidak ada modal sheet yang muncul. User melihat passkey di toolbar keyboard dan bisa tap untuk autentikasi.

swift
┌───────────────────────────┐
│  Username: ___________    │
│  Password: ___________    │
│                           │
│  [Log in]                 │
└───────────────────────────┘
┌───────────────────────────┐
│ 🔑 example.com Passkey  > │  ← AutoFill bar
├───────────────────────────┤
│ Q W E R T Y U I O P      │
│ ...                       │
└───────────────────────────┘

Implementasi yang Benar

swift
import AuthenticationServices

class LoginViewController: UIViewController {
    @IBOutlet weak var usernameField: UITextField!
    @IBOutlet weak var passwordField: UITextField!

    private var conditionalController: ASAuthorizationController?
    private var conditionalChallenge: Data = Data()

    override func viewDidLoad() {
        super.viewDidLoad()

        // Aktifkan AutoFill untuk text fields
        usernameField.textContentType = .username
        passwordField.textContentType = .password

        // Fetch challenge dari server dan mulai conditional request
        Task {
            await beginConditionalSignIn()
        }
    }

    private func beginConditionalSignIn() async {
        do {
            // Fetch challenge dari server (non-blocking untuk UI)
            conditionalChallenge = try await server.fetchChallenge()
            
            let provider = ASAuthorizationPlatformPublicKeyCredentialProvider(
                relyingPartyIdentifier: "example.com"
            )
            let request = provider.createCredentialAssertionRequest(
                challenge: conditionalChallenge
            )

            // Tambah password request agar saved passwords juga muncul di AutoFill
            let passwordProvider = ASAuthorizationPasswordProvider()
            let passwordRequest = passwordProvider.createRequest()

            let controller = ASAuthorizationController(authorizationRequests: [
                request,
                passwordRequest
            ])
            controller.delegate = self
            controller.presentationContextProvider = self

            // KUNCI: performAutoFillAssistedRequests, bukan performRequests
            controller.performAutoFillAssistedRequests()
            conditionalController = controller  // Retain
        } catch {
            // Gagal fetch challenge — AutoFill tidak aktif, tapi login manual masih bisa
            print("Conditional sign-in tidak tersedia: \(error)")
        }
    }

    // Saat user tap tombol Login manual (non-AutoFill)
    @IBAction func loginButtonTapped() {
        // Cancel conditional request yang sedang berjalan
        conditionalController = nil

        // Mulai modal sign-in (akan tampilkan sheet)
        Task {
            do {
                let challenge = try await server.fetchChallenge()
                try await performModalSignIn(challenge: challenge)
            } catch {
                showError(error)
            }
        }
    }

    private func performModalSignIn(challenge: Data) async throws {
        let provider = ASAuthorizationPlatformPublicKeyCredentialProvider(
            relyingPartyIdentifier: "example.com"
        )
        let request = provider.createCredentialAssertionRequest(challenge: challenge)

        let controller = ASAuthorizationController(authorizationRequests: [request])
        controller.delegate = self
        controller.presentationContextProvider = self
        controller.performRequests()  // Modal sheet
    }
}