This commit is contained in:
2025-12-19 17:06:01 +08:00
parent e0379d3717
commit 639ce7eafd

View File

@@ -22,6 +22,10 @@ static const NSTimeInterval kKBBackspaceChunkRepeatInterval = 0.1;
static const NSTimeInterval kKBBackspaceChunkFastDelay = 1.4; static const NSTimeInterval kKBBackspaceChunkFastDelay = 1.4;
static const NSInteger kKBBackspaceChunkSize = 6; static const NSInteger kKBBackspaceChunkSize = 6;
static const NSInteger kKBBackspaceChunkSizeFast = 12; static const NSInteger kKBBackspaceChunkSizeFast = 12;
static const CGFloat kKBBackspaceClearLabelCornerRadius = 8.0;
static const CGFloat kKBBackspaceClearLabelHeight = 26.0;
static const CGFloat kKBBackspaceClearLabelPaddingX = 10.0;
static const CGFloat kKBBackspaceClearLabelTopGap = 6.0;
static const NSTimeInterval kKBPreviewShowDuration = 0.08; static const NSTimeInterval kKBPreviewShowDuration = 0.08;
static const NSTimeInterval kKBPreviewHideDuration = 0.06; static const NSTimeInterval kKBPreviewHideDuration = 0.06;
@@ -50,6 +54,10 @@ typedef NS_ENUM(NSInteger, KBBackspaceChunkClass) {
// 退使 NSTimer GCD // 退使 NSTimer GCD
@property (nonatomic, assign) BOOL backspaceHoldActive; @property (nonatomic, assign) BOOL backspaceHoldActive;
@property (nonatomic, assign) NSTimeInterval backspaceHoldStartTime; @property (nonatomic, assign) NSTimeInterval backspaceHoldStartTime;
@property (nonatomic, assign) BOOL backspaceChunkModeActive;
@property (nonatomic, assign) BOOL backspaceClearHighlighted;
@property (nonatomic, weak) KBKeyButton *backspaceButton;
@property (nonatomic, strong) UILabel *backspaceClearLabel;
@property (nonatomic, strong) KBKeyPreviewView *previewView; @property (nonatomic, strong) KBKeyPreviewView *previewView;
@end @end
@@ -110,6 +118,10 @@ typedef NS_ENUM(NSInteger, KBBackspaceChunkClass) {
#pragma mark - Public #pragma mark - Public
- (void)reloadKeys { - (void)reloadKeys {
self.backspaceButton = nil;
self.backspaceChunkModeActive = NO;
self.backspaceClearHighlighted = NO;
[self kb_hideBackspaceClearLabel];
// //
for (UIView *row in @[self.row1, self.row2, self.row3, self.row4]) { for (UIView *row in @[self.row1, self.row2, self.row3, self.row4]) {
[row.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)]; [row.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
@@ -391,8 +403,10 @@ edgeSpacerMultiplier:(CGFloat)edgeSpacerMultiplier {
[[UILongPressGestureRecognizer alloc] initWithTarget:self [[UILongPressGestureRecognizer alloc] initWithTarget:self
action:@selector(onBackspaceLongPress:)]; action:@selector(onBackspaceLongPress:)];
lp.minimumPressDuration = kKBBackspaceLongPressMinDuration; lp.minimumPressDuration = kKBBackspaceLongPressMinDuration;
lp.allowableMovement = CGFLOAT_MAX;
lp.cancelsTouchesInView = YES; // lp.cancelsTouchesInView = YES; //
[btn addGestureRecognizer:lp]; [btn addGestureRecognizer:lp];
self.backspaceButton = btn;
} }
// Shift // Shift
@@ -686,12 +700,18 @@ edgeSpacerMultiplier:(CGFloat)edgeSpacerMultiplier {
case UIGestureRecognizerStateBegan: { case UIGestureRecognizerStateBegan: {
self.backspaceHoldActive = YES; self.backspaceHoldActive = YES;
self.backspaceHoldStartTime = [NSDate date].timeIntervalSinceReferenceDate; self.backspaceHoldStartTime = [NSDate date].timeIntervalSinceReferenceDate;
self.backspaceChunkModeActive = NO;
[self kb_setBackspaceClearHighlighted:NO];
[self kb_hideBackspaceClearLabel];
[self kb_backspaceStep]; [self kb_backspaceStep];
} break; } break;
case UIGestureRecognizerStateChanged: {
[self kb_handleBackspaceLongPressChanged:gr];
} break;
case UIGestureRecognizerStateEnded: case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled: case UIGestureRecognizerStateCancelled:
case UIGestureRecognizerStateFailed: { case UIGestureRecognizerStateFailed: {
self.backspaceHoldActive = NO; [self kb_handleBackspaceLongPressEnded:gr];
} break; } break;
default: break; default: break;
} }
@@ -709,6 +729,10 @@ edgeSpacerMultiplier:(CGFloat)edgeSpacerMultiplier {
if (before.length <= 0) { self.backspaceHoldActive = NO; return; } if (before.length <= 0) { self.backspaceHoldActive = NO; return; }
NSTimeInterval elapsed = [NSDate date].timeIntervalSinceReferenceDate - self.backspaceHoldStartTime; NSTimeInterval elapsed = [NSDate date].timeIntervalSinceReferenceDate - self.backspaceHoldStartTime;
NSInteger deleteCount = [self kb_backspaceDeleteCountForContext:before elapsed:elapsed]; NSInteger deleteCount = [self kb_backspaceDeleteCountForContext:before elapsed:elapsed];
if (!self.backspaceChunkModeActive && elapsed >= kKBBackspaceChunkStartDelay) {
self.backspaceChunkModeActive = YES;
[self kb_showBackspaceClearLabelIfNeeded];
}
for (NSInteger i = 0; i < deleteCount; i++) { for (NSInteger i = 0; i < deleteCount; i++) {
[proxy deleteBackward]; [proxy deleteBackward];
} }
@@ -784,6 +808,144 @@ edgeSpacerMultiplier:(CGFloat)edgeSpacerMultiplier {
return MAX(deleteCount, 1); return MAX(deleteCount, 1);
} }
- (void)kb_handleBackspaceLongPressChanged:(UILongPressGestureRecognizer *)gr {
if (!self.backspaceHoldActive) { return; }
NSTimeInterval elapsed = [NSDate date].timeIntervalSinceReferenceDate - self.backspaceHoldStartTime;
if (elapsed < kKBBackspaceChunkStartDelay) { return; }
[self kb_showBackspaceClearLabelIfNeeded];
CGPoint point = [gr locationInView:self];
BOOL inside = [self kb_isPointInsideBackspaceClearLabel:point];
[self kb_setBackspaceClearHighlighted:inside];
}
- (void)kb_handleBackspaceLongPressEnded:(UILongPressGestureRecognizer *)gr {
BOOL shouldClear = self.backspaceClearHighlighted;
if (!shouldClear && gr) {
CGPoint point = [gr locationInView:self];
shouldClear = [self kb_isPointInsideBackspaceClearLabel:point];
}
self.backspaceHoldActive = NO;
self.backspaceChunkModeActive = NO;
[self kb_hideBackspaceClearLabel];
if (shouldClear) {
[self kb_clearAllInput];
}
}
- (void)kb_showBackspaceClearLabelIfNeeded {
if (!self.backspaceButton) { return; }
UILabel *label = self.backspaceClearLabel;
[self kb_refreshBackspaceClearLabelColors];
if (!label.superview) {
[self addSubview:label];
}
[self kb_updateBackspaceClearLabelFrame];
[self bringSubviewToFront:label];
if (label.hidden) {
label.alpha = 0.0;
label.hidden = NO;
[UIView animateWithDuration:0.12 animations:^{
label.alpha = 1.0;
}];
}
}
- (void)kb_hideBackspaceClearLabel {
if (!_backspaceClearLabel || _backspaceClearLabel.hidden) { return; }
_backspaceClearLabel.hidden = YES;
_backspaceClearLabel.alpha = 1.0;
[self kb_setBackspaceClearHighlighted:NO];
}
- (void)kb_updateBackspaceClearLabelFrame {
if (!self.backspaceButton || !self.backspaceClearLabel) { return; }
CGRect btnFrame = [self.backspaceButton convertRect:self.backspaceButton.bounds toView:self];
UILabel *label = self.backspaceClearLabel;
CGSize textSize = [label sizeThatFits:CGSizeMake(CGFLOAT_MAX, kKBBackspaceClearLabelHeight)];
CGFloat width = MAX(textSize.width + kKBBackspaceClearLabelPaddingX * 2.0, 60.0);
CGFloat height = kKBBackspaceClearLabelHeight;
CGFloat x = CGRectGetMidX(btnFrame) - width * 0.5;
CGFloat y = CGRectGetMinY(btnFrame) - height - kKBBackspaceClearLabelTopGap;
if (x < kKBRowHorizontalInset) { x = kKBRowHorizontalInset; }
if (x + width > CGRectGetWidth(self.bounds) - kKBRowHorizontalInset) {
x = CGRectGetWidth(self.bounds) - kKBRowHorizontalInset - width;
}
if (y < 0) { y = 0; }
label.frame = CGRectIntegral(CGRectMake(x, y, width, height));
}
- (BOOL)kb_isPointInsideBackspaceClearLabel:(CGPoint)point {
if (!self.backspaceClearLabel || self.backspaceClearLabel.hidden) { return NO; }
[self layoutIfNeeded];
return CGRectContainsPoint(self.backspaceClearLabel.frame, point);
}
- (void)kb_setBackspaceClearHighlighted:(BOOL)highlighted {
if (self.backspaceClearHighlighted == highlighted) { return; }
self.backspaceClearHighlighted = highlighted;
[self kb_refreshBackspaceClearLabelColors];
}
- (void)kb_refreshBackspaceClearLabelColors {
UILabel *label = self.backspaceClearLabel;
label.textColor = [KBSkinManager shared].current.keyTextColor ?: UIColor.blackColor;
label.backgroundColor = self.backspaceClearHighlighted
? [self kb_backspaceClearLabelHighlightedColor]
: [self kb_backspaceClearLabelNormalColor];
}
- (UIColor *)kb_backspaceClearLabelNormalColor {
KBSkinTheme *t = [KBSkinManager shared].current;
return t.keyHighlightBackground ?: [UIColor colorWithWhite:0.9 alpha:1.0];
}
- (UIColor *)kb_backspaceClearLabelHighlightedColor {
KBSkinTheme *t = [KBSkinManager shared].current;
return t.accentColor ?: t.keyHighlightBackground ?: [UIColor colorWithWhite:0.8 alpha:1.0];
}
- (UILabel *)backspaceClearLabel {
if (!_backspaceClearLabel) {
UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero];
label.text = @"立刻清空";
label.textAlignment = NSTextAlignmentCenter;
label.font = [UIFont systemFontOfSize:12 weight:UIFontWeightSemibold];
label.textColor = [KBSkinManager shared].current.keyTextColor ?: UIColor.blackColor;
label.backgroundColor = [self kb_backspaceClearLabelNormalColor];
label.layer.cornerRadius = kKBBackspaceClearLabelCornerRadius;
label.layer.masksToBounds = YES;
label.hidden = YES;
label.userInteractionEnabled = NO;
_backspaceClearLabel = label;
}
return _backspaceClearLabel;
}
- (void)kb_clearAllInput {
UIInputViewController *ivc = KBFindInputViewController(self);
if (!ivc) { return; }
id<UITextDocumentProxy> proxy = ivc.textDocumentProxy;
NSInteger guard = 0; //
while (guard < 10000) {
NSString *before = proxy.documentContextBeforeInput ?: @"";
NSInteger count = before.length;
if (count <= 0) { break; }
for (NSInteger i = 0; i < count; i++) {
[proxy deleteBackward];
}
guard += count;
}
}
#pragma mark - Layout
- (void)layoutSubviews {
[super layoutSubviews];
if (self.backspaceClearLabel && !self.backspaceClearLabel.hidden) {
[self kb_updateBackspaceClearLabelFrame];
}
}
#pragma mark - Lazy #pragma mark - Lazy
- (UIView *)row1 { if (!_row1) _row1 = [UIView new]; return _row1; } - (UIView *)row1 { if (!_row1) _row1 = [UIView new]; return _row1; }