From e0379d37173a70b8a8b34d15cb82765c94b1ac14 Mon Sep 17 00:00:00 2001 From: CodeST <694468528@qq.com> Date: Fri, 19 Dec 2025 16:24:47 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=95=BF=E6=8C=89=E5=A4=9A?= =?UTF-8?q?=E6=96=87=E5=AD=97=E5=88=A0=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CustomKeyboard/View/KBKeyboardView.m | 87 +++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 3 deletions(-) diff --git a/CustomKeyboard/View/KBKeyboardView.m b/CustomKeyboard/View/KBKeyboardView.m index 3fd6cd2..e4c8c84 100644 --- a/CustomKeyboard/View/KBKeyboardView.m +++ b/CustomKeyboard/View/KBKeyboardView.m @@ -17,6 +17,11 @@ static const NSTimeInterval kKBBackspaceLongPressMinDuration = 0.35; static const NSTimeInterval kKBBackspaceRepeatInterval = 0.06; +static const NSTimeInterval kKBBackspaceChunkStartDelay = 0.7; +static const NSTimeInterval kKBBackspaceChunkRepeatInterval = 0.1; +static const NSTimeInterval kKBBackspaceChunkFastDelay = 1.4; +static const NSInteger kKBBackspaceChunkSize = 6; +static const NSInteger kKBBackspaceChunkSizeFast = 12; static const NSTimeInterval kKBPreviewShowDuration = 0.08; static const NSTimeInterval kKBPreviewHideDuration = 0.06; @@ -28,6 +33,14 @@ static const CGFloat kKBSpaceWidthMultiplier = 3.0; // 第二行字母行的左右占位比例(用于居中) static const CGFloat kKBLettersRow2EdgeSpacerMultiplier = 0.5; +typedef NS_ENUM(NSInteger, KBBackspaceChunkClass) { + KBBackspaceChunkClassUnknown = 0, + KBBackspaceChunkClassWhitespace, + KBBackspaceChunkClassASCIIWord, + KBBackspaceChunkClassPunctuation, + KBBackspaceChunkClassOther +}; + @interface KBKeyboardView () @property (nonatomic, strong) UIView *row1; @property (nonatomic, strong) UIView *row2; @@ -36,6 +49,7 @@ static const CGFloat kKBLettersRow2EdgeSpacerMultiplier = 0.5; @property (nonatomic, strong) NSArray *> *keysForRows; // 长按退格的一次次删除控制标记(不使用 NSTimer,仅用 GCD 递归调度) @property (nonatomic, assign) BOOL backspaceHoldActive; +@property (nonatomic, assign) NSTimeInterval backspaceHoldStartTime; @property (nonatomic, strong) KBKeyPreviewView *previewView; @end @@ -666,11 +680,12 @@ edgeSpacerMultiplier:(CGFloat)edgeSpacerMultiplier { }]; } -// 长按退格:按住时以小间隔逐个删除;松手停止。(不使用 NSTimer/DisplayLink) +// 长按退格:先连续单删,稍后切换为按段删除;松手停止。(不使用 NSTimer/DisplayLink) - (void)onBackspaceLongPress:(UILongPressGestureRecognizer *)gr { switch (gr.state) { case UIGestureRecognizerStateBegan: { self.backspaceHoldActive = YES; + self.backspaceHoldStartTime = [NSDate date].timeIntervalSinceReferenceDate; [self kb_backspaceStep]; } break; case UIGestureRecognizerStateEnded: @@ -692,17 +707,83 @@ edgeSpacerMultiplier:(CGFloat)edgeSpacerMultiplier { id proxy = ivc.textDocumentProxy; NSString *before = proxy.documentContextBeforeInput ?: @""; if (before.length <= 0) { self.backspaceHoldActive = NO; return; } - [proxy deleteBackward]; // 每次仅删 1 个 + NSTimeInterval elapsed = [NSDate date].timeIntervalSinceReferenceDate - self.backspaceHoldStartTime; + NSInteger deleteCount = [self kb_backspaceDeleteCountForContext:before elapsed:elapsed]; + for (NSInteger i = 0; i < deleteCount; i++) { + [proxy deleteBackward]; + } + NSTimeInterval interval = [self kb_backspaceRepeatIntervalForElapsed:elapsed]; __weak typeof(self) weakSelf = self; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, - (int64_t)(kKBBackspaceRepeatInterval * NSEC_PER_SEC)), + (int64_t)(interval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ __strong typeof(weakSelf) selfStrong = weakSelf; [selfStrong kb_backspaceStep]; }); } +- (NSTimeInterval)kb_backspaceRepeatIntervalForElapsed:(NSTimeInterval)elapsed { + if (elapsed >= kKBBackspaceChunkStartDelay) { + return kKBBackspaceChunkRepeatInterval; + } + return kKBBackspaceRepeatInterval; +} + +- (NSInteger)kb_backspaceDeleteCountForContext:(NSString *)context elapsed:(NSTimeInterval)elapsed { + if (elapsed < kKBBackspaceChunkStartDelay) { + return 1; + } + NSInteger maxCount = (elapsed >= kKBBackspaceChunkFastDelay) + ? kKBBackspaceChunkSizeFast : kKBBackspaceChunkSize; + return [self kb_backspaceChunkDeleteCountForContext:context maxCount:maxCount]; +} + +- (NSInteger)kb_backspaceChunkDeleteCountForContext:(NSString *)context maxCount:(NSInteger)maxCount { + if (context.length == 0) { return 1; } + + static NSCharacterSet *whitespaceSet = nil; + static NSCharacterSet *asciiWordSet = nil; + static NSCharacterSet *punctuationSet = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + whitespaceSet = [NSCharacterSet whitespaceAndNewlineCharacterSet]; + asciiWordSet = [NSCharacterSet characterSetWithCharactersInString: + @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"]; + punctuationSet = [NSCharacterSet punctuationCharacterSet]; + }); + + __block NSInteger deleteCount = 0; + __block KBBackspaceChunkClass chunkClass = KBBackspaceChunkClassUnknown; + [context enumerateSubstringsInRange:NSMakeRange(0, context.length) + options:NSStringEnumerationByComposedCharacterSequences | NSStringEnumerationReverse + usingBlock:^(NSString *substring, __unused NSRange substringRange, __unused NSRange enclosingRange, BOOL *stop) { + if (substring.length == 0) { return; } + KBBackspaceChunkClass currentClass = KBBackspaceChunkClassOther; + if ([substring rangeOfCharacterFromSet:whitespaceSet].location != NSNotFound) { + currentClass = KBBackspaceChunkClassWhitespace; + } else if ([substring rangeOfCharacterFromSet:asciiWordSet].location != NSNotFound) { + currentClass = KBBackspaceChunkClassASCIIWord; + } else if ([substring rangeOfCharacterFromSet:punctuationSet].location != NSNotFound) { + currentClass = KBBackspaceChunkClassPunctuation; + } + + if (chunkClass == KBBackspaceChunkClassUnknown) { + chunkClass = currentClass; + } else if (chunkClass != currentClass) { + *stop = YES; + return; + } + + deleteCount += 1; + if (deleteCount >= maxCount) { + *stop = YES; + } + }]; + + return MAX(deleteCount, 1); +} + #pragma mark - Lazy - (UIView *)row1 { if (!_row1) _row1 = [UIView new]; return _row1; }