apple login
This commit is contained in:
@@ -34,6 +34,8 @@
|
|||||||
04FC95DD2EB202A3007BD342 /* KBGuideVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC95DC2EB202A3007BD342 /* KBGuideVC.m */; };
|
04FC95DD2EB202A3007BD342 /* KBGuideVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC95DC2EB202A3007BD342 /* KBGuideVC.m */; };
|
||||||
04FC95E52EB220B5007BD342 /* UIColor+Extension.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC95E42EB220B5007BD342 /* UIColor+Extension.m */; };
|
04FC95E52EB220B5007BD342 /* UIColor+Extension.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC95E42EB220B5007BD342 /* UIColor+Extension.m */; };
|
||||||
04FC95E92EB23B67007BD342 /* KBNetworkManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC95E72EB23B67007BD342 /* KBNetworkManager.m */; };
|
04FC95E92EB23B67007BD342 /* KBNetworkManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC95E72EB23B67007BD342 /* KBNetworkManager.m */; };
|
||||||
|
04FC95F12EB339A7007BD342 /* LoginViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC95F02EB339A7007BD342 /* LoginViewController.m */; };
|
||||||
|
04FC95F42EB339C1007BD342 /* AppleSignInManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC95F32EB339C1007BD342 /* AppleSignInManager.m */; };
|
||||||
04FC97002EB30A00007BD342 /* KBGuideTopCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC96FF2EB30A00007BD342 /* KBGuideTopCell.m */; };
|
04FC97002EB30A00007BD342 /* KBGuideTopCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC96FF2EB30A00007BD342 /* KBGuideTopCell.m */; };
|
||||||
04FC97032EB30A00007BD342 /* KBGuideKFCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC97022EB30A00007BD342 /* KBGuideKFCell.m */; };
|
04FC97032EB30A00007BD342 /* KBGuideKFCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC97022EB30A00007BD342 /* KBGuideKFCell.m */; };
|
||||||
04FC97062EB30A00007BD342 /* KBGuideUserCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC97052EB30A00007BD342 /* KBGuideUserCell.m */; };
|
04FC97062EB30A00007BD342 /* KBGuideUserCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC97052EB30A00007BD342 /* KBGuideUserCell.m */; };
|
||||||
@@ -124,6 +126,11 @@
|
|||||||
04FC95E42EB220B5007BD342 /* UIColor+Extension.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIColor+Extension.m"; sourceTree = "<group>"; };
|
04FC95E42EB220B5007BD342 /* UIColor+Extension.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIColor+Extension.m"; sourceTree = "<group>"; };
|
||||||
04FC95E62EB23B67007BD342 /* KBNetworkManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBNetworkManager.h; sourceTree = "<group>"; };
|
04FC95E62EB23B67007BD342 /* KBNetworkManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBNetworkManager.h; sourceTree = "<group>"; };
|
||||||
04FC95E72EB23B67007BD342 /* KBNetworkManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBNetworkManager.m; sourceTree = "<group>"; };
|
04FC95E72EB23B67007BD342 /* KBNetworkManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBNetworkManager.m; sourceTree = "<group>"; };
|
||||||
|
04FC95EF2EB339A7007BD342 /* LoginViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LoginViewController.h; sourceTree = "<group>"; };
|
||||||
|
04FC95F02EB339A7007BD342 /* LoginViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LoginViewController.m; sourceTree = "<group>"; };
|
||||||
|
04FC95F22EB339C1007BD342 /* AppleSignInManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppleSignInManager.h; sourceTree = "<group>"; };
|
||||||
|
04FC95F32EB339C1007BD342 /* AppleSignInManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppleSignInManager.m; sourceTree = "<group>"; };
|
||||||
|
04FC95F52EB33B52007BD342 /* keyBoard.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = keyBoard.entitlements; sourceTree = "<group>"; };
|
||||||
04FC96FE2EB30A00007BD342 /* KBGuideTopCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBGuideTopCell.h; sourceTree = "<group>"; };
|
04FC96FE2EB30A00007BD342 /* KBGuideTopCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBGuideTopCell.h; sourceTree = "<group>"; };
|
||||||
04FC96FF2EB30A00007BD342 /* KBGuideTopCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBGuideTopCell.m; sourceTree = "<group>"; };
|
04FC96FF2EB30A00007BD342 /* KBGuideTopCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBGuideTopCell.m; sourceTree = "<group>"; };
|
||||||
04FC97012EB30A00007BD342 /* KBGuideKFCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBGuideKFCell.h; sourceTree = "<group>"; };
|
04FC97012EB30A00007BD342 /* KBGuideKFCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBGuideKFCell.h; sourceTree = "<group>"; };
|
||||||
@@ -172,6 +179,7 @@
|
|||||||
04C6EAB92EAF86530089C901 /* keyBoard */ = {
|
04C6EAB92EAF86530089C901 /* keyBoard */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
04FC95F52EB33B52007BD342 /* keyBoard.entitlements */,
|
||||||
04FC95BF2EB1E3B1007BD342 /* Class */,
|
04FC95BF2EB1E3B1007BD342 /* Class */,
|
||||||
04C6EAE32EAF942E0089C901 /* VC */,
|
04C6EAE32EAF942E0089C901 /* VC */,
|
||||||
04C6EAAC2EAF86530089C901 /* AppDelegate.h */,
|
04C6EAAC2EAF86530089C901 /* AppDelegate.h */,
|
||||||
@@ -347,6 +355,7 @@
|
|||||||
04FC95BF2EB1E3B1007BD342 /* Class */ = {
|
04FC95BF2EB1E3B1007BD342 /* Class */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
04FC95EE2EB3399D007BD342 /* Manager */,
|
||||||
04FC95ED2EB33611007BD342 /* Login */,
|
04FC95ED2EB33611007BD342 /* Login */,
|
||||||
04FC95E82EB23B67007BD342 /* Network */,
|
04FC95E82EB23B67007BD342 /* Network */,
|
||||||
04FC95E22EB2208F007BD342 /* Categories */,
|
04FC95E22EB2208F007BD342 /* Categories */,
|
||||||
@@ -476,6 +485,8 @@
|
|||||||
04FC95EC2EB33611007BD342 /* VC */ = {
|
04FC95EC2EB33611007BD342 /* VC */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
04FC95EF2EB339A7007BD342 /* LoginViewController.h */,
|
||||||
|
04FC95F02EB339A7007BD342 /* LoginViewController.m */,
|
||||||
);
|
);
|
||||||
path = VC;
|
path = VC;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -490,6 +501,15 @@
|
|||||||
path = Login;
|
path = Login;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
04FC95EE2EB3399D007BD342 /* Manager */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
04FC95F22EB339C1007BD342 /* AppleSignInManager.h */,
|
||||||
|
04FC95F32EB339C1007BD342 /* AppleSignInManager.m */,
|
||||||
|
);
|
||||||
|
path = Manager;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
2C53A0856097DCFBE7B55649 /* Pods */ = {
|
2C53A0856097DCFBE7B55649 /* Pods */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -735,6 +755,7 @@
|
|||||||
04FC95D22EB1E7AE007BD342 /* MyVC.m in Sources */,
|
04FC95D22EB1E7AE007BD342 /* MyVC.m in Sources */,
|
||||||
043FBCD22EAF97630036AFE1 /* KBPermissionViewController.m in Sources */,
|
043FBCD22EAF97630036AFE1 /* KBPermissionViewController.m in Sources */,
|
||||||
04C6EABE2EAF86530089C901 /* AppDelegate.m in Sources */,
|
04C6EABE2EAF86530089C901 /* AppDelegate.m in Sources */,
|
||||||
|
04FC95F12EB339A7007BD342 /* LoginViewController.m in Sources */,
|
||||||
04FC95D72EB1EA16007BD342 /* BaseTableView.m in Sources */,
|
04FC95D72EB1EA16007BD342 /* BaseTableView.m in Sources */,
|
||||||
04FC95D82EB1EA16007BD342 /* BaseCell.m in Sources */,
|
04FC95D82EB1EA16007BD342 /* BaseCell.m in Sources */,
|
||||||
04FC95C92EB1E4C9007BD342 /* BaseNavigationController.m in Sources */,
|
04FC95C92EB1E4C9007BD342 /* BaseNavigationController.m in Sources */,
|
||||||
@@ -749,6 +770,7 @@
|
|||||||
04FC95CF2EB1E7A1007BD342 /* HomeVC.m in Sources */,
|
04FC95CF2EB1E7A1007BD342 /* HomeVC.m in Sources */,
|
||||||
04C6EABF2EAF86530089C901 /* main.m in Sources */,
|
04C6EABF2EAF86530089C901 /* main.m in Sources */,
|
||||||
04FC95CC2EB1E780007BD342 /* BaseTabBarController.m in Sources */,
|
04FC95CC2EB1E780007BD342 /* BaseTabBarController.m in Sources */,
|
||||||
|
04FC95F42EB339C1007BD342 /* AppleSignInManager.m in Sources */,
|
||||||
04C6EAC12EAF86530089C901 /* ViewController.m in Sources */,
|
04C6EAC12EAF86530089C901 /* ViewController.m in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@@ -803,7 +825,7 @@
|
|||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.keyBoard.CustomKeyboard;
|
PRODUCT_BUNDLE_IDENTIFIER = com.keyBoardst.CustomKeyboard;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
@@ -831,7 +853,7 @@
|
|||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.keyBoard.CustomKeyboard;
|
PRODUCT_BUNDLE_IDENTIFIER = com.keyBoardst.CustomKeyboard;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SKIP_INSTALL = YES;
|
SKIP_INSTALL = YES;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
@@ -845,6 +867,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = keyBoard/keyBoard.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = TN6HHV45BB;
|
DEVELOPMENT_TEAM = TN6HHV45BB;
|
||||||
@@ -863,7 +886,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.keyBoard;
|
PRODUCT_BUNDLE_IDENTIFIER = com.keyBoardst;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
@@ -877,6 +900,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = keyBoard/keyBoard.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = TN6HHV45BB;
|
DEVELOPMENT_TEAM = TN6HHV45BB;
|
||||||
@@ -895,7 +919,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.keyBoard;
|
PRODUCT_BUNDLE_IDENTIFIER = com.keyBoardst;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
|||||||
@@ -6,11 +6,11 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#import "AppDelegate.h"
|
#import "AppDelegate.h"
|
||||||
#import "ViewController.h"
|
|
||||||
#import "KBPermissionViewController.h"
|
#import "KBPermissionViewController.h"
|
||||||
#import <AFNetworking/AFNetworking.h>
|
#import <AFNetworking/AFNetworking.h>
|
||||||
#import <Bugly/Bugly.h>
|
#import <Bugly/Bugly.h>
|
||||||
#import "BaseTabBarController.h"
|
#import "BaseTabBarController.h"
|
||||||
|
#import "LoginViewController.h"
|
||||||
|
|
||||||
static NSString * const kKBKeyboardExtensionBundleId = @"com.keyBoard.CustomKeyboard";
|
static NSString * const kKBKeyboardExtensionBundleId = @"com.keyBoard.CustomKeyboard";
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ static NSString * const kKBKeyboardExtensionBundleId = @"com.keyBoard.CustomKeyb
|
|||||||
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
||||||
self.window.backgroundColor = [UIColor whiteColor];
|
self.window.backgroundColor = [UIColor whiteColor];
|
||||||
[self.window makeKeyAndVisible];
|
[self.window makeKeyAndVisible];
|
||||||
BaseTabBarController *vc = [[BaseTabBarController alloc] init];
|
LoginViewController *vc = [[LoginViewController alloc] init];
|
||||||
self.window.rootViewController = vc;
|
self.window.rootViewController = vc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
18
keyBoard/Class/Login/VC/LoginViewController.h
Normal file
18
keyBoard/Class/Login/VC/LoginViewController.h
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
// LoginViewController.h
|
||||||
|
// 仅包含“Apple 登录”的登录页
|
||||||
|
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
@interface LoginViewController : UIViewController
|
||||||
|
|
||||||
|
/// Apple 登录成功回调。包含 userIdentifier;首次授权可能包含 email/fullName。
|
||||||
|
@property (nonatomic, copy, nullable) void (^onLoginSuccess)(NSDictionary *userInfo);
|
||||||
|
|
||||||
|
/// 登录失败或取消时回调(返回错误)。
|
||||||
|
@property (nonatomic, copy, nullable) void (^onLoginFailure)(NSError *error);
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
154
keyBoard/Class/Login/VC/LoginViewController.m
Normal file
154
keyBoard/Class/Login/VC/LoginViewController.m
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
// LoginViewController.m
|
||||||
|
|
||||||
|
#import "LoginViewController.h"
|
||||||
|
#import "AppleSignInManager.h"
|
||||||
|
#import <AuthenticationServices/AuthenticationServices.h>
|
||||||
|
#import <Masonry/Masonry.h>
|
||||||
|
|
||||||
|
@interface LoginViewController ()
|
||||||
|
// 容器视图(用于居中摆放内容)
|
||||||
|
@property (nonatomic, strong) UIView *contentView;
|
||||||
|
// 标题标签(懒加载)
|
||||||
|
@property (nonatomic, strong) UILabel *titleLabel;
|
||||||
|
// Apple 登录按钮容器(懒加载)
|
||||||
|
@property (nonatomic, strong) UIView *appleContainer;
|
||||||
|
// iOS13+ 的 Apple 登录按钮(懒加载)
|
||||||
|
@property (nonatomic, strong) ASAuthorizationAppleIDButton *appleButton API_AVAILABLE(ios(13.0));
|
||||||
|
// iOS13 以下的占位按钮(懒加载)
|
||||||
|
@property (nonatomic, strong) UIButton *compatHintButton;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation LoginViewController
|
||||||
|
|
||||||
|
- (void)viewDidLoad {
|
||||||
|
[super viewDidLoad];
|
||||||
|
if (@available(iOS 13.0, *)) {
|
||||||
|
self.view.backgroundColor = [UIColor systemBackgroundColor];
|
||||||
|
} else {
|
||||||
|
self.view.backgroundColor = [UIColor whiteColor];
|
||||||
|
}
|
||||||
|
[self setupUI]; // 仅做布局,控件懒加载
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setupUI {
|
||||||
|
// 添加并约束 contentView
|
||||||
|
[self.view addSubview:self.contentView];
|
||||||
|
[self.contentView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
make.center.equalTo(self.view);
|
||||||
|
make.left.equalTo(self.view).offset(24);
|
||||||
|
make.right.equalTo(self.view).offset(-24);
|
||||||
|
}];
|
||||||
|
|
||||||
|
// 添加并约束 titleLabel
|
||||||
|
[self.contentView addSubview:self.titleLabel];
|
||||||
|
[self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
make.top.left.right.equalTo(self.contentView);
|
||||||
|
}];
|
||||||
|
|
||||||
|
// 添加并约束 appleContainer
|
||||||
|
[self.contentView addSubview:self.appleContainer];
|
||||||
|
[self.appleContainer mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
make.top.equalTo(self.titleLabel.mas_bottom).offset(24);
|
||||||
|
make.left.right.equalTo(self.contentView);
|
||||||
|
make.height.mas_equalTo(50);
|
||||||
|
make.bottom.equalTo(self.contentView);
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 懒加载:容器视图
|
||||||
|
- (UIView *)contentView {
|
||||||
|
if (!_contentView) {
|
||||||
|
_contentView = [UIView new];
|
||||||
|
}
|
||||||
|
return _contentView;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 懒加载:标题
|
||||||
|
- (UILabel *)titleLabel {
|
||||||
|
if (!_titleLabel) {
|
||||||
|
_titleLabel = [UILabel new];
|
||||||
|
_titleLabel.text = @"登录";
|
||||||
|
_titleLabel.font = [UIFont boldSystemFontOfSize:24];
|
||||||
|
_titleLabel.textAlignment = NSTextAlignmentCenter;
|
||||||
|
}
|
||||||
|
return _titleLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 懒加载:Apple 按钮容器(内部放按钮或占位提示)
|
||||||
|
- (UIView *)appleContainer {
|
||||||
|
if (!_appleContainer) {
|
||||||
|
_appleContainer = [UIView new];
|
||||||
|
if (@available(iOS 13.0, *)) {
|
||||||
|
[_appleContainer addSubview:self.appleButton];
|
||||||
|
[self.appleButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
make.edges.equalTo(_appleContainer);
|
||||||
|
}];
|
||||||
|
} else {
|
||||||
|
[_appleContainer addSubview:self.compatHintButton];
|
||||||
|
[self.compatHintButton mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||||
|
make.edges.equalTo(_appleContainer);
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _appleContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 懒加载:Apple 登录按钮(iOS13+)
|
||||||
|
- (ASAuthorizationAppleIDButton *)appleButton API_AVAILABLE(ios(13.0)) {
|
||||||
|
if (!_appleButton) {
|
||||||
|
_appleButton = [ASAuthorizationAppleIDButton new];
|
||||||
|
[_appleButton addTarget:self action:@selector(handleAppleIDButtonPress) forControlEvents:UIControlEventTouchUpInside];
|
||||||
|
}
|
||||||
|
return _appleButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 懒加载:iOS13 以下占位提示
|
||||||
|
- (UIButton *)compatHintButton {
|
||||||
|
if (!_compatHintButton) {
|
||||||
|
_compatHintButton = [UIButton buttonWithType:UIButtonTypeSystem];
|
||||||
|
[_compatHintButton setTitle:@"需要 iOS13+ 才能使用 Apple 登录" forState:UIControlStateNormal];
|
||||||
|
_compatHintButton.enabled = NO;
|
||||||
|
}
|
||||||
|
return _compatHintButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)handleAppleIDButtonPress API_AVAILABLE(ios(13.0)) {
|
||||||
|
__weak typeof(self) weakSelf = self;
|
||||||
|
[[AppleSignInManager shared] signInFromViewController:self completion:^(ASAuthorizationAppleIDCredential * _Nullable credential, NSError * _Nullable error) {
|
||||||
|
__strong typeof(weakSelf) selfStrong = weakSelf;
|
||||||
|
if (error) {
|
||||||
|
if (selfStrong.onLoginFailure) selfStrong.onLoginFailure(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!credential) return;
|
||||||
|
|
||||||
|
NSString *userID = credential.user ?: @"";
|
||||||
|
NSString *email = credential.email ?: @""; // 仅在首次授权时返回
|
||||||
|
NSPersonNameComponents *name = credential.fullName; // 同上,仅首次返回
|
||||||
|
NSString *givenName = name.givenName ?: @"";
|
||||||
|
NSString *familyName = name.familyName ?: @"";
|
||||||
|
|
||||||
|
NSMutableDictionary *info = [@{ @"userIdentifier": userID } mutableCopy];
|
||||||
|
if (email.length) info[@"email"] = email;
|
||||||
|
if (givenName.length || familyName.length) {
|
||||||
|
info[@"givenName"] = givenName;
|
||||||
|
info[@"familyName"] = familyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如需服务端校验,附带 identityToken / authorizationCode
|
||||||
|
NSData *tokenData = credential.identityToken;
|
||||||
|
if (tokenData) {
|
||||||
|
NSString *token = [[NSString alloc] initWithData:tokenData encoding:NSUTF8StringEncoding] ?: @"";
|
||||||
|
if (token.length) info[@"identityToken"] = token;
|
||||||
|
}
|
||||||
|
NSData *codeData = credential.authorizationCode;
|
||||||
|
if (codeData) {
|
||||||
|
NSString *code = [[NSString alloc] initWithData:codeData encoding:NSUTF8StringEncoding] ?: @"";
|
||||||
|
if (code.length) info[@"authorizationCode"] = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selfStrong.onLoginSuccess) selfStrong.onLoginSuccess(info);
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
37
keyBoard/Class/Manager/AppleSignInManager.h
Normal file
37
keyBoard/Class/Manager/AppleSignInManager.h
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
// AppleSignInManager.h
|
||||||
|
// 封装“用 Apple 登录”逻辑
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import <AuthenticationServices/AuthenticationServices.h>
|
||||||
|
@class UIViewController;
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
/// Apple 登录回调结果
|
||||||
|
typedef void (^KBAppleSignInCompletion)(ASAuthorizationAppleIDCredential * _Nullable credential, NSError * _Nullable error);
|
||||||
|
|
||||||
|
/// AppleSignInManager 集中管理 Apple 登录流程与代理回调
|
||||||
|
@interface AppleSignInManager : NSObject <ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding>
|
||||||
|
|
||||||
|
/// 单例便捷访问
|
||||||
|
+ (instancetype)shared;
|
||||||
|
|
||||||
|
/// 发起 Apple 登录
|
||||||
|
/// - 参数说明:
|
||||||
|
/// - presenting: 用于呈现授权界面的控制器
|
||||||
|
/// - completion: 主线程回调,返回凭证或错误
|
||||||
|
- (void)signInFromViewController:(UIViewController *)presenting completion:(KBAppleSignInCompletion)completion;
|
||||||
|
|
||||||
|
/// 根据已存储的 userIdentifier 检查凭证状态
|
||||||
|
/// 返回:ASAuthorizationAppleIDProviderCredentialAuthorized/Revoked/NotFound
|
||||||
|
- (void)checkCredentialStateWithCompletion:(void(^)(ASAuthorizationAppleIDProviderCredentialState state))completion;
|
||||||
|
|
||||||
|
/// 最近一次成功登录的 userIdentifier(已持久化)
|
||||||
|
@property (nonatomic, readonly, nullable) NSString *storedUserIdentifier;
|
||||||
|
|
||||||
|
/// 本地登出:清除已存储的 userIdentifier(不会向苹果撤销授权)
|
||||||
|
- (void)signOut;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
NS_ASSUME_NONNULL_END
|
||||||
139
keyBoard/Class/Manager/AppleSignInManager.m
Normal file
139
keyBoard/Class/Manager/AppleSignInManager.m
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
// AppleSignInManager.m
|
||||||
|
// 封装“用 Apple 登录”的实现与存储
|
||||||
|
|
||||||
|
#import "AppleSignInManager.h"
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
#import <Security/Security.h>
|
||||||
|
|
||||||
|
static NSString * const kKBAppleUserIdentifierKey = @"com.company.keyboard.apple.user"; // 钥匙串键名
|
||||||
|
|
||||||
|
@interface AppleSignInManager ()
|
||||||
|
@property (nonatomic, weak) UIViewController *presentingVC;
|
||||||
|
@property (nonatomic, copy) KBAppleSignInCompletion completion;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation AppleSignInManager
|
||||||
|
|
||||||
|
+ (instancetype)shared {
|
||||||
|
static AppleSignInManager *instance;
|
||||||
|
static dispatch_once_t onceToken;
|
||||||
|
dispatch_once(&onceToken, ^{ instance = [AppleSignInManager new]; });
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)storedUserIdentifier {
|
||||||
|
return [self.class keychainLoad:kKBAppleUserIdentifierKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)signInFromViewController:(UIViewController *)presenting completion:(KBAppleSignInCompletion)completion {
|
||||||
|
if (@available(iOS 13.0, *)) {
|
||||||
|
self.presentingVC = presenting;
|
||||||
|
self.completion = completion;
|
||||||
|
|
||||||
|
ASAuthorizationAppleIDProvider *provider = [ASAuthorizationAppleIDProvider new];
|
||||||
|
ASAuthorizationAppleIDRequest *request = provider.createRequest;
|
||||||
|
request.requestedScopes = @[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail];
|
||||||
|
|
||||||
|
ASAuthorizationController *controller = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[request]];
|
||||||
|
controller.delegate = self;
|
||||||
|
controller.presentationContextProvider = self;
|
||||||
|
[controller performRequests];
|
||||||
|
} else {
|
||||||
|
if (completion) {
|
||||||
|
NSError *err = [NSError errorWithDomain:@"AppleSignIn" code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Apple 登录需要 iOS 13 及以上版本"}];
|
||||||
|
completion(nil, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)checkCredentialStateWithCompletion:(void(^)(ASAuthorizationAppleIDProviderCredentialState state))completion {
|
||||||
|
if (!completion) return;
|
||||||
|
if (@available(iOS 13.0, *)) {
|
||||||
|
NSString *userID = self.storedUserIdentifier;
|
||||||
|
if (!userID) {
|
||||||
|
completion(ASAuthorizationAppleIDProviderCredentialNotFound);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ASAuthorizationAppleIDProvider *provider = [ASAuthorizationAppleIDProvider new];
|
||||||
|
[provider getCredentialStateForUserID:userID completion:^(ASAuthorizationAppleIDProviderCredentialState credentialState, NSError * _Nullable error) {
|
||||||
|
dispatch_async(dispatch_get_main_queue(), ^{ completion(credentialState); });
|
||||||
|
}];
|
||||||
|
} else {
|
||||||
|
completion(ASAuthorizationAppleIDProviderCredentialNotFound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - 授权回调 (ASAuthorizationControllerDelegate)
|
||||||
|
|
||||||
|
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0)) {
|
||||||
|
if (@available(iOS 13.0, *)) {
|
||||||
|
ASAuthorizationAppleIDCredential *credential = authorization.credential;
|
||||||
|
// 持久化保存 userIdentifier,便于后续校验凭证状态
|
||||||
|
NSString *userID = credential.user;
|
||||||
|
[self.class keychainSave:kKBAppleUserIdentifierKey value:userID];
|
||||||
|
if (self.completion) {
|
||||||
|
self.completion(credential, nil);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.completion = nil;
|
||||||
|
self.presentingVC = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error API_AVAILABLE(ios(13.0)) {
|
||||||
|
if (self.completion) {
|
||||||
|
self.completion(nil, error);
|
||||||
|
}
|
||||||
|
self.completion = nil;
|
||||||
|
self.presentingVC = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - 授权界面展示锚点 (ASAuthorizationControllerPresentationContextProviding)
|
||||||
|
|
||||||
|
- (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller API_AVAILABLE(ios(13.0)) {
|
||||||
|
return self.presentingVC.view.window ?: UIApplication.sharedApplication.keyWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Keychain 工具
|
||||||
|
|
||||||
|
// 本地登出:删除已存储的 userIdentifier,使 App 重新要求登录
|
||||||
|
- (void)signOut {
|
||||||
|
NSDictionary *query = @{(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
|
||||||
|
(__bridge id)kSecAttrService: kKBAppleUserIdentifierKey,
|
||||||
|
(__bridge id)kSecAttrAccount: kKBAppleUserIdentifierKey};
|
||||||
|
SecItemDelete((__bridge CFDictionaryRef)query);
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (BOOL)keychainSave:(NSString *)key value:(NSString *)value {
|
||||||
|
if (!key) return NO;
|
||||||
|
NSData *data = [value dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
|
|
||||||
|
NSDictionary *query = @{(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
|
||||||
|
(__bridge id)kSecAttrService: key,
|
||||||
|
(__bridge id)kSecAttrAccount: key};
|
||||||
|
|
||||||
|
SecItemDelete((__bridge CFDictionaryRef)query);
|
||||||
|
|
||||||
|
NSMutableDictionary *attributes = [query mutableCopy];
|
||||||
|
attributes[(__bridge id)kSecValueData] = data ?: [NSData data];
|
||||||
|
|
||||||
|
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)attributes, NULL);
|
||||||
|
return (status == errSecSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (NSString *)keychainLoad:(NSString *)key {
|
||||||
|
if (!key) return nil;
|
||||||
|
NSDictionary *query = @{(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
|
||||||
|
(__bridge id)kSecAttrService: key,
|
||||||
|
(__bridge id)kSecAttrAccount: key,
|
||||||
|
(__bridge id)kSecReturnData: @YES,
|
||||||
|
(__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne};
|
||||||
|
|
||||||
|
CFTypeRef dataRef = NULL;
|
||||||
|
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &dataRef);
|
||||||
|
if (status != errSecSuccess || !dataRef) return nil;
|
||||||
|
|
||||||
|
NSData *data = (__bridge_transfer NSData *)dataRef;
|
||||||
|
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
10
keyBoard/keyBoard.entitlements
Normal file
10
keyBoard/keyBoard.entitlements
Normal file
@@ -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>
|
||||||
Reference in New Issue
Block a user