// // KBKeyboardInteractionHandler.m // CustomKeyboard // #import "KBKeyboardInteractionHandler.h" #import "KBKeyButton.h" #import "KBKey.h" #import "KBKeyPreviewView.h" static const NSTimeInterval kKBPreviewShowDuration = 0.08; static const NSTimeInterval kKBPreviewHideDuration = 0.06; @interface KBKeyboardInteractionHandler () @property (nonatomic, strong) KBKeyPreviewView *previewView; @end @implementation KBKeyboardInteractionHandler - (UIView *)resolveHitView:(UIView *)hitView point:(CGPoint)point container:(UIView *)container rowViews:(NSArray *)rowViews { if ([hitView isKindOfClass:[KBKeyButton class]]) { return hitView; } if ([self isHitInsideKeyRows:hitView rowViews:rowViews]) { KBKeyButton *btn = [self nearestKeyButtonForPoint:point container:container rowViews:rowViews]; if (btn) { return btn; } } return hitView; } - (NSArray *)collectKeyButtonsInView:(UIView *)view { if (!view) { return @[]; } NSMutableArray *buttons = [NSMutableArray array]; [self collectKeyButtonsInView:view into:buttons]; return buttons.copy; } - (void)showPreviewForButton:(KBKeyButton *)button inContainer:(UIView *)container { if (!button || !container) { return; } KBKey *key = button.key; if (key.type != KBKeyTypeCharacter) return; if (!self.previewView) { self.previewView = [[KBKeyPreviewView alloc] initWithFrame:CGRectZero]; self.previewView.hidden = YES; [container addSubview:self.previewView]; } else if (self.previewView.superview != container) { [container addSubview:self.previewView]; } [self.previewView configureWithKey:key icon:button.iconView.image]; // 计算预览视图位置:在按钮上方稍微偏上 CGRect btnFrameInSelf = [button convertRect:button.bounds toView:container]; CGFloat previewWidth = 42; CGFloat previewHeight = CGRectGetHeight(btnFrameInSelf) * 1.2; CGFloat centerX = CGRectGetMidX(btnFrameInSelf); CGFloat centerY = CGRectGetMinY(btnFrameInSelf) - previewHeight * 0.6; self.previewView.frame = CGRectMake(0, 0, previewWidth, previewHeight); self.previewView.center = CGPointMake(centerX, centerY); self.previewView.alpha = 0.0; self.previewView.hidden = NO; [UIView animateWithDuration:kKBPreviewShowDuration delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseOut animations:^{ self.previewView.alpha = 1.0; } completion:nil]; } - (void)hidePreview { if (!self.previewView || self.previewView.isHidden) return; [UIView animateWithDuration:kKBPreviewHideDuration delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseIn animations:^{ self.previewView.alpha = 0.0; } completion:^(BOOL finished) { self.previewView.hidden = YES; }]; } - (void)bringPreviewToFrontIfNeededInContainer:(UIView *)container { if (!container) { return; } if (self.previewView && self.previewView.superview == container) { [container bringSubviewToFront:self.previewView]; } } #pragma mark - Private - (BOOL)isHitInsideKeyRows:(UIView *)hitView rowViews:(NSArray *)rowViews { if (!hitView) { return NO; } if ([rowViews containsObject:hitView]) { return YES; } for (UIView *row in rowViews) { if ([hitView isDescendantOfView:row]) { return YES; } } return NO; } - (KBKeyButton *)nearestKeyButtonForPoint:(CGPoint)point container:(UIView *)container rowViews:(NSArray *)rowViews { if (!container) { return nil; } KBKeyButton *best = nil; CGFloat bestDistance = CGFLOAT_MAX; UIView *targetRow = nil; for (UIView *row in rowViews) { CGRect rowFrame = [container convertRect:row.bounds fromView:row]; if (CGRectContainsPoint(rowFrame, point)) { targetRow = row; break; } } NSArray *candidateRows = targetRow ? @[targetRow] : rowViews; for (UIView *row in candidateRows) { NSArray *buttons = [self collectKeyButtonsInView:row]; for (KBKeyButton *btn in buttons) { CGRect frame = [container convertRect:btn.frame fromView:btn.superview]; CGFloat dx = point.x - CGRectGetMidX(frame); CGFloat dy = point.y - CGRectGetMidY(frame); CGFloat dist = (dx * dx) + (dy * dy); if (dist < bestDistance) { bestDistance = dist; best = btn; } } } return best; } - (void)collectKeyButtonsInView:(UIView *)view into:(NSMutableArray *)buttons { for (UIView *sub in view.subviews) { if ([sub isKindOfClass:[KBKeyButton class]]) { [buttons addObject:(KBKeyButton *)sub]; continue; } if (sub.subviews.count > 0) { [self collectKeyButtonsInView:sub into:buttons]; } } } @end