This commit is contained in:
2025-12-02 18:29:04 +08:00
parent cafde48f4a
commit 8245e7b3d1
32 changed files with 2485 additions and 6 deletions

View File

@@ -24,6 +24,8 @@
#import "KBSexSelVC.h"
#import "KBKeyboardPermissionManager.h"
#import "KBVipPay.h"
#import "KBUserSessionManager.h"
#import "KBLoginVC.h"
// bundle id target
// PRODUCT_BUNDLE_IDENTIFIER
@@ -38,6 +40,9 @@
[[FGIAPManager shared] setConfigureWith:[IAPVerifyTransactionObj new]];
/// 2
[KBLocalizationManager shared].supportedLanguageCodes = @[ @"en", @"zh-Hans"];
/// 3 : token
// [[KBUserSessionManager shared] bootstrapIfNeeded];
// /访 Keychain
static NSString *const kKBFullAccessRecordInitializedKey = @"KBFullAccessRecordInitialized";
@@ -49,12 +54,12 @@
}
// Skip TabBar
BOOL hasShownSexVC = [[NSUserDefaults standardUserDefaults] boolForKey:KBSexSelectShownKey];
if (hasShownSexVC) {
[self setupRootVC];
} else {
// BOOL hasShownSexVC = [[NSUserDefaults standardUserDefaults] boolForKey:KBSexSelectShownKey];
// if (hasShownSexVC) {
// [self setupRootVC];
// } else {
[self setupSexSelectRootVC];
}
// }
// 访
[KBNetworkManager shared].enabled = YES;
@@ -93,9 +98,13 @@
///
- (void)setupSexSelectRootVC {
KBLoginVC *vc = [[KBLoginVC alloc] init];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
self.window.rootViewController = vc;
return;
KBSexSelVC *sexVC = [KBSexSelVC new];
__weak typeof(self) weakSelf = self;

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "login_bg_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "login_bg_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "login_email_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "login_email_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 753 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "login_jianp_icon@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "login_jianp_icon@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 KiB

View File

@@ -0,0 +1,16 @@
//
// KBLoginVC.h
// keyBoard
//
// Created by Mac on 2025/12/2.
//
#import "BaseViewController.h"
NS_ASSUME_NONNULL_BEGIN
@interface KBLoginVC : BaseViewController
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,308 @@
//
// KBLoginVC.m
// keyBoard
//
// Created by Mac on 2025/12/2.
//
#import "KBLoginVC.h"
#import <AuthenticationServices/AuthenticationServices.h>
@interface KBLoginVC ()
//
@property (nonatomic, strong) UIImageView *bgImageView; // login_bg_icon
@property (nonatomic, strong) UIImageView *topRightImageView; // login_jianp_icon
// 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) UILabel *agreementLabel; // By Continuing, You Agree To Our
@property (nonatomic, strong) UIButton *policyButton; // Terms Of Service & Privacy Policy
@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;
}
}
#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.topRightImageView];
[self.topRightImageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.view).offset(KB_StatusBarHeight() + KBFit(24));
make.right.equalTo(self.view).offset(-KBFit(20));
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(272);
}];
//
[self.contentContainerView addSubview:self.titleLabel];
[self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.contentContainerView).offset(32);
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(16);
make.left.right.height.equalTo(self.appleLoginButton);
}];
//
[self.contentContainerView addSubview:self.policyButton];
[self.contentContainerView addSubview:self.agreementLabel];
[self.policyButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self.contentContainerView);
make.bottom.equalTo(self.contentContainerView).offset(-KB_SAFE_BOTTOM - 84);
}];
[self.agreementLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self.contentContainerView);
make.bottom.equalTo(self.policyButton.mas_top).offset(-4);
}];
//
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 - 24);
}];
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(-8);
}];
[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 {
// Apple Sign-In
}
- (void)onTapEmailLogin {
//
}
- (void)onTapPolicy {
// /
}
- (void)onTapSignUp {
//
}
- (void)onTapForgotPassword {
//
}
#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;
}
- (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;
_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:16];
_emailLoginButton.backgroundColor = [UIColor colorWithHex:0xF7F7F7];
_emailLoginButton.layer.cornerRadius = 10.0;
_emailLoginButton.layer.masksToBounds = YES;
UIImage *icon = [UIImage imageNamed:@"login_email_icon"];
if (icon) {
[_emailLoginButton setImage:icon forState:UIControlStateNormal];
//
_emailLoginButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
_emailLoginButton.imageEdgeInsets = UIEdgeInsetsMake(0, 16, 0, 0);
_emailLoginButton.titleEdgeInsets = UIEdgeInsetsMake(0, 32, 0, 0);
}
[_emailLoginButton addTarget:self action:@selector(onTapEmailLogin) forControlEvents:UIControlEventTouchUpInside];
}
return _emailLoginButton;
}
- (UILabel *)agreementLabel {
if (!_agreementLabel) {
_agreementLabel = [UILabel new];
_agreementLabel.text = KBLocalized(@"By Continuing, You Agree To Our");
_agreementLabel.font = [KBFont regular:12];
_agreementLabel.textColor = [UIColor colorWithWhite:0.45 alpha:1.0];
_agreementLabel.textAlignment = NSTextAlignmentCenter;
_agreementLabel.numberOfLines = 1;
}
return _agreementLabel;
}
- (UIButton *)policyButton {
if (!_policyButton) {
_policyButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_policyButton setTitle:KBLocalized(@"Terms Of Service & Privacy Policy") forState:UIControlStateNormal];
[_policyButton setTitleColor:[UIColor colorWithHex:KBColorValue] forState:UIControlStateNormal];
_policyButton.titleLabel.font = [KBFont regular:12];
[_policyButton addTarget:self action:@selector(onTapPolicy) forControlEvents:UIControlEventTouchUpInside];
}
return _policyButton;
}
- (UILabel *)noAccountLabel {
if (!_noAccountLabel) {
_noAccountLabel = [UILabel new];
_noAccountLabel.text = KBLocalized(@"Don't Have An Account?");
_noAccountLabel.font = [KBFont regular:13];
_noAccountLabel.textColor = [UIColor colorWithWhite:0.45 alpha:1.0];
}
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:13];
[_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:13];
[_forgotPasswordButton addTarget:self action:@selector(onTapForgotPassword) forControlEvents:UIControlEventTouchUpInside];
}
return _forgotPasswordButton;
}
@end

View File

@@ -0,0 +1,42 @@
//
// KBUserSessionManager.h
// keyBoard
//
// Created by Mac on 2025/12/1.
//
// KBUserSessionManager.h
// 主 App + 键盘扩展 共用的登录会话管理
#import <Foundation/Foundation.h>
@class KBUser;
NS_ASSUME_NONNULL_BEGIN
@interface KBUserSessionManager : NSObject
+ (instancetype)shared;
/// 当前登录用户(可能为 nil
@property (atomic, strong, readonly, nullable) KBUser *currentUser;
/// 是否已登录:由 KBAuthManager 基于 accessToken 判断
@property (nonatomic, assign, readonly) BOOL isLoggedIn;
/// 启动时调用:做“卸载重装”检测 & 恢复本地缓存
- (void)bootstrapIfNeeded;
/// 当前 accessToken可能为 nil/空字符串
- (nullable NSString *)accessToken;
/// 登录成功统一入口:写 Keychain + 缓存用户信息
- (void)handleLoginSuccessWithUser:(KBUser *)user;
/// 退出登录:清 Keychain + 本地缓存
- (void)logout;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,155 @@
//
// KBUserSessionManager.m
// keyBoard
//
// Created by Mac on 2025/12/1.
//
#import "KBUserSessionManager.h"
#import "KBAuthManager.h"
#import "KBUser.h"
#import "KBConfig.h"
#import <MJExtension/MJExtension.h>
/// App Group key
static NSString * const kKBSessionUserStoreKey = @"KBSession.currentUser";
/// key
static NSString * const kKBSessionInstallFlagKey = @"KBSession.installInitialized";
@interface KBUserSessionManager ()
@property (nonatomic, strong) NSUserDefaults *defaults;
@property (atomic, strong, readwrite, nullable) KBUser *currentUser;
@property (nonatomic, assign) BOOL didBootstrap;
@end
@implementation KBUserSessionManager
+ (instancetype)shared {
static KBUserSessionManager *m;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
m = [KBUserSessionManager new];
});
return m;
}
- (instancetype)init {
if (self = [super init]) {
// App Group UserDefaultsApp
NSUserDefaults *ud = [[NSUserDefaults alloc] initWithSuiteName:AppGroup];
_defaults = ud ?: [NSUserDefaults standardUserDefaults];
// token signOut
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(_onAuthChanged:)
name:KBAuthChangedNotification
object:nil];
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - Public
- (void)bootstrapIfNeeded {
if (self.didBootstrap) return;
self.didBootstrap = YES;
BOOL hasInitializedThisInstall = [self.defaults boolForKey:kKBSessionInstallFlagKey];
KBAuthManager *auth = [KBAuthManager shared]; // reloadFromKeychain
if (!hasInitializedThisInstall) {
//
[self.defaults setBool:YES forKey:kKBSessionInstallFlagKey];
[self.defaults synchronize];
// Keychain token
if (auth.current.accessToken.length > 0) {
[auth signOut];
}
//
[self.defaults removeObjectForKey:kKBSessionUserStoreKey];
[self.defaults synchronize];
self.currentUser = nil;
} else {
//
[self p_loadUserFromStore];
}
}
- (BOOL)isLoggedIn {
return [[KBAuthManager shared] isLoggedIn];
}
- (NSString *)accessToken {
return [KBAuthManager shared].current.accessToken;
}
- (void)handleLoginSuccessWithUser:(KBUser *)user {
if (!user) return;
// Keychain KBAuthManager
NSString *token = user.token;
if (token.length > 0) {
[[KBAuthManager shared] saveAccessToken:token
refreshToken:user.refreshToken
expiryDate:user.expiryDate
userIdentifier:user.appleUserId ?: user.userId];
}
// App/使
self.currentUser = user;
[self p_saveUserToStore:user];
}
- (void)logout {
// Keychain
[[KBAuthManager shared] signOut];
//
self.currentUser = nil;
[self.defaults removeObjectForKey:kKBSessionUserStoreKey];
[self.defaults synchronize];
}
#pragma mark - Private
- (void)p_loadUserFromStore {
id obj = [self.defaults objectForKey:kKBSessionUserStoreKey];
if (![obj isKindOfClass:[NSDictionary class]]) {
self.currentUser = nil;
return;
}
KBUser *user = [KBUser mj_objectWithKeyValues:(NSDictionary *)obj];
self.currentUser = user;
}
- (void)p_saveUserToStore:(KBUser *)user {
if (!user) {
[self.defaults removeObjectForKey:kKBSessionUserStoreKey];
[self.defaults synchronize];
return;
}
NSDictionary *dict = [user mj_keyValues];
if (dict) {
[self.defaults setObject:dict forKey:kKBSessionUserStoreKey];
[self.defaults synchronize];
}
}
- (void)_onAuthChanged:(NSNotification *)note {
// token currentUser
if (![[KBAuthManager shared] isLoggedIn]) {
self.currentUser = nil;
[self.defaults removeObjectForKey:kKBSessionUserStoreKey];
[self.defaults synchronize];
}
}
@end

View File

@@ -185,7 +185,7 @@
- (void)onTapAvatarEdit { [self presentImagePicker]; }
- (void)onTapLogout {
// 退
[[KBUserSessionManager shared] logout];
}
#pragma mark - Lazy UI

View File

@@ -0,0 +1,31 @@
//
// CRBoxFlowLayout.h
// CaiShenYe
//
// Created by Chobits on 2019/1/3.
// Copyright © 2019 Chobits. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface CRBoxFlowLayout : UICollectionViewFlowLayout
/** ifNeedEqualGap
* default: YES
*/
@property (assign, nonatomic) BOOL ifNeedEqualGap;
@property (assign, nonatomic) NSInteger itemNum;
/** minLineSpacing
* default: 10
*/
@property (assign, nonatomic) NSInteger minLineSpacing;
- (void)autoCalucateLineSpacing;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,58 @@
//
// CRBoxFlowLayout.m
// CaiShenYe
//
// Created by Chobits on 2019/1/3.
// Copyright © 2019 Chobits. All rights reserved.
//
#import "CRBoxFlowLayout.h"
@implementation CRBoxFlowLayout
- (instancetype)init
{
self = [super init];
if (self) {
[self initPara];
}
return self;
}
- (void)initPara
{
self.ifNeedEqualGap = YES;
self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.minLineSpacing = 10;
self.minimumLineSpacing = 0;
self.minimumInteritemSpacing = 0;
self.sectionInset = UIEdgeInsetsZero;
self.itemNum = 1;
}
- (void)prepareLayout
{
if (_ifNeedEqualGap) {
[self autoCalucateLineSpacing];
}
[super prepareLayout];
}
- (void)autoCalucateLineSpacing
{
if (self.itemNum > 1) {
CGFloat width = CGRectGetWidth(self.collectionView.frame);
self.minimumLineSpacing = floor(1.0 * (width - self.itemNum * self.itemSize.width - self.collectionView.contentInset.left - self.collectionView.contentInset.right) / (self.itemNum - 1));
if (self.minimumLineSpacing < self.minLineSpacing) {
self.minimumLineSpacing = self.minLineSpacing;
}
}else{
self.minimumLineSpacing = 0;
}
}
@end

View File

@@ -0,0 +1,38 @@
//
// CRBoxInputCell.h
// CaiShenYe
//
// Created by Chobits on 2019/1/3.
// Copyright © 2019 Chobits. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "CRBoxInputCellProperty.h"
NS_ASSUME_NONNULL_BEGIN
#define CRBoxCursoryAnimationKey @"CRBoxCursoryAnimationKey"
#define CRBoxInputCellID @"CRBoxInputCellID"
@interface CRBoxInputCell : UICollectionViewCell
/**
cursor
You should not use these properties, unless you know what you are doing.
*/
@property (strong, nonatomic) UIView *cursorView;
@property (assign, nonatomic) BOOL ifNeedCursor;
/**
boxInputCellProperty
You should not use these properties, unless you know what you are doing.
*/
@property (strong, nonatomic) CRBoxInputCellProperty *boxInputCellProperty;
// 你可以在继承的子类中重写父类方法
// You can inherit and rewrite
- (UIView *)createCustomSecurityView __deprecated_msg("Please use `customSecurityViewBlock` in CRBoxInputCellProperty.");
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,281 @@
//
// CRBoxInputCell.m
// CaiShenYe
//
// Created by Chobits on 2019/1/3.
// Copyright © 2019 Chobits. All rights reserved.
//
#import "CRBoxInputCell.h"
#import <Masonry/Masonry.h>
#import "CRLineView.h"
@interface CRBoxInputCell ()
{
}
@property (strong, nonatomic) UILabel *valueLabel;
@property (strong, nonatomic) CABasicAnimation *opacityAnimation;
@property (strong, nonatomic) UIView *customSecurityView;
@property (strong, nonatomic) CRLineView *lineView;
@end
@implementation CRBoxInputCell
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self createUIBase];
}
return self;
}
- (void)initPara
{
self.ifNeedCursor = YES;
self.userInteractionEnabled = NO;
}
- (void)createUIBase
{
[self initPara];
_valueLabel = [UILabel new];
_valueLabel.font = [UIFont systemFontOfSize:38];
[self.contentView addSubview:_valueLabel];
[_valueLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.offset(0);
make.centerY.offset(0);
}];
_cursorView = [UIView new];
[self.contentView addSubview:_cursorView];
[_cursorView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.offset(0);
make.centerY.offset(0);
}];
[self initCellProperty];
}
- (void)initCellProperty
{
CRBoxInputCellProperty *cellProperty = [CRBoxInputCellProperty new];
self.boxInputCellProperty = cellProperty;
}
- (void)valueLabelLoadData
{
_valueLabel.hidden = NO;
[self hideCustomSecurityView];
//
__weak typeof(self) weakSelf = self;
void (^defaultTextConfig)(void) = ^{
if (weakSelf.boxInputCellProperty.cellFont) {
weakSelf.valueLabel.font = weakSelf.boxInputCellProperty.cellFont;
}
if (weakSelf.boxInputCellProperty.cellTextColor) {
weakSelf.valueLabel.textColor = weakSelf.boxInputCellProperty.cellTextColor;
}
};
//
void (^placeholderTextConfig)(void) = ^{
if (weakSelf.boxInputCellProperty.cellFont) {
weakSelf.valueLabel.font = weakSelf.boxInputCellProperty.cellPlaceholderFont;
}
if (weakSelf.boxInputCellProperty.cellTextColor) {
weakSelf.valueLabel.textColor = weakSelf.boxInputCellProperty.cellPlaceholderTextColor;
}
};
BOOL hasOriginValue = self.boxInputCellProperty.originValue && self.boxInputCellProperty.originValue.length > 0;
if (hasOriginValue) {
if (self.boxInputCellProperty.ifShowSecurity) {
if (self.boxInputCellProperty.securityType == CRBoxSecuritySymbolType) {
_valueLabel.text = self.boxInputCellProperty.securitySymbol;
}else if (self.boxInputCellProperty.securityType == CRBoxSecurityCustomViewType) {
_valueLabel.hidden = YES;
[self showCustomSecurityView];
}
}else{
_valueLabel.text = self.boxInputCellProperty.originValue;
}
defaultTextConfig();
}else{
BOOL hasPlaceholderText = self.boxInputCellProperty.cellPlaceholderText && self.boxInputCellProperty.cellPlaceholderText.length > 0;
//
if (hasPlaceholderText) {
_valueLabel.text = self.boxInputCellProperty.cellPlaceholderText;
placeholderTextConfig();
}
//
else{
_valueLabel.text = @"";
defaultTextConfig();
}
}
}
#pragma mark - Custom security view
- (void)showCustomSecurityView
{
if (!self.customSecurityView.superview) {
[self.contentView addSubview:self.customSecurityView];
[self.customSecurityView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(UIEdgeInsetsZero);
}];
}
self.customSecurityView.alpha = 1;
}
- (void)hideCustomSecurityView
{
// Must add this judge. Otherwise _customSecurityView maybe null, and cause error.
if (_customSecurityView) {
self.customSecurityView.alpha = 0;
}
}
#pragma mark - Setter & Getter
- (CABasicAnimation *)opacityAnimation
{
if (!_opacityAnimation) {
_opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
_opacityAnimation.fromValue = @(1.0);
_opacityAnimation.toValue = @(0.0);
_opacityAnimation.duration = 0.9;
_opacityAnimation.repeatCount = HUGE_VALF;
_opacityAnimation.removedOnCompletion = YES;
_opacityAnimation.fillMode = kCAFillModeForwards;
_opacityAnimation.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
}
return _opacityAnimation;
}
- (void)setSelected:(BOOL)selected
{
if (selected) {
self.layer.borderColor = self.boxInputCellProperty.cellBorderColorSelected.CGColor;
self.backgroundColor = self.boxInputCellProperty.cellBgColorSelected;
}else{
BOOL hasFill = _valueLabel.text.length > 0 ? YES : NO;
UIColor *cellBorderColor = self.boxInputCellProperty.cellBorderColorNormal;
UIColor *cellBackgroundColor = self.boxInputCellProperty.cellBgColorNormal;
if (hasFill) {
if (self.boxInputCellProperty.cellBorderColorFilled) {
cellBorderColor = self.boxInputCellProperty.cellBorderColorFilled;
}
if (self.boxInputCellProperty.cellBgColorFilled) {
cellBackgroundColor = self.boxInputCellProperty.cellBgColorFilled;
}
}
self.layer.borderColor = cellBorderColor.CGColor;
self.backgroundColor = cellBackgroundColor;
}
if (_lineView) {
//
if (!selected) {
if (self.boxInputCellProperty.originValue.length > 0 && _lineView.underlineColorFilled) {
//
_lineView.lineView.backgroundColor = _lineView.underlineColorFilled;
}else if (_lineView.underlineColorNormal) {
//
_lineView.lineView.backgroundColor = _lineView.underlineColorNormal;
}else{
//
_lineView.lineView.backgroundColor = CRColorMaster;
}
}
//
else if (selected && _lineView.underlineColorSelected){
_lineView.lineView.backgroundColor = _lineView.underlineColorSelected;
}
//
else{
_lineView.lineView.backgroundColor = CRColorMaster;
}
_lineView.selected = selected;
}
if (_ifNeedCursor) {
if (selected) {
_cursorView.hidden= NO;
[_cursorView.layer addAnimation:self.opacityAnimation forKey:CRBoxCursoryAnimationKey];
}else{
_cursorView.hidden= YES;
[_cursorView.layer removeAnimationForKey:CRBoxCursoryAnimationKey];
}
}else{
_cursorView.hidden= YES;
}
}
- (void)setBoxInputCellProperty:(CRBoxInputCellProperty *)boxInputCellProperty
{
_boxInputCellProperty = boxInputCellProperty;
_cursorView.backgroundColor = boxInputCellProperty.cellCursorColor;
[_cursorView mas_updateConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo(boxInputCellProperty.cellCursorWidth);
make.height.mas_equalTo(boxInputCellProperty.cellCursorHeight);
}];
self.layer.cornerRadius = boxInputCellProperty.cornerRadius;
self.layer.borderWidth = boxInputCellProperty.borderWidth;
[self valueLabelLoadData];
}
- (UIView *)customSecurityView
{
if (!_customSecurityView) {
// Compatiable for 0.19 verion and earlier.
if ([self respondsToSelector:@selector(createCustomSecurityView)]) {
_customSecurityView = [self createCustomSecurityView];
}
else if(_boxInputCellProperty.customSecurityViewBlock){
NSAssert(_boxInputCellProperty.customSecurityViewBlock, @"customSecurityViewBlock can not be null");
_customSecurityView = _boxInputCellProperty.customSecurityViewBlock();
}
}
return _customSecurityView;
}
- (void)layoutSubviews
{
__weak typeof(self) weakSelf = self;
if (_boxInputCellProperty.showLine && !_lineView) {
NSAssert(_boxInputCellProperty.customLineViewBlock, @"customLineViewBlock can not be null");
_lineView = _boxInputCellProperty.customLineViewBlock();
[self.contentView addSubview:_lineView];
[_lineView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.bottom.top.offset(0);
}];
}
if (_boxInputCellProperty.configCellShadowBlock) {
_boxInputCellProperty.configCellShadowBlock(weakSelf.layer);
}
[super layoutSubviews];
}
@end

View File

@@ -0,0 +1,200 @@
//
// CRBoxInputCellProperty.h
// CaiShenYe
//
// Created by Chobits on 2019/1/3.
// Copyright © 2019 Chobits. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "CRLineView.h"
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, CRBoxSecurityType) {
CRBoxSecuritySymbolType,
CRBoxSecurityCustomViewType,
};
typedef UIView *_Nonnull(^CustomSecurityViewBlock)(void);
typedef CRLineView *_Nonnull(^CustomLineViewBlock)(void);
typedef void(^ConfigCellShadowBlock)(CALayer *layer);
@interface CRBoxInputCellProperty : NSObject <NSCopying>
#pragma mark - UI
/**
cell边框宽度
默认0.5
*/
@property (assign, nonatomic) CGFloat borderWidth;
/**
cell边框颜色
状态:未选中状态时
默认:[UIColor colorWithRed:228/255.0 green:228/255.0 blue:228/255.0 alpha:1]
*/
@property (copy, nonatomic) UIColor *cellBorderColorNormal;
/**
cell边框颜色
状态:选中状态时
默认:[UIColor colorWithRed:255/255.0 green:70/255.0 blue:62/255.0 alpha:1]
*/
@property (copy, nonatomic) UIColor *cellBorderColorSelected;
/**
cell边框颜色
状态:填充文字后,未选中状态时
默认nil
*/
@property (copy, nonatomic) UIColor *__nullable cellBorderColorFilled;
/**
cell背景颜色
状态:无填充文字,未选中状态时
默认:[UIColor whiteColor]
*/
@property (copy, nonatomic) UIColor *cellBgColorNormal;
/**
cell背景颜色
状态:选中状态时
默认:[UIColor whiteColor]
*/
@property (copy, nonatomic) UIColor *cellBgColorSelected;
/**
cell背景颜色
状态:填充文字后,未选中状态时
默认与cellBgColorFilled相同
*/
@property (copy, nonatomic) UIColor *__nullable cellBgColorFilled;
/**
光标颜色
默认: [UIColor colorWithRed:255/255.0 green:70/255.0 blue:62/255.0 alpha:1]
*/
@property (copy, nonatomic) UIColor *cellCursorColor;
/**
光标宽度
默认: 2
*/
@property (assign, nonatomic) CGFloat cellCursorWidth;
/**
光标高度
默认: 32
*/
@property (assign, nonatomic) CGFloat cellCursorHeight;
/**
圆角
默认: 4
*/
@property (assign, nonatomic) CGFloat cornerRadius;
#pragma mark - line
/**
显示下划线
默认: NO
*/
@property (assign, nonatomic) BOOL showLine;
#pragma mark - label
/**
字体/字号
默认:[UIFont systemFontOfSize:20];
*/
@property (copy, nonatomic) UIFont *cellFont;
/**
字体颜色
默认:[UIColor blackColor];
*/
@property (copy, nonatomic) UIColor *cellTextColor;
#pragma mark - Security
/**
是否密文显示
默认NO
*/
@property (assign, nonatomic) BOOL ifShowSecurity;
/**
密文符号
默认:✱
说明只有ifShowSecurity=YES时有效
*/
@property (copy, nonatomic) NSString *securitySymbol;
/**
保存当前显示的字符
若想一次性修改所有输入值,请使用 CRBoxInputView中的'reloadInputString'方法
禁止修改该值!!!(除非你知道该怎么使用它。)
*/
@property (copy, nonatomic, readonly) NSString *originValue;
- (void)setMyOriginValue:(NSString *)originValue;
/**
密文类型
默认CRBoxSecuritySymbolType
类型说明:
CRBoxSecuritySymbolType 符号类型根据securitySymboloriginValue的内容来显示
CRBoxSecurityCustomViewType 自定义View类型可以自定义密文状态下的图片View
*/
@property (assign, nonatomic) CRBoxSecurityType securityType;
#pragma mark - Placeholder
/**
占位符默认填充值
禁止修改该值!!!(除非你知道该怎么使用它。)
*/
@property (strong, nonatomic) NSString *__nullable cellPlaceholderText;
/**
占位符字体颜色
默认:[UIColor colorWithRed:114/255.0 green:126/255.0 blue:124/255.0 alpha:0.3];
*/
@property (copy, nonatomic) UIColor *cellPlaceholderTextColor;
/**
占位符字体/字号
默认:[UIFont systemFontOfSize:20];
*/
@property (copy, nonatomic) UIFont *cellPlaceholderFont;
#pragma mark - Block
/**
自定义密文View回调
*/
@property (copy, nonatomic) CustomSecurityViewBlock customSecurityViewBlock;
/**
自定义下划线回调
*/
@property (copy, nonatomic) CustomLineViewBlock customLineViewBlock;
/**
自定义阴影回调
*/
@property (copy, nonatomic) ConfigCellShadowBlock __nullable configCellShadowBlock;
#pragma mark - Test
@property (assign, nonatomic) NSInteger index;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,157 @@
//
// CRBoxInputself.m
// CaiShenYe
//
// Created by Chobits on 2019/1/3.
// Copyright © 2019 Chobits. All rights reserved.
//
#import "CRBoxInputCellProperty.h"
#import <Masonry/Masonry.h>
@interface CRBoxInputCellProperty ()
@property (copy, nonatomic, readwrite) NSString *originValue;
@end
@implementation CRBoxInputCellProperty
- (instancetype)init
{
self = [super init];
if (self) {
__weak typeof(self) weakSelf = self;
// UI
self.borderWidth = (0.5);
self.cellBorderColorNormal = [UIColor colorWithRed:228/255.0 green:228/255.0 blue:228/255.0 alpha:1];
self.cellBorderColorSelected = [UIColor colorWithRed:255/255.0 green:70/255.0 blue:62/255.0 alpha:1];
self.cellBorderColorFilled = nil;
self.cellBgColorNormal = [UIColor whiteColor];
self.cellBgColorSelected = [UIColor whiteColor];
self.cellBgColorFilled = nil;
self.cellCursorColor = [UIColor colorWithRed:255/255.0 green:70/255.0 blue:62/255.0 alpha:1];
self.cellCursorWidth = 2;
self.cellCursorHeight = 32;
self.cornerRadius = 4;
// line
self.showLine = NO;
// label
self.cellFont = [UIFont systemFontOfSize:20];
self.cellTextColor = [UIColor blackColor];
// Security
self.ifShowSecurity = NO;
self.securitySymbol = @"✱";
self.originValue = @"";
self.securityType = CRBoxSecuritySymbolType;
// Placeholder
self.cellPlaceholderText = nil;
self.cellPlaceholderTextColor = [UIColor colorWithRed:114/255.0 green:116/255.0 blue:124/255.0 alpha:0.3];
self.cellPlaceholderFont = [UIFont systemFontOfSize:20];
// Block
self.customSecurityViewBlock = ^UIView * _Nonnull{
return [weakSelf defaultCustomSecurityView];
};
self.customLineViewBlock = ^CRLineView * _Nonnull{
return [CRLineView new];
};
self.configCellShadowBlock = nil;
// Test
self.index = 0;
}
return self;
}
#pragma mark - Copy
- (id)copyWithZone:(NSZone *)zone
{
CRBoxInputCellProperty *copy = [[self class] allocWithZone:zone];
// UI
copy.borderWidth = _borderWidth;
copy.cellBorderColorNormal = [_cellBorderColorNormal copy];
copy.cellBorderColorSelected = [_cellBorderColorSelected copy];
if (_cellBorderColorFilled) {
copy.cellBorderColorFilled = [_cellBorderColorFilled copy];
}
copy.cellBgColorNormal = [_cellBgColorNormal copy];
copy.cellBgColorSelected = [_cellBgColorSelected copy];
if (_cellBgColorFilled) {
copy.cellBgColorFilled = [_cellBgColorFilled copy];
}
copy.cellCursorColor = [_cellCursorColor copy];
copy.cellCursorWidth = _cellCursorWidth;
copy.cellCursorHeight = _cellCursorHeight;
copy.cornerRadius = _cornerRadius;
// line
copy.showLine = _showLine;
// label
copy.cellFont = [_cellFont copy];
copy.cellTextColor = [_cellTextColor copy];
// Security
copy.ifShowSecurity = _ifShowSecurity;
copy.securitySymbol = [_securitySymbol copy];
copy.originValue = [_originValue copy];
copy.securityType = _securityType;
// Placeholder
if (_cellPlaceholderText) {
copy.cellPlaceholderText = [_cellPlaceholderText copy];
}
copy.cellPlaceholderTextColor = [_cellPlaceholderTextColor copy];
copy.cellPlaceholderFont = [_cellPlaceholderFont copy];
// Block
copy.customSecurityViewBlock = [_customSecurityViewBlock copy];
copy.customLineViewBlock = [_customLineViewBlock copy];
if (_configCellShadowBlock) {
copy.configCellShadowBlock = [_configCellShadowBlock copy];
}
// Test
copy.index = _index;
return copy;
}
#pragma mark - Getter
- (UIView *)defaultCustomSecurityView
{
UIView *customSecurityView = [UIView new];
customSecurityView.backgroundColor = [UIColor clearColor];
// circleView
static CGFloat circleViewWidth = 20;
UIView *circleView = [UIView new];
circleView.backgroundColor = [UIColor blackColor];
circleView.layer.cornerRadius = 4;
[customSecurityView addSubview:circleView];
[circleView mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.height.mas_equalTo(circleViewWidth);
make.centerX.offset(0);
make.centerY.offset(0);
}];
return customSecurityView;
}
#pragma mark - Setter
- (void)setMyOriginValue:(NSString *)originValue {
_originValue = originValue;
}
@end

View File

@@ -0,0 +1,165 @@
//
// CRBoxInputView.h
// CRBoxInputView
//
// Created by Chobits on 2019/1/3.
// Copyright © 2019 Chobits. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "CRBoxFlowLayout.h"
#import "CRBoxInputCellProperty.h"
#import "CRBoxInputCell.h"
@class CRBoxInputView;
typedef NS_ENUM(NSInteger, CRTextEditStatus) {
CRTextEditStatus_Idle,
CRTextEditStatus_BeginEdit,
CRTextEditStatus_EndEdit,
};
typedef NS_ENUM(NSInteger, CRInputType) {
/// 数字
CRInputType_Number,
/// 普通(不作任何处理)
CRInputType_Normal,
/// 自定义正则此时需要设置customInputRegex
CRInputType_Regex,
};
typedef void(^TextDidChangeblock)(NSString * _Nullable text, BOOL isFinished);
typedef void(^TextEditStatusChangeblock)(CRTextEditStatus editStatus);
typedef NSString *(^TextCustomProcessblock)(NSString * _Nullable text);
@interface CRBoxInputView : UIView
/**
是否需要光标
ifNeedCursor
default: YES
*/
@property (assign, nonatomic) BOOL ifNeedCursor;
/**
验证码长度
codeLength
default: 4
*/
@property (nonatomic, assign, readonly) NSInteger codeLength; //If you want to set codeLength, please use `- (instancetype)initWithCodeLength:(NSInteger)codeLength, or - (void)resetCodeLength:(NSInteger)codeLength beginEdit:(BOOL)beginEdit` in CRBoxInputView.
/**
是否开启密文模式
描述:你可以在任何时候修改该属性,并且已经存在的文字会自动刷新。
ifNeedSecurity
desc: You can change this property anytime. And the existing texts can be refreshed automatically.
default: NO
*/
@property (assign, nonatomic) BOOL ifNeedSecurity;
/**
显示密文的延时时间
securityDelay
desc: show security delay time
default: 0.3
*/
@property (assign, nonatomic) CGFloat securityDelay;
/**
键盘类型
keyBoardType
default: UIKeyboardTypeNumberPad
*/
@property (assign, nonatomic) UIKeyboardType keyBoardType;
/**
输入样式
inputType
default: CRInputType_Number
*/
@property (assign, nonatomic) CRInputType inputType;
/**
自定义正则匹配输入内容
customInputRegex
default: @""
当inputType == CRInputType_Regex时才会生效
*/
@property (copy, nonatomic) NSString * _Nullable customInputRegex;
/**
textContentType
描述: 你可以设置为 'nil' 或者 'UITextContentTypeOneTimeCode' 来自动获取短信验证码
desc: You set this 'nil' or 'UITextContentTypeOneTimeCode' to auto fill verify code.
default: nil
*/
@property (null_unspecified,nonatomic,copy) UITextContentType textContentType NS_AVAILABLE_IOS(10_0);
/**
占位字符填充值
说明:在对应的输入框没有内容时,会显示该值。
默认nil
*/
@property (strong, nonatomic) NSString * _Nullable placeholderText;
/**
弹出键盘时,是否清空所有输入
只有在输入的字数等于codeLength时生效
default: NO
*/
@property (assign, nonatomic) BOOL ifClearAllInBeginEditing;
@property (copy, nonatomic) TextDidChangeblock _Nullable textDidChangeblock;
@property (copy, nonatomic) TextEditStatusChangeblock _Nullable textEditStatusChangeblock;
/// 文本自定义处理
@property (copy, nonatomic) TextCustomProcessblock _Nullable textCustomProcessblock;
@property (strong, nonatomic) CRBoxFlowLayout * _Nullable boxFlowLayout;
@property (strong, nonatomic) CRBoxInputCellProperty * _Nullable customCellProperty;
@property (strong, nonatomic, readonly) NSString * _Nullable textValue;
@property (strong, nonatomic) UIView * _Nullable inputAccessoryView;
/**
装载数据和准备界面
desc: Load and prepareView
beginEdit: 自动开启编辑模式
default: YES
*/
- (void)loadAndPrepareView;
- (void)loadAndPrepareViewWithBeginEdit:(BOOL)beginEdit;
/**
重载输入的数据(用来设置预设数据)
desc:Reload string. (You can use this function to set deault value)
*/
- (void)reloadInputString:(NSString *_Nullable)value;
/**
清空输入
desc: Clear all
beginEdit: 自动开启编辑模式
default: YES
*/
- (void)clearAll;
- (void)clearAllWithBeginEdit:(BOOL)beginEdit;
- (UICollectionView *_Nullable)mainCollectionView;
// 快速设置
// Qiuck set
- (void)quickSetSecuritySymbol:(NSString *_Nullable)securitySymbol;
// 你可以在继承的子类中调用父类方法
// You can inherit and call super
- (void)initDefaultValue;
// 你可以在继承的子类中重写父类方法
// You can inherit and rewrite
- (UICollectionViewCell *_Nullable)customCollectionView:(UICollectionView *_Nullable)collectionView cellForItemAtIndexPath:(NSIndexPath *_Nullable)indexPath;
// code Length 调整
- (void)resetCodeLength:(NSInteger)codeLength beginEdit:(BOOL)beginEdit;
// Init
- (instancetype _Nullable )initWithCodeLength:(NSInteger)codeLength;
@end

View File

@@ -0,0 +1,649 @@
//
// CRBoxInputView.m
// CRBoxInputView
//
// Created by Chobits on 2019/1/3.
// Copyright © 2019 Chobits. All rights reserved.
//
#import "CRBoxInputView.h"
#import <Masonry/Masonry.h>
#import "CRBoxTextView.h"
typedef NS_ENUM(NSInteger, CRBoxTextChangeType) {
CRBoxTextChangeType_NoChange,
CRBoxTextChangeType_Insert,
CRBoxTextChangeType_Delete,
};
@interface CRBoxInputView () <UICollectionViewDataSource, UICollectionViewDelegate, UITextFieldDelegate>
{
NSInteger _oldLength;
BOOL _ifNeedBeginEdit;
}
@property (nonatomic, assign) NSInteger codeLength;
@property (nonatomic, strong) UITapGestureRecognizer *tapGR;
@property (nonatomic, strong) CRBoxTextView *textView;
@property (nonatomic, strong) UICollectionView *mainCollectionView;
@property (nonatomic, strong) NSMutableArray <NSString *> *valueArr;
@property (nonatomic, strong) NSMutableArray <CRBoxInputCellProperty *> *cellPropertyArr;
@end
@implementation CRBoxInputView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self initDefaultValue];
[self addNotificationObserver];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
[self initDefaultValue];
[self addNotificationObserver];
}
return self;
}
- (instancetype)init
{
self = [super init];
if (self) {
[self initDefaultValue];
[self addNotificationObserver];
}
return self;
}
- (instancetype _Nullable )initWithCodeLength:(NSInteger)codeLength
{
self = [super init];
if (self) {
[self initDefaultValue];
[self addNotificationObserver];
self.codeLength = codeLength;
}
return self;
}
- (void)dealloc
{
[self removeNotificationObserver];
}
#pragma mark - Notification Observer
- (void)addNotificationObserver
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
}
- (void)removeNotificationObserver {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)applicationWillResignActive:(NSNotification *)notification
{
// home
}
- (void)applicationDidBecomeActive:(NSNotification *)notification
{
//
[self reloadAllCell];
}
#pragma mark - You can inherit
- (void)initDefaultValue
{
_oldLength = 0;
self.ifNeedSecurity = NO;
self.securityDelay = 0.3;
self.codeLength = 4;
self.ifNeedCursor = YES;
self.keyBoardType = UIKeyboardTypeNumberPad;
self.inputType = CRInputType_Number;
self.customInputRegex = @"";
self.backgroundColor = [UIColor clearColor];
_valueArr = [NSMutableArray new];
_ifNeedBeginEdit = NO;
}
#pragma mark - LoadAndPrepareView
- (void)loadAndPrepareView
{
[self loadAndPrepareViewWithBeginEdit:YES];
}
- (void)loadAndPrepareViewWithBeginEdit:(BOOL)beginEdit
{
if (_codeLength<=0) {
NSAssert(NO, @"请输入大于0的验证码位数");
return;
}
[self generateCellPropertyArr];
// mainCollectionView
if (!self.mainCollectionView || ![self.subviews containsObject:self.mainCollectionView]) {
[self addSubview:self.mainCollectionView];
[self.mainCollectionView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(UIEdgeInsetsZero);
}];
}
// textView
if (!self.textView || ![self.subviews containsObject:self.textView]) {
[self addSubview:self.textView];
[self.textView mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.height.mas_equalTo(0);
make.left.top.mas_equalTo(0);
}];
}
// tap
if (self.tapGR.view != self) {
[self addGestureRecognizer:self.tapGR];
}
if (![self.textView.text isEqualToString:self.customCellProperty.originValue]) {
self.textView.text = self.customCellProperty.originValue;
[self textDidChange:self.textView];
}
if (beginEdit) {
[self beginEdit];
}
}
- (void)generateCellPropertyArr
{
[self.cellPropertyArr removeAllObjects];
for (int i = 0; i < self.codeLength; i++) {
[self.cellPropertyArr addObject:[self.customCellProperty copy]];
}
}
#pragma mark - code Length
- (void)resetCodeLength:(NSInteger)codeLength beginEdit:(BOOL)beginEdit
{
if (codeLength<=0) {
NSAssert(NO, @"请输入大于0的验证码位数");
return;
}
self.codeLength = codeLength;
[self generateCellPropertyArr];
[self clearAllWithBeginEdit:beginEdit];
}
#pragma mark - Reload Input View
- (void)reloadInputString:(NSString *_Nullable)value
{
if (![self.textView.text isEqualToString:value]) {
self.textView.text = value;
[self baseTextDidChange:self.textView manualInvoke:YES];
}
}
#pragma mark - UITextFieldDelegate
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
_ifNeedBeginEdit = YES;
if (self.ifClearAllInBeginEditing && self.textValue.length == self.codeLength) {
[self clearAll];
}
if (self.textEditStatusChangeblock) {
self.textEditStatusChangeblock(CRTextEditStatus_BeginEdit);
}
[self reloadAllCell];
}
- (void)textFieldDidEndEditing:(UITextField *)textField
{
_ifNeedBeginEdit = NO;
if (self.textEditStatusChangeblock) {
self.textEditStatusChangeblock(CRTextEditStatus_EndEdit);
}
[self reloadAllCell];
}
#pragma mark - TextViewEdit
- (void)beginEdit{
if (![self.textView isFirstResponder]) {
[self.textView becomeFirstResponder];
}
}
- (void)endEdit{
if ([self.textView isFirstResponder]) {
[self.textView resignFirstResponder];
}
}
- (void)clearAll
{
[self clearAllWithBeginEdit:YES];
}
- (void)clearAllWithBeginEdit:(BOOL)beginEdit
{
_oldLength = 0;
[_valueArr removeAllObjects];
self.textView.text = @"";
[self allSecurityClose];
[self reloadAllCell];
[self triggerBlock];
if (beginEdit) {
[self beginEdit];
}
}
#pragma mark - UITextFieldDidChange
- (void)textDidChange:(UITextField *)textField {
[self baseTextDidChange:textField manualInvoke:NO];
}
/**
*
*/
- (NSString *)filterInputContent:(NSString *)inputStr {
NSMutableString *mutableStr = [[NSMutableString alloc] initWithString:inputStr];
if (self.inputType == CRInputType_Number) {
///
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"[^0-9]" options:0 error:nil];
[regex replaceMatchesInString:mutableStr options:0 range:NSMakeRange(0, [mutableStr length]) withTemplate:@""];
} else if (self.inputType == CRInputType_Normal) {
///
nil;
} else if (self.inputType == CRInputType_Regex) {
///
if (self.customInputRegex.length > 0) {
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:self.customInputRegex options:0 error:nil];
[regex replaceMatchesInString:mutableStr options:0 range:NSMakeRange(0, [mutableStr length]) withTemplate:@""];
}
}
return [mutableStr copy];
}
/**
* textDidChange
* manualInvoke
*/
- (void)baseTextDidChange:(UITextField *)textField manualInvoke:(BOOL)manualInvoke {
__weak typeof(self) weakSelf = self;
NSString *verStr = textField.text;
//
verStr = [verStr stringByReplacingOccurrencesOfString:@" " withString:@""];
verStr = [self filterInputContent:verStr];
//
if (self.textCustomProcessblock) {
verStr = self.textCustomProcessblock(verStr);
}
if (verStr.length >= _codeLength) {
verStr = [verStr substringToIndex:_codeLength];
[self endEdit];
}
textField.text = verStr;
// /
CRBoxTextChangeType boxTextChangeType = CRBoxTextChangeType_NoChange;
if (verStr.length > _oldLength) {
boxTextChangeType = CRBoxTextChangeType_Insert;
}else if (verStr.length < _oldLength){
boxTextChangeType = CRBoxTextChangeType_Delete;
}
// _valueArr
if (boxTextChangeType == CRBoxTextChangeType_Delete) {
[self setSecurityShow:NO index:_valueArr.count-1];
[_valueArr removeLastObject];
}else if (boxTextChangeType == CRBoxTextChangeType_Insert){
if (verStr.length > 0) {
if (_valueArr.count > 0) {
[self replaceValueArrToAsteriskWithIndex:_valueArr.count - 1 needEqualToCount:NO];
}
// NSString *subStr = [verStr substringWithRange:NSMakeRange(verStr.length - 1, 1)];
// [strongSelf.valueArr addObject:subStr];
[_valueArr removeAllObjects];
[verStr enumerateSubstringsInRange:NSMakeRange(0, verStr.length) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
[strongSelf.valueArr addObject:substring];
}];
if (self.ifNeedSecurity) {
if (manualInvoke) {
//
[self delaySecurityProcessAll];
}else {
//
[self delaySecurityProcessLastOne];
}
}
}
}
[self reloadAllCell];
_oldLength = verStr.length;
if (boxTextChangeType != CRBoxTextChangeType_NoChange) {
[self triggerBlock];
}
}
#pragma mark - Control security show
- (void)setSecurityShow:(BOOL)isShow index:(NSInteger)index
{
if (index < 0) {
NSAssert(NO, @"index必须大于等于0");
return;
}
CRBoxInputCellProperty *cellProperty = self.cellPropertyArr[index];
cellProperty.ifShowSecurity = isShow;
}
- (void)allSecurityClose
{
[self.cellPropertyArr enumerateObjectsUsingBlock:^(CRBoxInputCellProperty * _Nonnull cellProperty, NSUInteger idx, BOOL * _Nonnull stop) {
if (cellProperty.ifShowSecurity == YES) {
cellProperty.ifShowSecurity = NO;
}
}];
}
- (void)allSecurityOpen
{
[self.cellPropertyArr enumerateObjectsUsingBlock:^(CRBoxInputCellProperty * _Nonnull cellProperty, NSUInteger idx, BOOL * _Nonnull stop) {
if (cellProperty.ifShowSecurity == NO) {
cellProperty.ifShowSecurity = YES;
}
}];
}
#pragma mark - Trigger block
- (void)triggerBlock
{
if (self.textDidChangeblock) {
BOOL isFinished = _valueArr.count == _codeLength ? YES : NO;
self.textDidChangeblock(_textView.text, isFinished);
}
}
#pragma mark - Asterisk
/**
*
* needEqualToCount
*/
- (void)replaceValueArrToAsteriskWithIndex:(NSInteger)index needEqualToCount:(BOOL)needEqualToCount
{
if (!self.ifNeedSecurity) {
return;
}
if (needEqualToCount && index != _valueArr.count - 1) {
return;
}
[self setSecurityShow:YES index:index];
}
#pragma mark
- (void)delaySecurityProcessLastOne
{
__weak __typeof(self)weakSelf = self;
[self delayAfter:self.securityDelay dealBlock:^{
__strong __typeof(weakSelf)strongSelf = weakSelf;
if (strongSelf.valueArr.count > 0) {
[strongSelf replaceValueArrToAsteriskWithIndex:strongSelf.valueArr.count-1 needEqualToCount:YES];
dispatch_async(dispatch_get_main_queue(), ^{
[strongSelf reloadAllCell];
});
}
}];
}
#pragma mark
- (void)delaySecurityProcessAll
{
__weak __typeof(self)weakSelf = self;
[self.valueArr enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
[strongSelf replaceValueArrToAsteriskWithIndex:idx needEqualToCount:NO];
}];
[self reloadAllCell];
}
#pragma mark - DelayBlock
- (void)delayAfter:(CGFloat)delayTime dealBlock:(void (^)(void))dealBlock
{
dispatch_time_t timer = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayTime *NSEC_PER_SEC));
dispatch_after(timer, dispatch_get_main_queue(), ^{
if (dealBlock) {
dealBlock();
}
});
}
#pragma mark - UICollectionViewDataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return _codeLength;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
id tempCell = [self customCollectionView:collectionView cellForItemAtIndexPath:indexPath];
if ([tempCell isKindOfClass:[CRBoxInputCell class]]) {
CRBoxInputCell *cell = (CRBoxInputCell *)tempCell;
cell.ifNeedCursor = self.ifNeedCursor;
// CellProperty
CRBoxInputCellProperty *cellProperty = self.cellPropertyArr[indexPath.row];
cellProperty.index = indexPath.row;
NSString *currentPlaceholderStr = nil;
if (_placeholderText.length > indexPath.row) {
currentPlaceholderStr = [_placeholderText substringWithRange:NSMakeRange(indexPath.row, 1)];
cellProperty.cellPlaceholderText = currentPlaceholderStr;
}
// setOriginValue
NSUInteger focusIndex = _valueArr.count;
if (_valueArr.count > 0 && indexPath.row <= focusIndex - 1) {
[cellProperty setMyOriginValue:_valueArr[indexPath.row]];
}else{
[cellProperty setMyOriginValue:@""];
}
cell.boxInputCellProperty = cellProperty;
if (_ifNeedBeginEdit) {
cell.selected = indexPath.row == focusIndex ? YES : NO;
}else{
cell.selected = NO;
}
}
return tempCell;
}
- (void)reloadAllCell
{
[self.mainCollectionView reloadData];
NSUInteger focusIndex = _valueArr.count;
///
if (focusIndex == self.codeLength) {
[self.mainCollectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:focusIndex - 1 inSection:0] atScrollPosition:UICollectionViewScrollPositionRight animated:YES];
} else {
[self.mainCollectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:focusIndex inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES];
}
}
#pragma mark - Qiuck set
- (void)quickSetSecuritySymbol:(NSString *)securitySymbol
{
if (securitySymbol.length != 1) {
securitySymbol = @"✱";
}
self.customCellProperty.securitySymbol = securitySymbol;
}
#pragma mark - You can rewrite
- (UICollectionViewCell *)customCollectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
CRBoxInputCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:CRBoxInputCellID forIndexPath:indexPath];
return cell;
}
#pragma mark - Setter & Getter
- (UITapGestureRecognizer *)tapGR
{
if (!_tapGR) {
_tapGR = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(beginEdit)];
}
return _tapGR;
}
- (UICollectionView *)mainCollectionView
{
if (!_mainCollectionView) {
_mainCollectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:self.boxFlowLayout];
_mainCollectionView.showsHorizontalScrollIndicator = NO;
_mainCollectionView.backgroundColor = [UIColor clearColor];
_mainCollectionView.delegate = self;
_mainCollectionView.dataSource = self;
_mainCollectionView.layer.masksToBounds = YES;
_mainCollectionView.clipsToBounds = YES;
[_mainCollectionView registerClass:[CRBoxInputCell class] forCellWithReuseIdentifier:CRBoxInputCellID];
}
return _mainCollectionView;
}
- (CRBoxFlowLayout *)boxFlowLayout
{
if (!_boxFlowLayout) {
_boxFlowLayout = [CRBoxFlowLayout new];
_boxFlowLayout.itemSize = CGSizeMake(42, 47);
}
return _boxFlowLayout;
}
- (void)setCodeLength:(NSInteger)codeLength
{
_codeLength = codeLength;
self.boxFlowLayout.itemNum = codeLength;
}
- (void)setKeyBoardType:(UIKeyboardType)keyBoardType{
_keyBoardType = keyBoardType;
self.textView.keyboardType = keyBoardType;
}
- (CRBoxTextView *)textView{
if (!_textView) {
_textView = [CRBoxTextView new];
// _textView.alpha = 0.1;
// _textView.tintColor = [UIColor clearColor];
// _textView.backgroundColor = [UIColor clearColor];
// _textView.textColor = [UIColor clearColor];
_textView.delegate = self;
[_textView addTarget:self action:@selector(textDidChange:) forControlEvents:UIControlEventEditingChanged];
}
return _textView;
}
- (void)setTextContentType:(UITextContentType)textContentType
{
_textContentType = textContentType;
_textView.textContentType = textContentType;
}
- (CRBoxInputCellProperty *)customCellProperty
{
if (!_customCellProperty) {
_customCellProperty = [CRBoxInputCellProperty new];
}
return _customCellProperty;
}
- (NSMutableArray <CRBoxInputCellProperty *> *)cellPropertyArr
{
if (!_cellPropertyArr) {
_cellPropertyArr = [NSMutableArray new];
}
return _cellPropertyArr;
}
- (NSString *)textValue
{
return _textView.text;
}
@synthesize inputAccessoryView = _inputAccessoryView;
- (void)setInputAccessoryView:(UIView *)inputAccessoryView
{
_inputAccessoryView = inputAccessoryView;
self.textView.inputAccessoryView = _inputAccessoryView;
}
- (UIView *)inputAccessoryView
{
return _inputAccessoryView;
}
- (void)setIfNeedSecurity:(BOOL)ifNeedSecurity
{
_ifNeedSecurity = ifNeedSecurity;
if (ifNeedSecurity == YES) {
[self allSecurityOpen];
}else{
[self allSecurityClose];
}
dispatch_async(dispatch_get_main_queue(), ^{
[self reloadAllCell];
});
}
@end

View File

@@ -0,0 +1,16 @@
//
// CRBoxTextView.h
// CRBoxInputView
//
// Created by Chobits on 2019/1/3.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface CRBoxTextView : UITextField
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,20 @@
//
// CRBoxTextView.m
// CRBoxInputView
//
// Created by Chobits on 2019/1/3.
//
#import "CRBoxTextView.h"
@implementation CRBoxTextView
/**
* /
*/
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
return NO;
}
@end

View File

@@ -0,0 +1,52 @@
//
// CRLineView.h
// CRBoxInputView_Example
//
// Created by Chobits on 2019/6/10.
// Copyright © 2019 BearRan. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
#define CRColorMaster [UIColor colorWithRed:49/255.0 green:51/255.0 blue:64/255.0 alpha:1]
@interface CRLineView : UIView
typedef void(^CRLineViewSelectChangeBlock)(CRLineView *lineView, BOOL selected);
@property (strong, nonatomic) UIView *lineView;
@property (assign, nonatomic) BOOL selected;
/**
下划线颜色
状态:未选中状态,且没有填充文字时
默认:[UIColor colorWithRed:49/255.0 green:51/255.0 blue:64/255.0 alpha:1]
*/
@property (copy, nonatomic) UIColor *underlineColorNormal;
/**
下划线颜色
状态:选中状态时
默认:[UIColor colorWithRed:49/255.0 green:51/255.0 blue:64/255.0 alpha:1]
*/
@property (copy, nonatomic) UIColor *underlineColorSelected;
/**
下划线颜色
状态:未选中状态,且有填充文字时
默认:[UIColor colorWithRed:49/255.0 green:51/255.0 blue:64/255.0 alpha:1]
*/
@property (copy, nonatomic) UIColor *underlineColorFilled;
/**
选择状态改变时回调
*/
@property (copy, nonatomic) CRLineViewSelectChangeBlock __nullable selectChangeBlock;
- (instancetype)initWithFrame:(CGRect)frame UNAVAILABLE_ATTRIBUTE;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,60 @@
//
// CRLineView.m
// CRBoxInputView_Example
//
// Created by Chobits on 2019/6/10.
// Copyright © 2019 BearRan. All rights reserved.
//
#import "CRLineView.h"
#import <Masonry/Masonry.h>
@interface CRLineView()
{
}
@end
@implementation CRLineView
- (instancetype)init
{
self = [super init];
if (self) {
_underlineColorNormal = CRColorMaster;
_underlineColorSelected = CRColorMaster;
_underlineColorFilled = CRColorMaster;
[self createUI];
}
return self;
}
- (void)createUI
{
static CGFloat sepLineViewHeight = 4;
_lineView = [UIView new];
[self addSubview:_lineView];
_lineView.backgroundColor = _underlineColorNormal;
_lineView.layer.cornerRadius = sepLineViewHeight / 2.0;
[_lineView mas_makeConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(sepLineViewHeight);
make.left.right.bottom.offset(0);
}];
_lineView.layer.shadowColor = [[UIColor blackColor] colorWithAlphaComponent:0.2].CGColor;
_lineView.layer.shadowOpacity = 1;
_lineView.layer.shadowOffset = CGSizeMake(0, 2);
_lineView.layer.shadowRadius = 4;
}
- (void)setSelected:(BOOL)selected {
_selected = selected;
if (self.selectChangeBlock) {
__weak __typeof(self)weakSelf = self;
self.selectChangeBlock(weakSelf, selected);
}
}
@end

View File

@@ -0,0 +1,21 @@
//
// CRSecrectImageView.h
// CRBoxInputView_Example
//
// Created by Chobits on 2019/6/10.
// Copyright © 2019 BearRan. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface CRSecrectImageView : UIView
@property (strong, nonatomic) UIImage *image;
@property (assign, nonatomic) CGFloat imageWidth;
@property (assign, nonatomic) CGFloat imageHeight;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,65 @@
//
// CRSecrectImageView.m
// CRBoxInputView_Example
//
// Created by Chobits on 2019/6/10.
// Copyright © 2019 BearRan. All rights reserved.
//
#import "CRSecrectImageView.h"
#import <Masonry/Masonry.h>
@interface CRSecrectImageView()
{
UIImageView *_lockImgView;
}
@end
@implementation CRSecrectImageView
- (instancetype)init
{
self = [super init];
if (self) {
[self createUI];
}
return self;
}
- (void)createUI
{
_lockImgView = [UIImageView new];
_lockImgView.image = [UIImage imageNamed:@"smallLock"];
[self addSubview:_lockImgView];
[_lockImgView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.offset(0);
make.centerY.offset(0);
make.width.mas_equalTo(23);
make.height.mas_equalTo(27);
}];
}
#pragma mark - Setter & Getter
- (void)setImage:(UIImage *)image
{
_image = image;
_lockImgView.image = image;
}
- (void)setImageWidth:(CGFloat)imageWidth
{
_imageWidth = imageWidth;
[_lockImgView mas_updateConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo(imageWidth);
}];
}
- (void)setImageHeight:(CGFloat)imageHeight
{
_imageHeight = imageHeight;
[_lockImgView mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(imageHeight);
}];
}
@end

View File

@@ -25,6 +25,8 @@
#import "KBHUD.h"
#import "KBAlert.h"
#import "KBNetworkManager.h"
#import "KBUserSessionManager.h"
/// 项目