Files
keyboard/keyBoard/Class/Guard/VC/KBGuideVC.m

402 lines
16 KiB
Mathematica
Raw Normal View History

2025-10-29 16:26:57 +08:00
//
// KBGuideVC.m
// keyBoard
//
// Created by Mac on 2025/10/29.
//
#import "KBGuideVC.h"
#import "KBGuideTopCell.h"
#import "KBGuideKFCell.h"
#import "KBGuideUserCell.h"
2025-11-03 16:57:24 +08:00
#import "KBPermissionViewController.h"
#import "KBKeyboardPermissionManager.h"
2025-10-29 16:26:57 +08:00
typedef NS_ENUM(NSInteger, KBGuideItemType) {
KBGuideItemTypeTop = 0, //
KBGuideItemTypeUser, //
KBGuideItemTypeKF //
};
2025-10-29 16:44:00 +08:00
@interface KBGuideVC () <UITableViewDelegate, UITableViewDataSource, UITextFieldDelegate, UIGestureRecognizerDelegate>
2025-10-29 16:26:57 +08:00
@property (nonatomic, strong) BaseTableView *tableView; // BaseTableView
@property (nonatomic, strong) UIView *inputBar; //
@property (nonatomic, strong) UITextField *textField; //
@property (nonatomic, strong) MASConstraint *inputBarBottom;//
2025-10-29 16:44:00 +08:00
@property (nonatomic, strong) UITapGestureRecognizer *bgTap;//
2025-10-29 16:26:57 +08:00
@property (nonatomic, strong) NSMutableArray<NSDictionary *> *items; // [{type, text}]
2025-11-03 18:45:06 +08:00
///
@property (nonatomic, strong, nullable) KBPermissionViewController *permVC;
2025-11-14 16:34:01 +08:00
///
@property (nonatomic, copy, nullable) NSString *kb_lastInputModeIdentifier;
2025-10-29 16:26:57 +08:00
@end
@implementation KBGuideVC
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor colorWithWhite:0.96 alpha:1.0];
[self.view addSubview:self.tableView];
[self.view addSubview:self.inputBar];
[self.inputBar addSubview:self.textField];
[self.tableView mas_makeConstraints:^(MASConstraintMaker *make) {
2025-11-14 16:34:01 +08:00
make.left.right.equalTo(self.view);
2025-11-03 19:00:47 +08:00
make.bottom.equalTo(self.view);
2025-11-14 16:34:01 +08:00
make.top.mas_equalTo(KB_NAV_TOTAL_HEIGHT);
2025-10-29 16:26:57 +08:00
}];
[self.inputBar mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.equalTo(self.view);
make.height.mas_equalTo(52);
//
if (@available(iOS 11.0, *)) {
self.inputBarBottom = make.bottom.equalTo(self.view.mas_safeAreaLayoutGuideBottom);
} else {
self.inputBarBottom = make.bottom.equalTo(self.view);
}
}];
[self.textField mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.inputBar).offset(12);
make.right.equalTo(self.inputBar).offset(-12);
make.centerY.equalTo(self.inputBar);
make.height.mas_equalTo(36);
}];
// Top
[self.items addObject:@{ @"type": @(KBGuideItemTypeTop), @"text": @"" }];
[self.tableView reloadData];
//
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(kb_keyboardWillChange:) name:UIKeyboardWillChangeFrameNotification object:nil];
2025-10-29 16:44:00 +08:00
// cell /
self.bgTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(kb_didTapBackground)];
self.bgTap.cancelsTouchesInView = NO;
self.bgTap.delegate = self;
[self.tableView addGestureRecognizer:self.bgTap];
2025-11-03 16:57:24 +08:00
// /
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(kb_checkKeyboardPermission) name:UIApplicationDidBecomeActiveNotification object:nil];
2025-11-03 18:45:06 +08:00
2025-11-14 16:34:01 +08:00
// 使
NSArray<NSString *> *modeNotiNames = @[ @"UITextInputCurrentInputModeDidChangeNotification",
@"UITextInputCurrentInputModeDidChange" ];
for (NSString *n in modeNotiNames) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(kb_inputModeDidChange:) name:n object:nil];
}
2025-11-03 18:45:06 +08:00
//
[self kb_preparePermissionOverlayIfNeeded];
2025-11-14 16:34:01 +08:00
2025-10-29 16:26:57 +08:00
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
2025-11-03 16:57:24 +08:00
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
//
[self kb_checkKeyboardPermission];
2025-11-14 16:34:01 +08:00
// /
BOOL permissionReady = (self.permVC && self.permVC.view.hidden == YES);
if (permissionReady) {
// textInputMode
if (![self.textField isFirstResponder]) {
[self.textField becomeFirstResponder];
}
// 线
// UIKeyboardWillChangeFrame
dispatch_async(dispatch_get_main_queue(), ^{
[self kb_evaluateCurrentInputModeAndNotifyIfNeeded];
});
}
2025-11-03 16:57:24 +08:00
}
///
/// - 访 =>
/// - =>
- (void)kb_checkKeyboardPermission {
KBKeyboardPermissionManager *mgr = [KBKeyboardPermissionManager shared];
BOOL enabled = [mgr isKeyboardEnabled];
KBFARecord fa = [mgr lastKnownFullAccess];
BOOL needGuide = (!enabled) || (enabled && fa == KBFARecordDenied);
2025-11-03 18:45:06 +08:00
[self kb_preparePermissionOverlayIfNeeded];
BOOL show = needGuide;
2025-11-03 19:00:47 +08:00
// [UIView performWithoutAnimation:^{
2025-11-03 18:45:06 +08:00
self.permVC.view.hidden = !show;
2025-11-03 19:00:47 +08:00
// }];
2025-11-14 18:24:38 +08:00
//
// 便/访
if (!show) {
[self kb_tryActivateTextFieldIfReady];
}
2025-11-03 18:45:06 +08:00
}
///
- (void)kb_preparePermissionOverlayIfNeeded {
if (self.permVC) return;
KBPermissionViewController *guide = [KBPermissionViewController new];
2025-11-03 19:00:47 +08:00
// guide.modalPresentationStyle = UIModalPresentationFullScreen; // present
2025-11-03 18:45:06 +08:00
KBWeakSelf;
guide.backHandler = ^{ [weakSelf.navigationController popViewControllerAnimated:YES]; };
self.permVC = guide;
2025-11-03 19:00:47 +08:00
guide.backButton.hidden = true;
2025-11-03 18:45:06 +08:00
[self addChildViewController:guide];
[self.view addSubview:guide.view];
2025-11-03 19:00:47 +08:00
// [guide.view mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(self.view); }];
2025-11-03 18:45:06 +08:00
[guide didMoveToParentViewController:self];
guide.view.hidden = YES; //
2025-11-03 16:57:24 +08:00
}
2025-10-29 16:44:00 +08:00
- (void)kb_didTapBackground {
//
[self.view endEditing:YES];
}
2025-10-29 16:26:57 +08:00
#pragma mark - Actions
//
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
NSString *text = [textField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if (text.length == 0) { return NO; }
// 1.
[self.items addObject:@{ @"type": @(KBGuideItemTypeUser), @"text": text ?: @"" }];
// 2.
2025-11-17 20:07:39 +08:00
NSString *reply = KBLocalized(@"🎉 If you run into any other issues, tap Online Support to get help~");
2025-10-29 16:26:57 +08:00
[self.items addObject:@{ @"type": @(KBGuideItemTypeKF), @"text": reply }];
//
[self.tableView reloadData];
[self scrollToBottomAnimated:YES];
textField.text = @"";
return YES;
}
- (void)kb_keyboardWillChange:(NSNotification *)note {
NSDictionary *info = note.userInfo;
NSTimeInterval duration = [info[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
UIViewAnimationOptions curve = ([info[UIKeyboardAnimationCurveUserInfoKey] integerValue] << 16);
CGRect endFrame = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGFloat screenH = UIScreen.mainScreen.bounds.size.height;
CGFloat kbHeight = MAX(0, screenH - endFrame.origin.y);
CGFloat safeBtm = 0;
if (@available(iOS 11.0, *)) { safeBtm = self.view.safeAreaInsets.bottom; }
// = -max(kbHeight - , 0)
CGFloat offset = -MAX(kbHeight - safeBtm, 0);
self.inputBarBottom.offset = offset;
[UIView animateWithDuration:duration delay:0 options:curve animations:^{
UIEdgeInsets inset = self.tableView.contentInset;
inset.bottom = 52 + MAX(kbHeight - safeBtm, 0);
self.tableView.contentInset = inset;
2025-11-03 19:00:47 +08:00
// self.tableView.scrollIndicatorInsets = inset;
2025-10-29 16:26:57 +08:00
} completion:^(BOOL finished) {
[self scrollToBottomAnimated:YES];
2025-11-14 16:34:01 +08:00
//
[self kb_evaluateCurrentInputModeAndNotifyIfNeeded];
2025-10-29 16:26:57 +08:00
}];
}
- (void)scrollToBottomAnimated:(BOOL)animated {
if (self.items.count == 0) return;
NSInteger last = self.items.count - 1;
NSIndexPath *ip = [NSIndexPath indexPathForRow:last inSection:0];
if (last >= 0) {
[self.tableView scrollToRowAtIndexPath:ip atScrollPosition:UITableViewScrollPositionBottom animated:animated];
}
}
2025-11-14 16:34:01 +08:00
#pragma mark - Keyboard Detection
///
///
/// - `UITextField.textInputMode`
/// - KVC `identifier` bundle id
/// - `textField`
- (BOOL)kb_isMyExtensionKeyboardSelected {
UITextInputMode *mode = self.textField.textInputMode;
if (!mode) { return NO; }
NSString *identifier = nil;
@try {
// App
identifier = [mode valueForKey:@"identifier"];
} @catch (__unused NSException *e) {
identifier = nil;
}
if (![identifier isKindOfClass:[NSString class]]) { return NO; }
return [identifier rangeOfString:KB_KEYBOARD_EXTENSION_BUNDLE_ID].location != NSNotFound;
}
- (void)kb_inputModeDidChange:(NSNotification *)note {
//
[self kb_evaluateCurrentInputModeAndNotifyIfNeeded];
}
/// identifier nil
- (NSString *)kb_currentInputModeIdentifier {
UITextInputMode *mode = self.textField.textInputMode;
if (!mode) return nil;
NSString *identifier = nil;
@try { identifier = [mode valueForKey:@"identifier"]; } @catch (__unused NSException *e) { identifier = nil; }
return [identifier isKindOfClass:NSString.class] ? identifier : nil;
}
///
- (void)kb_evaluateCurrentInputModeAndNotifyIfNeeded {
//
if (![self.textField isFirstResponder]) return;
//
if (self.permVC && self.permVC.view.hidden == NO) return;
NSString *currId = [self kb_currentInputModeIdentifier];
if (currId.length == 0) return;
if ([self.kb_lastInputModeIdentifier isEqualToString:currId]) return; //
self.kb_lastInputModeIdentifier = currId;
BOOL isMine = [currId rangeOfString:KB_KEYBOARD_EXTENSION_BUNDLE_ID].location != NSNotFound;
2025-11-17 20:07:39 +08:00
[KBHUD showInfo:(isMine ? KBLocalized(@"是自己的键盘") : KBLocalized(@"❎不是自己的键盘"))];
2025-11-14 16:34:01 +08:00
}
2025-11-14 18:24:38 +08:00
///
- (void)kb_tryActivateTextFieldIfReady {
//
if (self.permVC && self.permVC.view.hidden == NO) return;
//
if (!self.view.window) return;
//
if (![self.textField isFirstResponder]) {
[self.textField becomeFirstResponder];
}
dispatch_async(dispatch_get_main_queue(), ^{
[self kb_evaluateCurrentInputModeAndNotifyIfNeeded];
});
}
2025-10-29 16:26:57 +08:00
#pragma mark - UITableView
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.items.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSDictionary *it = self.items[indexPath.row];
KBGuideItemType type = [it[@"type"] integerValue];
NSString *text = it[@"text"] ?: @"";
if (type == KBGuideItemTypeTop) {
KBGuideTopCell *cell = [tableView dequeueReusableCellWithIdentifier:[KBGuideTopCell reuseId]];
if (!cell) cell = [[KBGuideTopCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:[KBGuideTopCell reuseId]];
return cell;
} else if (type == KBGuideItemTypeUser) {
KBGuideUserCell *cell = [tableView dequeueReusableCellWithIdentifier:[KBGuideUserCell reuseId]];
if (!cell) cell = [[KBGuideUserCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:[KBGuideUserCell reuseId]];
[cell configText:text];
return cell;
} else {
KBGuideKFCell *cell = [tableView dequeueReusableCellWithIdentifier:[KBGuideKFCell reuseId]];
if (!cell) cell = [[KBGuideKFCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:[KBGuideKFCell reuseId]];
[cell configText:text];
return cell;
}
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 100;
}
#pragma mark - Lazy
- (BaseTableView *)tableView {
if (!_tableView) {
_tableView = [[BaseTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
_tableView.delegate = self;
_tableView.dataSource = self;
_tableView.separatorStyle = UITableViewCellSeparatorStyleNone; // 线
_tableView.backgroundColor = [UIColor colorWithWhite:0.96 alpha:1.0];
_tableView.rowHeight = UITableViewAutomaticDimension;
_tableView.estimatedRowHeight = 120; //
_tableView.contentInset = UIEdgeInsetsMake(0, 0, 52 + KB_SafeAreaBottom(), 0);
_tableView.scrollIndicatorInsets = _tableView.contentInset;
}
return _tableView;
}
- (UIView *)inputBar {
if (!_inputBar) {
_inputBar = [UIView new];
_inputBar.backgroundColor = [UIColor colorWithWhite:0.96 alpha:1.0];
UIView *bg = [UIView new];
bg.backgroundColor = [UIColor whiteColor];
bg.layer.cornerRadius = 10; bg.layer.masksToBounds = YES;
[_inputBar addSubview:bg];
[bg mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(_inputBar).offset(12);
make.right.equalTo(_inputBar).offset(-12);
make.top.equalTo(_inputBar).offset(8);
make.bottom.equalTo(_inputBar).offset(-8);
}];
}
return _inputBar;
}
- (UITextField *)textField {
if (!_textField) {
_textField = [UITextField new];
_textField.delegate = self;
_textField.returnKeyType = UIReturnKeySend; //
_textField.font = [UIFont systemFontOfSize:15];
2025-11-17 20:07:39 +08:00
_textField.placeholder = KBLocalized(@"After pasting the conversation in the keyboard, choose a reply style");
2025-10-29 16:26:57 +08:00
_textField.backgroundColor = [UIColor whiteColor];
_textField.layer.cornerRadius = 10; _textField.layer.masksToBounds = YES;
UIView *pad = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 36)];
_textField.leftView = pad; _textField.leftViewMode = UITextFieldViewModeAlways;
}
return _textField;
}
- (NSMutableArray<NSDictionary *> *)items {
if (!_items) { _items = @[].mutableCopy; }
return _items;
}
2025-10-29 16:44:00 +08:00
#pragma mark - UIGestureRecognizerDelegate
//
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
if (gestureRecognizer == self.bgTap) {
if ([touch.view isDescendantOfView:self.inputBar]) {
return NO;
}
}
return YES;
}
// /
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
2025-10-29 16:26:57 +08:00
@end