第一方支持不要求额外 SDK,处理的时候判断一下系统版本即可。
General
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| public class SystemHandler {
public let endpoints: [Endpoint] = [ Endpoints.System.activity, ]
public let platform: Platform = Platforms.system
public var isInstalled: Bool { true }
private var oauthCompletionHandler: Bus.OauthCompletionHandler?
public var logHandler: Bus.LogHandler = { message, _, _, _ in #if DEBUG print(message) #endif }
private var boxHelper: Any!
@available(iOS 13.0, *) private var helper: Helper { boxHelper as! Helper }
public init() { if #available(iOS 13.0, *) { boxHelper = Helper(master: self) } } }
|
使用 Helper
处理系统的回调,相关内容下文会进行说明。通过 Apple 登录
功能要求 iOS 13.0+
,所以使用 boxHelper: Any!
来持有,另外提供 helper
计算属性快速访问。
LogHandlerProxyType
1
| extension SystemHandler: LogHandlerProxyType {}
|
声明 SystemHandler
遵循 LogHandlerProxyType
协议。
ShareHandlerType
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
| extension SystemHandler: ShareHandlerType {
public func share( message: MessageType, to endpoint: Endpoint, options: [Bus.ShareOptionKey: Any] = [:], completionHandler: @escaping Bus.ShareCompletionHandler ) { guard let presentingViewController = options[ShareOptionKeys.presentingViewController] as? UIViewController ?? UIApplication.shared.keyWindow?.rootViewController else { assertionFailure() completionHandler(.failure(.unknown)) return }
var activityItems: [Any?] = []
if let message = message as? MediaMessageType { activityItems.append(message.title) activityItems.append(message.description) }
switch message { case let message as TextMessage: activityItems.append(message.text)
case let message as ImageMessage: activityItems.append(message.data)
case let message as AudioMessage: activityItems.append(message.link)
case let message as VideoMessage: activityItems.append(message.link)
case let message as WebPageMessage: activityItems.append(message.link)
case let message as FileMessage: activityItems.append(message.data)
case let message as MiniProgramMessage: activityItems.append(message.link)
default: completionHandler(.failure(.unsupportedMessage)) return }
let activityViewController = UIActivityViewController( activityItems: activityItems.compactMap { $0 }, applicationActivities: nil )
activityViewController.completionWithItemsHandler = { _, result, _, error in switch (result, error) { case (_, _?): completionHandler(.failure(.unknown)) case (true, _): completionHandler(.success(())) case (false, _): completionHandler(.failure(.userCancelled)) } }
if let popoverPresentationController = activityViewController.popoverPresentationController { guard let sourceView = options[ShareOptionKeys.sourceView] as? UIView else { assertionFailure() completionHandler(.failure(.unknown)) return }
popoverPresentationController.sourceView = sourceView
if let sourceRect = options[ShareOptionKeys.sourceRect] as? CGRect { popoverPresentationController.sourceRect = sourceRect } }
presentingViewController.present( activityViewController, animated: true ) } }
extension SystemHandler {
public enum ShareOptionKeys {
public static let presentingViewController = Bus.ShareOptionKey(rawValue: "com.nuomi1.bus.systemHandler.presentingViewController")
public static let sourceView = Bus.ShareOptionKey(rawValue: "com.nuomi1.bus.systemHandler.sourceView")
public static let sourceRect = Bus.ShareOptionKey(rawValue: "com.nuomi1.bus.systemHandler.sourceRect") } }
|
在分享流程中:
- 从
options
中获取 presentingViewController
,没有则使用 keyWindow.rootViewController
,都获取不到就提前退出。
- 创建
activityItems
待分享内容,如果 message
遵循 MediaMessageType
协议,添加 title
/ description
。
- 判断
message
的具体类型,添加消息的主要内容。
- 创建
activityViewController
,调用 activityItems.compactMap { $0 }
过滤空数据,设置 completionWithItemsHandler
分享回调。
- 当
popoverPresentationController
存在时,设置 sourceView
/ sourceRect
。
- 调用
presentingViewController.present(_:animated:completion:
拉起系统分享。
OauthHandlerType
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| extension SystemHandler: OauthHandlerType {
public func oauth( options: [Bus.OauthOptionKey: Any] = [:], completionHandler: @escaping Bus.OauthCompletionHandler ) { guard #available(iOS 13.0, *) else { completionHandler(.failure(.unknown)) return }
oauthCompletionHandler = completionHandler
let provider = ASAuthorizationAppleIDProvider()
let request = provider.createRequest() request.requestedScopes = [.email, .fullName]
let controller = ASAuthorizationController(authorizationRequests: [request]) controller.delegate = helper
controller.performRequests() } }
|
在登录流程中:
- 调用
#available(iOS 13.0, *)
判断是否可以使用 通过 Apple 登录
功能,不可以则提前退出。
- 创建
ASAuthorizationAppleIDProvider
提供者,调用 createRequest()
创建请求,设置 requestedScopes
为 [.email, .fullName]
。
- 创建
ASAuthorizationController
控制器,设置 delegate
为 helper
。
- 调用
controller.performRequests()
拉起 Apple 登录。
Helper
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| extension SystemHandler {
@available(iOS 13.0, *) fileprivate class Helper: NSObject, ASAuthorizationControllerDelegate {
weak var master: SystemHandler?
required init(master: SystemHandler) { self.master = master }
func authorizationController( controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization ) { switch authorization.credential { case let credential as ASAuthorizationAppleIDCredential: let identityToken = credential.identityToken.flatMap { String(data: $0, encoding: .utf8) }
let authorizationCode = credential.authorizationCode.flatMap { String(data: $0, encoding: .utf8) }
let parameters = [ OauthInfoKeys.identityToken: identityToken, OauthInfoKeys.authorizationCode: authorizationCode, OauthInfoKeys.user: credential.user, OauthInfoKeys.email: credential.email, OauthInfoKeys.givenName: credential.fullName?.givenName, OauthInfoKeys.familyName: credential.fullName?.familyName, ] .compactMapContent()
if !parameters.isEmpty { master?.oauthCompletionHandler?(.success(parameters)) } else { master?.oauthCompletionHandler?(.failure(.unknown)) } default: assertionFailure("\(authorization.credential)") } }
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) { switch error { case ASAuthorizationError.canceled: master?.oauthCompletionHandler?(.failure(.userCancelled)) default: master?.oauthCompletionHandler?(.failure(.unknown)) } } } }
extension SystemHandler {
public enum OauthInfoKeys {
public static let identityToken = Bus.OauthInfoKey(rawValue: "com.nuomi1.bus.systemHandler.identityToken")
public static let authorizationCode = Bus.OauthInfoKey(rawValue: "com.nuomi1.bus.systemHandler.authorizationCode")
public static let user = Bus.OauthInfoKey(rawValue: "com.nuomi1.bus.systemHandler.user")
public static let email = Bus.OauthInfoKey(rawValue: "com.nuomi1.bus.systemHandler.email")
public static let givenName = Bus.OauthInfoKey(rawValue: "com.nuomi1.bus.systemHandler.givenName")
public static let familyName = Bus.OauthInfoKey(rawValue: "com.nuomi1.bus.systemHandler.familyName") } }
|
创建 Helper
处理系统的回调:
SystemHandler
强持有 Helper
,所以 Helper
的 master
声明为 weak
避免循环引用。
- 处理登录回调时,成功则
authorizationController(controller:didCompleteWithAuthorization:)
被调用,返回 ASAuthorizationAppleIDCredential
,identityToken
/ authorizationCode
是 Data?
类型,需要转为 String?
,再获取 user
/ email
/ givenName
/ familyName
等信息,调用 master?.oauthCompletionHandler?(.success(parameters))
完成登录回调。失败则 authorizationController(controller:didCompleteWithError:)
被调用,调用 master?.oauthCompletionHandler?(.failure(Error))
完成登录回调。
总结
本文通过封装系统模块做出 SystemHandler
,实现系统的登录和分享功能。需要注意在 iPad
环境下需要设置 popoverPresentationController
相关属性,否则无法进行分享。同时一定要尽可能获取 ASAuthorizationAppleIDCredential
的全部数据,例如用户提供的邮箱和姓名,后续与 Apple 服务器校验时,Apple 不会返回用户数据。