版本記錄
版本號(hào) | 時(shí)間 |
---|---|
V1.0 | 2019.10.18 星期五 |
前言
今天翻閱蘋(píng)果的API文檔妈经,發(fā)現(xiàn)多了一個(gè)框架SwiftUI淮野,這里我們就一起來(lái)看一下這個(gè)框架。感興趣的看下面幾篇文章吹泡。
1. SwiftUI框架詳細(xì)解析 (一) —— 基本概覽(一)
2. SwiftUI框架詳細(xì)解析 (二) —— 基于SwiftUI的閃屏頁(yè)的創(chuàng)建(一)
3. SwiftUI框架詳細(xì)解析 (三) —— 基于SwiftUI的閃屏頁(yè)的創(chuàng)建(二)
4. SwiftUI框架詳細(xì)解析 (四) —— 使用SwiftUI進(jìn)行蘋(píng)果登錄(一)
開(kāi)始
首先看下工程組織結(jié)構(gòu)
接著就是源碼了
1. WebApi.swift
import Foundation
struct WebApi {
static func Register(user: UserData, identityToken: Data?, authorizationCode: Data?) throws -> Bool {
return true
}
}
2. ContentView.swift
import UIKit
import SwiftUI
import AuthenticationServices
struct ContentView: View {
@Environment(\.window) var window: UIWindow?
@State var appleSignInDelegates: SignInWithAppleDelegates! = nil
var body: some View {
ZStack {
Color.green.edgesIgnoringSafeArea(.all)
VStack {
Image("razeware")
UserAndPassword()
.padding()
SignInWithApple()
.frame(width: 280, height: 60)
.onTapGesture(perform: showAppleLogin)
}
}
.onAppear {
self.performExistingAccountSetupFlows()
}
}
private func showAppleLogin() {
let request = ASAuthorizationAppleIDProvider().createRequest()
request.requestedScopes = [.fullName, .email]
performSignIn(using: [request])
}
/// Prompts the user if an existing iCloud Keychain credential or Apple ID credential is found.
private func performExistingAccountSetupFlows() {
#if !targetEnvironment(simulator)
// Note that this won't do anything in the simulator. You need to
// be on a real device or you'll just get a failure from the call.
let requests = [
ASAuthorizationAppleIDProvider().createRequest(),
ASAuthorizationPasswordProvider().createRequest()
]
performSignIn(using: requests)
#endif
}
private func performSignIn(using requests: [ASAuthorizationRequest]) {
appleSignInDelegates = SignInWithAppleDelegates(window: window) { success in
if success {
// update UI
} else {
// show the user an error
}
}
let controller = ASAuthorizationController(authorizationRequests: requests)
controller.delegate = appleSignInDelegates
controller.presentationContextProvider = appleSignInDelegates
controller.performRequests()
}
}
#if DEBUG
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
#endif
3. Keychain.swift
import Foundation
enum KeychainError: Error {
case secCallFailed(OSStatus)
case notFound
case badData
case archiveFailure(Error)
}
protocol Keychain {
associatedtype DataType: Codable
var account: String { get set }
var service: String { get set }
func remove() throws
func retrieve() throws -> DataType
func store(_ data: DataType) throws
}
extension Keychain {
func remove() throws {
let status = SecItemDelete(keychainQuery() as CFDictionary)
guard status == noErr || status == errSecItemNotFound else {
throw KeychainError.secCallFailed(status)
}
}
func retrieve() throws -> DataType {
var query = keychainQuery()
query[kSecMatchLimit as String] = kSecMatchLimitOne
query[kSecReturnAttributes as String] = kCFBooleanTrue
query[kSecReturnData as String] = kCFBooleanTrue
var result: AnyObject?
let status = withUnsafeMutablePointer(to: &result) {
SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))
}
guard status != errSecItemNotFound else { throw KeychainError.notFound }
guard status == noErr else { throw KeychainError.secCallFailed(status) }
do {
guard
let dict = result as? [String: AnyObject],
let data = dict[kSecAttrGeneric as String] as? Data,
let userData = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? DataType
else {
throw KeychainError.badData
}
return userData
} catch {
throw KeychainError.archiveFailure(error)
}
}
func store(_ data: DataType) throws {
var query = keychainQuery()
let archived: AnyObject
do {
archived = try NSKeyedArchiver.archivedData(withRootObject: data, requiringSecureCoding: true) as AnyObject
} catch {
throw KeychainError.archiveFailure(error)
}
let status: OSStatus
do {
// If doesn't already exist, this will throw a KeychainError.notFound,
// causing the catch block to add it.
_ = try retrieve()
let updates = [
String(kSecAttrGeneric): archived
]
status = SecItemUpdate(query as CFDictionary, updates as CFDictionary)
} catch KeychainError.notFound {
query[kSecAttrGeneric as String] = archived
status = SecItemAdd(query as CFDictionary, nil)
}
guard status == noErr else {
throw KeychainError.secCallFailed(status)
}
}
private func keychainQuery() -> [String: AnyObject] {
var query: [String: AnyObject] = [:]
query[kSecClass as String] = kSecClassGenericPassword
query[kSecAttrService as String] = service as AnyObject
query[kSecAttrAccount as String] = account as AnyObject
return query
}
}
4. SharedWebCredential.swift
import Foundation
enum SharedWebCredentialError: Error {
case SecRequestFailure(CFError)
case MissingCredentials
case ConversionFailure
}
struct SharedWebCredential {
private let domain: CFString
private let safeForTutorialCode: Bool
init(domain: String) {
self.domain = domain as CFString
safeForTutorialCode = !domain.isEmpty
}
func retrieve(account: String? = nil, completion: @escaping (Result<(account: String?, password: String?), SharedWebCredentialError>) -> Void) {
guard safeForTutorialCode else {
print("Please set your domain for SharedWebCredential constructor in UserAndPassword.swift!")
return
}
var acct: CFString? = nil
if let account = account {
acct = account as CFString
}
SecRequestSharedWebCredential(domain, acct) { credentials, error in
if let error = error {
DispatchQueue.main.async {
completion(.failure(.SecRequestFailure(error)))
}
return
}
guard
let credentials = credentials,
CFArrayGetCount(credentials) > 0
else {
DispatchQueue.main.async {
completion(.failure(.MissingCredentials))
}
return
}
let unsafeCredential = CFArrayGetValueAtIndex(credentials, 0)
let credential: CFDictionary = unsafeBitCast(unsafeCredential, to: CFDictionary.self)
guard let dict = credential as? Dictionary<String, String> else {
DispatchQueue.main.async {
completion(.failure(.ConversionFailure))
}
return
}
let username = dict[kSecAttrAccount as String]
let password = dict[kSecSharedPassword as String]
DispatchQueue.main.async {
completion(.success((username, password)))
}
}
}
private func update(account: String, password: String?, completion: @escaping (Result<Bool, SharedWebCredentialError>) -> Void) {
guard safeForTutorialCode else {
print("Please set your domain for SharedWebCredential constructor in UserAndPassword.swift!")
return
}
var pwd: CFString? = nil
if let password = password {
pwd = password as CFString
}
SecAddSharedWebCredential(domain, account as CFString, pwd) { error in
DispatchQueue.main.async {
if let error = error {
completion(.failure(.SecRequestFailure(error)))
} else {
completion(.success(true))
}
}
}
}
func store(account: String, password: String, completion: @escaping (Result<Bool, SharedWebCredentialError>) -> Void) {
update(account: account, password: password, completion: completion)
}
func delete(account: String, completion: @escaping (Result<Bool, SharedWebCredentialError>) -> Void) {
update(account: account, password: nil, completion: completion)
}
}
5. UserData.swift
import Foundation
/// Represents the details about the user which were provided during initial registration.
struct UserData: Codable {
/// The email address to use for user communications. Remember it might be a relay!
let email: String
/// The components which make up the user's name. See `displayName(style:)`
let name: PersonNameComponents
/// The team scoped identifier Apple provided to represent this user.
let identifier: String
/// Returns the localized name for the person
/// - Parameter style: The `PersonNameComponentsFormatter.Style` to use for the display.
func displayName(style: PersonNameComponentsFormatter.Style = .default) -> String {
PersonNameComponentsFormatter.localizedString(from: name, style: style)
}
}
6. UserDataKeychain.swift
import Foundation
struct UserDataKeychain: Keychain {
// Make sure the account name doesn't match the bundle identifier!
var account = "com.raywenderlich.SignInWithApple.Details"
var service = "userIdentifier"
typealias DataType = UserData
}
7. EnvironmentWindowKey.swift
import UIKit
import SwiftUI
struct WindowKey: EnvironmentKey {
struct Value {
weak var value: UIWindow?
}
static let defaultValue: Value = .init(value: nil)
}
extension EnvironmentValues {
var window: UIWindow? {
get { return self[WindowKey.self].value }
set { self[WindowKey.self] = .init(value: newValue) }
}
}
8. SceneDelegate.swift
import UIKit
import SwiftUI
class SceneDelegate: UIResponder {
var window: UIWindow?
}
extension SceneDelegate: UIWindowSceneDelegate {
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = scene as? UIWindowScene else { return }
let window = UIWindow(windowScene: windowScene)
let rootView = ContentView().environment(\.window, window)
window.rootViewController = UIHostingController(rootView: rootView)
self.window = window
window.makeKeyAndVisible()
}
}
9. SignInWithApple.swift
import SwiftUI
import AuthenticationServices
final class SignInWithApple: UIViewRepresentable {
func makeUIView(context: Context) -> ASAuthorizationAppleIDButton {
return ASAuthorizationAppleIDButton()
}
func updateUIView(_ uiView: ASAuthorizationAppleIDButton, context: Context) {
}
}
10. SignInWithAppleDelegates.swift
import UIKit
import AuthenticationServices
import Contacts
class SignInWithAppleDelegates: NSObject {
private let signInSucceeded: (Bool) -> Void
private weak var window: UIWindow!
init(window: UIWindow?, onSignedIn: @escaping (Bool) -> Void) {
self.window = window
self.signInSucceeded = onSignedIn
}
}
extension SignInWithAppleDelegates: ASAuthorizationControllerDelegate {
private func registerNewAccount(credential: ASAuthorizationAppleIDCredential) {
// 1
let userData = UserData(email: credential.email!,
name: credential.fullName!,
identifier: credential.user)
// 2
let keychain = UserDataKeychain()
do {
try keychain.store(userData)
} catch {
self.signInSucceeded(false)
}
// 3
do {
let success = try WebApi.Register(user: userData,
identityToken: credential.identityToken,
authorizationCode: credential.authorizationCode)
self.signInSucceeded(success)
} catch {
self.signInSucceeded(false)
}
}
private func signInWithExistingAccount(credential: ASAuthorizationAppleIDCredential) {
// You *should* have a fully registered account here. If you get back an error from your server
// that the account doesn't exist, you can look in the keychain for the credentials and rerun setup
// if (WebAPI.Login(credential.user, credential.identityToken, credential.authorizationCode)) {
// ...
// }
self.signInSucceeded(true)
}
private func signInWithUserAndPassword(credential: ASPasswordCredential) {
// You *should* have a fully registered account here. If you get back an error from your server
// that the account doesn't exist, you can look in the keychain for the credentials and rerun setup
// if (WebAPI.Login(credential.user, credential.password)) {
// ...
// }
self.signInSucceeded(true)
}
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
switch authorization.credential {
case let appleIdCredential as ASAuthorizationAppleIDCredential:
if let _ = appleIdCredential.email, let _ = appleIdCredential.fullName {
registerNewAccount(credential: appleIdCredential)
} else {
signInWithExistingAccount(credential: appleIdCredential)
}
break
case let passwordCredential as ASPasswordCredential:
signInWithUserAndPassword(credential: passwordCredential)
break
default:
break
}
}
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
// Handle error.
}
}
extension SignInWithAppleDelegates: ASAuthorizationControllerPresentationContextProviding {
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
return self.window
}
}
11. UserAndPassword.swift
import SwiftUI
struct UserAndPassword: View {
@State var username: String = ""
@State var password: String = ""
@State var showingAlert = false
@State var alertText: String = ""
var body: some View {
VStack {
TextField("Username", text: $username)
.textFieldStyle(RoundedBorderTextFieldStyle())
.textContentType(.username)
.autocapitalization(.none)
.disableAutocorrection(true)
SecureField("Password", text: $password)
.textFieldStyle(RoundedBorderTextFieldStyle())
.textContentType(.password)
Button(action: signInTapped) {
Text("Log In")
.foregroundColor(Color.white)
}
.alert(isPresented: $showingAlert) {
Alert(title: Text(alertText))
}
}
.padding()
}
private func signInTapped() {
let ws = CharacterSet.whitespacesAndNewlines
let account = username.trimmingCharacters(in: ws)
let pwd = password.trimmingCharacters(in: ws)
guard !(account.isEmpty || pwd.isEmpty) else {
alertText = "Please enter a username and password."
showingAlert = true
return
}
// Putting the user/pwd into the shared web credentials ensures that
// it's available for your browser based logins if you haven't implemented
// the web version of Sign in with Apple but also then makes it available
// for future logins via Sign in with Apple on your iOS devices.
SharedWebCredential(domain: "")
.store(account: account, password: password) { result in
guard case .failure = result else { return }
self.alertText = "Failed to store password."
self.showingAlert = true
}
}
}
#if DEBUG
struct UserAndPassword_Previews: PreviewProvider {
static var previews: some View {
UserAndPassword()
}
}
#endif
后記
本篇主要講述了使用SwiftUI進(jìn)行蘋(píng)果登錄骤星,感興趣的給個(gè)贊或者關(guān)注~~~