2025-10-30 20:46:54 +08:00
|
|
|
|
//
|
|
|
|
|
|
// KBFullAccessGuideView.m
|
|
|
|
|
|
// CustomKeyboard
|
2025-10-30 20:53:44 +08:00
|
|
|
|
// 没有开启完全访问的提示框
|
2025-10-30 20:46:54 +08:00
|
|
|
|
|
|
|
|
|
|
#import "KBFullAccessGuideView.h"
|
|
|
|
|
|
#import "Masonry.h"
|
2025-11-04 16:37:24 +08:00
|
|
|
|
#import "KBResponderUtils.h" // 统一查找 UIInputViewController 的工具
|
2025-11-05 18:10:56 +08:00
|
|
|
|
#import "KBHUD.h"
|
|
|
|
|
|
#import "KBURLOpenBridge.h"
|
2025-10-30 20:46:54 +08:00
|
|
|
|
|
|
|
|
|
|
@interface KBFullAccessGuideView ()
|
|
|
|
|
|
@property (nonatomic, strong) UIControl *backdrop;
|
|
|
|
|
|
@property (nonatomic, strong) UIView *card;
|
2025-11-05 18:10:56 +08:00
|
|
|
|
// 预先保存当前键盘控制器,避免运行时通过响应链找不到
|
|
|
|
|
|
@property (nonatomic, weak) UIInputViewController *ivc;
|
2025-10-30 20:46:54 +08:00
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
|
|
@implementation KBFullAccessGuideView
|
|
|
|
|
|
|
|
|
|
|
|
+ (instancetype)build {
|
|
|
|
|
|
KBFullAccessGuideView *v = [[KBFullAccessGuideView alloc] initWithFrame:CGRectZero];
|
|
|
|
|
|
[v setupUI];
|
|
|
|
|
|
return v;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)setupUI {
|
|
|
|
|
|
self.backgroundColor = [UIColor clearColor];
|
|
|
|
|
|
|
|
|
|
|
|
self.backdrop = [[UIControl alloc] init];
|
|
|
|
|
|
self.backdrop.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.35];
|
|
|
|
|
|
// [self.backdrop addTarget:self action:@selector(dismiss) forControlEvents:UIControlEventTouchUpInside];
|
|
|
|
|
|
[self addSubview:self.backdrop];
|
|
|
|
|
|
[self.backdrop mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
|
|
|
make.edges.equalTo(self);
|
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
|
|
self.card = [[UIView alloc] init];
|
|
|
|
|
|
self.card.backgroundColor = [UIColor whiteColor];
|
|
|
|
|
|
self.card.layer.cornerRadius = 16;
|
|
|
|
|
|
self.card.layer.masksToBounds = YES;
|
|
|
|
|
|
[self addSubview:self.card];
|
|
|
|
|
|
[self.card mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
|
|
|
make.center.equalTo(self);
|
|
|
|
|
|
make.left.equalTo(self).offset(28);
|
|
|
|
|
|
make.right.equalTo(self).offset(-28);
|
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
|
|
UILabel *title = [UILabel new];
|
|
|
|
|
|
title.text = @"开启【允许完全访问】,体验完整功能";
|
|
|
|
|
|
title.font = [UIFont boldSystemFontOfSize:16];
|
|
|
|
|
|
title.textColor = [UIColor blackColor];
|
|
|
|
|
|
title.textAlignment = NSTextAlignmentCenter;
|
|
|
|
|
|
[self.card addSubview:title];
|
|
|
|
|
|
[title mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
|
|
|
make.top.equalTo(self.card).offset(16);
|
|
|
|
|
|
make.left.right.equalTo(self.card).insets(UIEdgeInsetsMake(0, 16, 0, 16));
|
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
|
|
// 模拟两行开关(纯展示,不真实控制)
|
|
|
|
|
|
UIView *box = [UIView new];
|
|
|
|
|
|
box.backgroundColor = [UIColor colorWithWhite:0.98 alpha:1.0];
|
|
|
|
|
|
box.layer.cornerRadius = 12;
|
|
|
|
|
|
[self.card addSubview:box];
|
|
|
|
|
|
[box mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
|
|
|
make.top.equalTo(title.mas_bottom).offset(12);
|
|
|
|
|
|
make.left.equalTo(self.card).offset(16);
|
|
|
|
|
|
make.right.equalTo(self.card).offset(-16);
|
|
|
|
|
|
make.height.mas_equalTo(100);
|
|
|
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
|
|
UILabel *row1 = [UILabel new]; row1.text = @"恋爱键盘"; row1.textColor = [UIColor blackColor];
|
|
|
|
|
|
UILabel *row2 = [UILabel new]; row2.text = @"允许完全访问"; row2.textColor = [UIColor blackColor];
|
|
|
|
|
|
[box addSubview:row1]; [box addSubview:row2];
|
|
|
|
|
|
[row1 mas_makeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(box).offset(16); make.top.equalTo(box).offset(14); }];
|
|
|
|
|
|
UIView *line = [UIView new]; line.backgroundColor = [UIColor colorWithWhite:0.9 alpha:1.0];
|
|
|
|
|
|
[box addSubview:line];
|
|
|
|
|
|
[line mas_makeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(box).offset(12); make.right.equalTo(box).offset(-12); make.centerY.equalTo(box); make.height.mas_equalTo(1); }];
|
|
|
|
|
|
[row2 mas_makeConstraints:^(MASConstraintMaker *make) { make.left.equalTo(box).offset(16); make.bottom.equalTo(box).offset(-14); }];
|
|
|
|
|
|
|
|
|
|
|
|
// 右侧绿色开关的装饰
|
|
|
|
|
|
UIView* (^switchView)(void) = ^UIView *{
|
|
|
|
|
|
UIView *sw = [UIView new];
|
|
|
|
|
|
sw.backgroundColor = [UIColor colorWithRed:0.12 green:0.75 blue:0.35 alpha:1.0];
|
|
|
|
|
|
sw.layer.cornerRadius = 15;
|
|
|
|
|
|
UIView *dot = [UIView new];
|
|
|
|
|
|
dot.backgroundColor = [UIColor whiteColor];
|
|
|
|
|
|
dot.layer.cornerRadius = 12;
|
|
|
|
|
|
[sw addSubview:dot];
|
|
|
|
|
|
[dot mas_makeConstraints:^(MASConstraintMaker *make) { make.centerY.equalTo(sw); make.right.equalTo(sw).offset(-3); make.width.height.mas_equalTo(24); }];
|
|
|
|
|
|
[sw mas_makeConstraints:^(MASConstraintMaker *make) { make.width.mas_equalTo(52); make.height.mas_equalTo(30); }];
|
|
|
|
|
|
return sw;
|
|
|
|
|
|
};
|
|
|
|
|
|
UIView *sw1 = switchView(); UIView *sw2 = switchView();
|
|
|
|
|
|
[box addSubview:sw1]; [box addSubview:sw2];
|
|
|
|
|
|
[sw1 mas_makeConstraints:^(MASConstraintMaker *make) { make.right.equalTo(box).offset(-12); make.centerY.equalTo(row1); }];
|
|
|
|
|
|
[sw2 mas_makeConstraints:^(MASConstraintMaker *make) { make.right.equalTo(box).offset(-12); make.centerY.equalTo(row2); }];
|
|
|
|
|
|
|
|
|
|
|
|
UIButton *go = [UIButton buttonWithType:UIButtonTypeSystem];
|
|
|
|
|
|
go.backgroundColor = [UIColor blackColor];
|
|
|
|
|
|
[go setTitle:@"去开启" forState:UIControlStateNormal];
|
|
|
|
|
|
[go setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
|
|
|
|
|
|
go.titleLabel.font = [UIFont boldSystemFontOfSize:18];
|
|
|
|
|
|
go.layer.cornerRadius = 12;
|
|
|
|
|
|
[go addTarget:self action:@selector(onTapGoEnable) forControlEvents:UIControlEventTouchUpInside];
|
|
|
|
|
|
[self.card addSubview:go];
|
|
|
|
|
|
[go mas_makeConstraints:^(MASConstraintMaker *make) {
|
|
|
|
|
|
make.top.equalTo(box.mas_bottom).offset(16);
|
|
|
|
|
|
make.left.equalTo(self.card).offset(16);
|
|
|
|
|
|
make.right.equalTo(self.card).offset(-16);
|
|
|
|
|
|
make.height.mas_equalTo(48);
|
|
|
|
|
|
make.bottom.equalTo(self.card).offset(-16);
|
|
|
|
|
|
}];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)presentIn:(UIView *)parent {
|
2025-11-05 18:10:56 +08:00
|
|
|
|
if (!parent) return;
|
|
|
|
|
|
UIView *container = parent; // 关键:加到键盘视图树中,而不是 window
|
2025-10-30 20:46:54 +08:00
|
|
|
|
self.frame = container.bounds;
|
|
|
|
|
|
self.alpha = 0;
|
|
|
|
|
|
[container addSubview:self];
|
|
|
|
|
|
[self mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(container); }];
|
|
|
|
|
|
[UIView animateWithDuration:0.2 animations:^{ self.alpha = 1; }];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
- (void)dismiss {
|
|
|
|
|
|
[UIView animateWithDuration:0.18 animations:^{ self.alpha = 0; } completion:^(BOOL finished) {
|
|
|
|
|
|
[self removeFromSuperview];
|
|
|
|
|
|
}];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
+ (void)showInView:(UIView *)parent {
|
|
|
|
|
|
if (!parent) return;
|
2025-11-05 18:10:56 +08:00
|
|
|
|
// 避免重复(仅在 parent 层级检查)
|
|
|
|
|
|
for (UIView *v in parent.subviews) {
|
2025-10-30 20:46:54 +08:00
|
|
|
|
if ([v isKindOfClass:[KBFullAccessGuideView class]]) return;
|
|
|
|
|
|
}
|
2025-11-05 18:10:56 +08:00
|
|
|
|
KBFullAccessGuideView *view = [KBFullAccessGuideView build];
|
|
|
|
|
|
// 预取 ivc
|
|
|
|
|
|
view.ivc = KBFindInputViewController(parent);
|
|
|
|
|
|
[view presentIn:parent];
|
2025-10-30 20:46:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
+ (void)dismissFromView:(UIView *)parent {
|
2025-11-05 18:10:56 +08:00
|
|
|
|
UIView *container = parent;
|
|
|
|
|
|
if (!container) return;
|
2025-10-30 20:46:54 +08:00
|
|
|
|
for (UIView *v in container.subviews) {
|
|
|
|
|
|
if ([v isKindOfClass:[KBFullAccessGuideView class]]) {
|
|
|
|
|
|
[(KBFullAccessGuideView *)v dismiss];
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#pragma mark - Actions
|
|
|
|
|
|
|
2025-11-04 16:37:24 +08:00
|
|
|
|
// 工具方法已提取到 KBResponderUtils.h
|
2025-11-05 18:10:56 +08:00
|
|
|
|
// 打开主 App,引导用户去系统设置开启完全访问:优先 Scheme,失败再试 UL;仍失败则提示手动路径。
|
2025-10-30 20:46:54 +08:00
|
|
|
|
- (void)onTapGoEnable {
|
2025-11-04 16:37:24 +08:00
|
|
|
|
UIInputViewController *ivc = KBFindInputViewController(self);
|
2025-10-30 20:46:54 +08:00
|
|
|
|
if (!ivc) { [self dismiss]; return; }
|
2025-11-05 18:10:56 +08:00
|
|
|
|
|
|
|
|
|
|
// 自定义 Scheme(App 里在 openURL 中转到设置页)
|
|
|
|
|
|
// 统一使用主 App 的自定义 Scheme
|
|
|
|
|
|
NSURL *scheme = [NSURL URLWithString:[NSString stringWithFormat:@"%@@//settings?src=kb_extension", KB_APP_SCHEME]];
|
|
|
|
|
|
// Universal Link(需 AASA/Associated Domains 配置且 KB_UL_BASE 与域名一致)
|
2025-10-30 20:53:44 +08:00
|
|
|
|
NSURL *ul = [NSURL URLWithString:[NSString stringWithFormat:@"%@?src=kb_extension", KB_UL_SETTINGS]];
|
2025-11-05 18:10:56 +08:00
|
|
|
|
|
|
|
|
|
|
void (^finish)(BOOL) = ^(BOOL ok){
|
|
|
|
|
|
if (ok) { [self dismiss]; }
|
|
|
|
|
|
else {
|
|
|
|
|
|
[KBHUD showInfo:@"无法自动打开,请按路径:设置→通用→键盘→键盘→恋爱键盘→允许完全访问"]; // 失败兜底提示
|
|
|
|
|
|
}
|
2025-10-30 20:46:54 +08:00
|
|
|
|
};
|
2025-11-05 18:10:56 +08:00
|
|
|
|
|
|
|
|
|
|
// 先试 Scheme(更可能被宿主允许直接拉起 App)
|
|
|
|
|
|
if (scheme) {
|
|
|
|
|
|
[ivc.extensionContext openURL:scheme completionHandler:^(BOOL ok) {
|
|
|
|
|
|
if (ok) { finish(YES); return; }
|
|
|
|
|
|
if (ul) {
|
|
|
|
|
|
[ivc.extensionContext openURL:ul completionHandler:^(BOOL ok2) {
|
|
|
|
|
|
if (ok2) { finish(YES); return; }
|
|
|
|
|
|
// 兜底:在用户点击触发的场景下,尝试通过响应链调用 openURL:
|
|
|
|
|
|
BOOL bridged = NO;
|
|
|
|
|
|
@try {
|
|
|
|
|
|
#pragma clang diagnostic push
|
|
|
|
|
|
#pragma clang diagnostic ignored "-Wunguarded-availability"
|
|
|
|
|
|
bridged = [KBURLOpenBridge openURLViaResponder:scheme from:self];
|
|
|
|
|
|
if (!bridged && ul) {
|
|
|
|
|
|
bridged = [KBURLOpenBridge openURLViaResponder:ul from:self];
|
|
|
|
|
|
}
|
|
|
|
|
|
#pragma clang diagnostic pop
|
|
|
|
|
|
} @catch (__unused NSException *e) { bridged = NO; }
|
|
|
|
|
|
finish(bridged);
|
|
|
|
|
|
}];
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 没有 UL,则直接尝试桥接 Scheme
|
|
|
|
|
|
BOOL bridged = NO;
|
|
|
|
|
|
@try {
|
|
|
|
|
|
#pragma clang diagnostic push
|
|
|
|
|
|
#pragma clang diagnostic ignored "-Wunguarded-availability"
|
|
|
|
|
|
bridged = [KBURLOpenBridge openURLViaResponder:scheme from:self];
|
|
|
|
|
|
#pragma clang diagnostic pop
|
|
|
|
|
|
} @catch (__unused NSException *e) { bridged = NO; }
|
|
|
|
|
|
finish(bridged);
|
|
|
|
|
|
}
|
|
|
|
|
|
}];
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 无 scheme 时,直接尝试 UL
|
2025-10-30 20:46:54 +08:00
|
|
|
|
if (ul) {
|
|
|
|
|
|
[ivc.extensionContext openURL:ul completionHandler:^(BOOL ok) {
|
2025-11-05 18:10:56 +08:00
|
|
|
|
if (ok) { finish(YES); return; }
|
|
|
|
|
|
BOOL bridged = NO;
|
|
|
|
|
|
@try {
|
|
|
|
|
|
#pragma clang diagnostic push
|
|
|
|
|
|
#pragma clang diagnostic ignored "-Wunguarded-availability"
|
|
|
|
|
|
bridged = [KBURLOpenBridge openURLViaResponder:ul from:self];
|
|
|
|
|
|
#pragma clang diagnostic pop
|
|
|
|
|
|
} @catch (__unused NSException *e) { bridged = NO; }
|
|
|
|
|
|
finish(bridged);
|
2025-10-30 20:46:54 +08:00
|
|
|
|
}];
|
|
|
|
|
|
} else {
|
2025-11-05 18:10:56 +08:00
|
|
|
|
finish(NO);
|
2025-10-30 20:46:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
@end
|