1
This commit is contained in:
@@ -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; }
|
||||||
|
|||||||
Reference in New Issue
Block a user