157 lines
5.4 KiB
Mathematica
157 lines
5.4 KiB
Mathematica
|
|
//
|
|||
|
|
// 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<UIView *> *)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<KBKeyButton *> *)collectKeyButtonsInView:(UIView *)view {
|
|||
|
|
if (!view) { return @[]; }
|
|||
|
|
NSMutableArray<KBKeyButton *> *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<UIView *> *)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<UIView *> *)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<UIView *> *candidateRows = targetRow ? @[targetRow] : rowViews;
|
|||
|
|
for (UIView *row in candidateRows) {
|
|||
|
|
NSArray<KBKeyButton *> *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<KBKeyButton *> *)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
|