// // KBFullAccessGuideView.m // CustomKeyboard // 没有开启完全访问的提示框 #import "KBFullAccessGuideView.h" #import "Masonry.h" #import "KBResponderUtils.h" // 统一查找 UIInputViewController 的工具 #import "KBHUD.h" #import "KBURLOpenBridge.h" @interface KBFullAccessGuideView () @property (nonatomic, strong) UIControl *backdrop; @property (nonatomic, strong) UIView *card; // 预先保存当前键盘控制器,避免运行时通过响应链找不到 @property (nonatomic, weak) UIInputViewController *ivc; @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 = AppName; 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 { if (!parent) return; UIView *container = parent; // 关键:加到键盘视图树中,而不是 window 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; // 避免重复(仅在 parent 层级检查) for (UIView *v in parent.subviews) { if ([v isKindOfClass:[KBFullAccessGuideView class]]) return; } KBFullAccessGuideView *view = [KBFullAccessGuideView build]; // 预取 ivc view.ivc = KBFindInputViewController(parent); [view presentIn:parent]; } + (void)dismissFromView:(UIView *)parent { UIView *container = parent; if (!container) return; for (UIView *v in container.subviews) { if ([v isKindOfClass:[KBFullAccessGuideView class]]) { [(KBFullAccessGuideView *)v dismiss]; break; } } } #pragma mark - Actions // 工具方法已提取到 KBResponderUtils.h // 打开主 App,引导用户去系统设置开启完全访问:优先 Scheme,失败再试 UL;仍失败则提示手动路径。 - (void)onTapGoEnable { UIInputViewController *ivc = KBFindInputViewController(self); if (!ivc) { [self dismiss]; return; } // 自定义 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 与域名一致) NSURL *ul = [NSURL URLWithString:[NSString stringWithFormat:@"%@?src=kb_extension", KB_UL_SETTINGS]]; void (^finish)(BOOL) = ^(BOOL ok){ if (ok) { [self dismiss]; } else { NSString *showInfo = [NSString stringWithFormat:@"请按路径:设置→通用→键盘→键盘→%@→允许完全访问",AppName]; [KBHUD showInfo:showInfo]; } }; // 先试 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 if (ul) { [ivc.extensionContext openURL:ul completionHandler:^(BOOL ok) { 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); }]; } else { finish(NO); } } @end