Files
keyboard/keyBoard/Class/Login/VC/KBLoginVC.m
2025-12-03 15:19:03 +08:00

461 lines
19 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// KBLoginVC.m
// keyBoard
//
// Created by Mac on 2025/12/2.
//
#import "KBLoginVC.h"
#import <AuthenticationServices/AuthenticationServices.h>
#import "KBLoginVM.h"
#import "AppDelegate.h"
#import "KBEmailRegistVC.h"
@interface KBLoginVC () <UITextViewDelegate>
// 背景
@property (nonatomic, strong) UIImageView *bgImageView; // 整体背景图login_bg_icon
@property (nonatomic, strong) UIImageView *topRightImageView; // 顶部右侧装饰图login_jianp_icon
@property (nonatomic, strong) UIButton *backButton; // 顶部左侧返回按钮
// 底部白色容器(仅上圆角 26
@property (nonatomic, strong) UIView *contentContainerView;
// 标题
@property (nonatomic, strong) UILabel *titleLabel;
// 按钮
@property (nonatomic, strong) UIControl *appleLoginButton; // Apple 原生登录按钮iOS13 以下降级为普通按钮)
@property (nonatomic, strong) UIButton *emailLoginButton; // 邮箱登录按钮
// 协议 & 底部文案
@property (nonatomic, strong) UITextView *agreementTextView; // 底部协议富文本
@property (nonatomic, strong) UILabel *noAccountLabel; // “Don't Have An Account?”
@property (nonatomic, strong) UIButton *signUpButton; // “Sign Up”
@property (nonatomic, strong) UIButton *forgotPasswordButton; // “Forgot Password?”
@end
@implementation KBLoginVC
- (void)viewDidLoad {
[super viewDidLoad];
// 使用全屏内容,隐藏自定义导航栏
self.kb_enableCustomNavBar = NO;
self.view.backgroundColor = [UIColor whiteColor];
[self setupUI];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
// 仅白色容器的左上、右上圆角为 26
if (!CGRectIsEmpty(self.contentContainerView.bounds)) {
UIRectCorner corners = UIRectCornerTopLeft | UIRectCornerTopRight;
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:self.contentContainerView.bounds
byRoundingCorners:corners
cornerRadii:CGSizeMake(26, 26)];
CAShapeLayer *mask = [CAShapeLayer layer];
mask.frame = self.contentContainerView.bounds;
mask.path = path.CGPath;
self.contentContainerView.layer.mask = mask;
}
// 让按钮内部的图片+文字整体居中,图片与文字间距为 50按设计稿缩放
// if (self.emailLoginButton.currentImage && self.emailLoginButton.currentTitle.length > 0) {
// [self kb_centerImageAndTitleForButton:self.emailLoginButton spacing:KBFit(50)];
// }
}
#pragma mark - UI
- (void)setupUI {
// 背景图
[self.view addSubview:self.bgImageView];
[self.bgImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view);
}];
// 顶部左侧返回按钮
[self.view addSubview:self.backButton];
[self.backButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view).offset(16);
make.top.equalTo(self.view).offset(KB_StatusBarHeight() + 8);
make.width.height.mas_equalTo(32);
}];
// 顶部右侧装饰图
[self.view addSubview:self.topRightImageView];
[self.topRightImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.view).offset(KB_StatusBarHeight() + KBFit(24));
make.right.equalTo(self.view).offset(KBFit(10));
make.width.mas_equalTo(KBFit(244));
make.height.mas_equalTo(KBFit(224));
}];
// 底部白色容器
[self.view addSubview:self.contentContainerView];
[self.contentContainerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.bottom.equalTo(self.view);
// 顶部位置大致贴合设计稿,可根据实际效果微调
make.top.equalTo(self.view).offset(KBFit(272));
}];
// 标题
[self.contentContainerView addSubview:self.titleLabel];
[self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.contentContainerView).offset(27);
make.centerX.equalTo(self.contentContainerView);
}];
// Apple 登录按钮
[self.contentContainerView addSubview:self.appleLoginButton];
[self.appleLoginButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.titleLabel.mas_bottom).offset(24);
make.left.equalTo(self.contentContainerView).offset(30);
make.right.equalTo(self.contentContainerView).offset(-30);
make.height.mas_equalTo(52);
}];
// 邮箱登录按钮
[self.contentContainerView addSubview:self.emailLoginButton];
[self.emailLoginButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.appleLoginButton.mas_bottom).offset(15);
make.left.right.height.equalTo(self.appleLoginButton);
}];
// 底部协议文案(单个富文本视图,内部 terms/privacy 文案可点击)
[self.contentContainerView addSubview:self.agreementTextView];
[self.agreementTextView mas_makeConstraints:^(MASConstraintMaker *make) {
// 协议文案占满容器左右 30 的留白,配合 textAlignmentCenter 实现真正水平居中
make.left.equalTo(self.contentContainerView).offset(30);
make.right.equalTo(self.contentContainerView).offset(-30);
make.bottom.equalTo(self.contentContainerView).offset(-KB_SAFE_BOTTOM - 84);
}];
// 底部账号相关文案
UIButton *forgot = self.forgotPasswordButton;
[self.contentContainerView addSubview:forgot];
[forgot mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self.contentContainerView);
make.bottom.equalTo(self.contentContainerView).offset(-KB_SAFE_BOTTOM - 10);
}];
UIView *accountLine = [UIView new];
[self.contentContainerView addSubview:accountLine];
[accountLine mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self.contentContainerView);
make.bottom.equalTo(forgot.mas_top).offset(-2);
}];
[accountLine addSubview:self.noAccountLabel];
[accountLine addSubview:self.signUpButton];
[self.noAccountLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.left.bottom.equalTo(accountLine);
}];
[self.signUpButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.noAccountLabel.mas_right).offset(4);
make.right.equalTo(accountLine);
make.centerY.equalTo(self.noAccountLabel);
}];
}
#pragma mark - Actions
- (void)onTapAppleLogin {
KBLOG(@"onTapAppleLogin");
[[KBLoginVM shared] signInWithAppleFromViewController:KB_CURRENT_NAV completion:^(BOOL success, NSError * _Nullable error) {
if (success) {
[KBHUD showInfo:KBLocalized(@"Signed in successfully")];
// 登录成功后切换到主 TabBar
dispatch_async(dispatch_get_main_queue(), ^{
id<UIApplicationDelegate> appDelegate = UIApplication.sharedApplication.delegate;
if ([appDelegate respondsToSelector:@selector(setupRootVC)]) {
AppDelegate *delegate = (AppDelegate *)appDelegate;
[delegate toMainTabbarVC];
}
});
} else {
NSString *msg = error.localizedDescription ?: KBLocalized(@"Sign-in failed");
[KBHUD showInfo:msg];
}
}];
}
- (void)onTapEmailLogin {
// 后续接入邮箱登录逻辑
KBLOG(@"onTapEmailLogin");
}
- (void)onTapPolicy {
// 打开服务条款/隐私政策
KBLOG(@"onTapPolicy");
}
- (void)onTapSignUp {
// 打开注册页
KBLOG(@"onTapSignUp");
KBEmailRegistVC *vc = [[KBEmailRegistVC alloc] init];
[KB_CURRENT_NAV pushViewController:vc animated:true];
}
- (void)onTapForgotPassword {
// 打开忘记密码页
KBLOG(@"onTapForgotPassword");
}
- (void)onTapBack {
// 与 BaseViewController 的返回逻辑保持一致:优先 pop其次 dismiss
if (self.navigationController && self.navigationController.viewControllers.count > 1) {
[self.navigationController popViewControllerAnimated:YES];
} else if (self.presentingViewController) {
[self dismissViewControllerAnimated:YES completion:nil];
}
}
#pragma mark - Lazy UI
- (UIImageView *)bgImageView {
if (!_bgImageView) {
_bgImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"login_bg_icon"]];
_bgImageView.contentMode = UIViewContentModeScaleAspectFill;
_bgImageView.clipsToBounds = YES;
}
return _bgImageView;
}
- (UIImageView *)topRightImageView {
if (!_topRightImageView) {
_topRightImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"login_jianp_icon"]];
_topRightImageView.contentMode = UIViewContentModeScaleAspectFit;
_topRightImageView.clipsToBounds = YES;
}
return _topRightImageView;
}
- (UIView *)contentContainerView {
if (!_contentContainerView) {
_contentContainerView = [UIView new];
_contentContainerView.backgroundColor = [UIColor whiteColor];
}
return _contentContainerView;
}
- (UIButton *)backButton {
if (!_backButton) {
_backButton = [UIButton buttonWithType:UIButtonTypeCustom];
UIImage *img = [UIImage imageNamed:@"back"];
if (!img) { img = [UIImage imageNamed:@"back_black_icon"]; }
if (img) {
[_backButton setImage:img forState:UIControlStateNormal];
}
_backButton.adjustsImageWhenHighlighted = YES;
_backButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
[_backButton addTarget:self action:@selector(onTapBack) forControlEvents:UIControlEventTouchUpInside];
}
return _backButton;
}
- (UILabel *)titleLabel {
if (!_titleLabel) {
_titleLabel = [UILabel new];
_titleLabel.text = KBLocalized(@"Log In To Key Of Love");
_titleLabel.textColor = [UIColor colorWithHex:KBBlackValue];
_titleLabel.font = [KBFont bold:18];
_titleLabel.textAlignment = NSTextAlignmentCenter;
}
return _titleLabel;
}
- (UIControl *)appleLoginButton {
if (!_appleLoginButton) {
ASAuthorizationAppleIDButton *btn = [ASAuthorizationAppleIDButton buttonWithType:ASAuthorizationAppleIDButtonTypeSignIn
style:ASAuthorizationAppleIDButtonStyleWhite];
[btn addTarget:self action:@selector(onTapAppleLogin) forControlEvents:UIControlEventTouchUpInside];
btn.cornerRadius = 10.0;
btn.layer.borderColor = [UIColor colorWithHex:0xE5E5E5].CGColor;
btn.layer.borderWidth = 1;
btn.layer.cornerRadius = 10.0;
btn.layer.masksToBounds = true;
_appleLoginButton = btn;
}
return _appleLoginButton;
}
- (UIButton *)emailLoginButton {
if (!_emailLoginButton) {
_emailLoginButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_emailLoginButton setTitle:KBLocalized(@"Continue Via Email") forState:UIControlStateNormal];
[_emailLoginButton setTitleColor:[UIColor colorWithHex:KBBlackValue] forState:UIControlStateNormal];
_emailLoginButton.titleLabel.font = [KBFont medium:19];
// _emailLoginButton.backgroundColor = [UIColor colorWithHex:0xF7F7F7];
_emailLoginButton.layer.cornerRadius = 10.0;
_emailLoginButton.layer.masksToBounds = YES;
_emailLoginButton.layer.borderColor = [UIColor colorWithHex:0xE5E5E5].CGColor;
_emailLoginButton.layer.borderWidth = 1;
_emailLoginButton.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 0);
_emailLoginButton.titleEdgeInsets = UIEdgeInsetsMake(0, 15, 0, 0);
UIImage *icon = [UIImage imageNamed:@"login_email_icon"];
if (icon) {
// 将邮箱图标缩小一点,让视觉上更接近系统 Apple 按钮的图标大小
CGFloat targetHeight = 12; // 设计上略小于按钮高度
CGFloat scale = targetHeight / icon.size.height;
CGSize targetSize = CGSizeMake(icon.size.width * scale, targetHeight);
UIGraphicsBeginImageContextWithOptions(targetSize, NO, 0.0);
[icon drawInRect:CGRectMake(0, 0, targetSize.width, targetSize.height)];
UIImage *scaledIcon = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[_emailLoginButton setImage:scaledIcon forState:UIControlStateNormal];
}
[_emailLoginButton addTarget:self action:@selector(onTapEmailLogin) forControlEvents:UIControlEventTouchUpInside];
}
return _emailLoginButton;
}
- (UITextView *)agreementTextView {
if (!_agreementTextView) {
_agreementTextView = [UITextView new];
_agreementTextView.backgroundColor = [UIColor clearColor];
_agreementTextView.editable = NO; // 不可编辑
_agreementTextView.selectable = NO; // 不可选中文本
_agreementTextView.scrollEnabled = NO;
_agreementTextView.textAlignment = NSTextAlignmentCenter;
_agreementTextView.textContainerInset = UIEdgeInsetsZero;
_agreementTextView.textContainer.lineFragmentPadding = 0;
// 协议文案terms of service / privacy policy 为纯黑色并可点击,其余为 #717171
NSString *fullText = @"By continuing, you agree to our terms of service and confirm that you have read our privacy policy";
NSString *termsText = @"terms of service";
NSString *privacyText = @"privacy policy";
NSMutableParagraphStyle *paragraph = [[NSMutableParagraphStyle alloc] init];
paragraph.alignment = NSTextAlignmentCenter; // 多行文本整体居中
NSMutableAttributedString *attr = [[NSMutableAttributedString alloc] initWithString:fullText
attributes:@{
NSFontAttributeName : [KBFont regular:10],
NSForegroundColorAttributeName : [UIColor colorWithHex:0x717171],
NSParagraphStyleAttributeName : paragraph
}];
NSString *lowerFull = fullText.lowercaseString;
NSRange termsRange = [lowerFull rangeOfString:termsText.lowercaseString];
if (termsRange.location != NSNotFound) {
[attr addAttributes:@{
NSForegroundColorAttributeName : [UIColor colorWithHex:KBBlackValue],
NSUnderlineStyleAttributeName : @(NSUnderlineStyleNone)
} range:termsRange];
}
NSRange privacyRange = [lowerFull rangeOfString:privacyText.lowercaseString];
if (privacyRange.location != NSNotFound) {
[attr addAttributes:@{
NSForegroundColorAttributeName : [UIColor colorWithHex:KBBlackValue],
NSUnderlineStyleAttributeName : @(NSUnderlineStyleNone)
} range:privacyRange];
}
_agreementTextView.linkTextAttributes = @{
NSForegroundColorAttributeName : [UIColor colorWithHex:KBBlackValue],
NSUnderlineStyleAttributeName : @(NSUnderlineStyleNone)
};
_agreementTextView.attributedText = attr;
// 自定义点击识别,避免系统选择/复制
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(kb_handleAgreementTap:)];
_agreementTextView.userInteractionEnabled = YES;
[_agreementTextView addGestureRecognizer:tap];
}
return _agreementTextView;
}
- (UILabel *)noAccountLabel {
if (!_noAccountLabel) {
_noAccountLabel = [UILabel new];
_noAccountLabel.text = KBLocalized(@"Don't Have An Account?");
_noAccountLabel.font = [KBFont regular:10];
_noAccountLabel.textColor = [UIColor colorWithHex:KBBlackValue];
}
return _noAccountLabel;
}
- (UIButton *)signUpButton {
if (!_signUpButton) {
_signUpButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_signUpButton setTitle:KBLocalized(@"Sign Up") forState:UIControlStateNormal];
[_signUpButton setTitleColor:[UIColor colorWithHex:KBColorValue] forState:UIControlStateNormal];
_signUpButton.titleLabel.font = [KBFont medium:10];
[_signUpButton addTarget:self action:@selector(onTapSignUp) forControlEvents:UIControlEventTouchUpInside];
}
return _signUpButton;
}
- (UIButton *)forgotPasswordButton {
if (!_forgotPasswordButton) {
_forgotPasswordButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_forgotPasswordButton setTitle:KBLocalized(@"Forgot Password?") forState:UIControlStateNormal];
[_forgotPasswordButton setTitleColor:[UIColor colorWithHex:KBColorValue] forState:UIControlStateNormal];
_forgotPasswordButton.titleLabel.font = [KBFont regular:10];
[_forgotPasswordButton addTarget:self action:@selector(onTapForgotPassword) forControlEvents:UIControlEventTouchUpInside];
}
return _forgotPasswordButton;
}
#pragma mark - Helper
/// 让按钮内部图片和文字整体居中并设置固定间距spacing 为设计稿值,调用时已做 KBFit
- (void)kb_centerImageAndTitleForButton:(UIButton *)button spacing:(CGFloat)spacing {
if (!button.currentImage || button.currentTitle.length == 0) { return; }
button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
CGSize imageSize = button.imageView.image.size;
CGSize titleSize = button.titleLabel.intrinsicContentSize;
CGFloat totalWidth = imageSize.width + spacing + titleSize.width;
CGFloat imageOffsetX = -(totalWidth / 2.0 - imageSize.width / 2.0);
CGFloat titleOffsetX = totalWidth / 2.0 - titleSize.width / 2.0;
button.imageEdgeInsets = UIEdgeInsetsMake(0, imageOffsetX, 0, -imageOffsetX);
button.titleEdgeInsets = UIEdgeInsetsMake(0, titleOffsetX, 0, -titleOffsetX);
}
#pragma mark - Agreement Tap
- (void)kb_handleAgreementTap:(UITapGestureRecognizer *)tap {
UITextView *textView = self.agreementTextView;
CGPoint point = [tap locationInView:textView];
// 将点击点转换到 textContainer 坐标系
CGPoint location = point;
location.x -= textView.textContainerInset.left;
location.y -= textView.textContainerInset.top;
NSLayoutManager *layoutManager = textView.layoutManager;
NSTextContainer *textContainer = textView.textContainer;
NSUInteger glyphIndex = [layoutManager glyphIndexForPoint:location
inTextContainer:textContainer];
if (glyphIndex >= textView.textStorage.length) { return; }
NSUInteger charIndex = [layoutManager characterIndexForGlyphAtIndex:glyphIndex];
NSString *lowerFull = textView.text.lowercaseString ?: @"";
NSRange termsRange = [lowerFull rangeOfString:@"terms of service"];
NSRange privacyRange = [lowerFull rangeOfString:@"privacy policy"];
BOOL hitTerms = (termsRange.location != NSNotFound && NSLocationInRange(charIndex, termsRange));
BOOL hitPrivacy = (privacyRange.location != NSNotFound && NSLocationInRange(charIndex, privacyRange));
if (hitTerms || hitPrivacy) {
[self onTapPolicy];
}
}
@end