添加长按多文字删除
This commit is contained in:
@@ -17,6 +17,11 @@
|
|||||||
|
|
||||||
static const NSTimeInterval kKBBackspaceLongPressMinDuration = 0.35;
|
static const NSTimeInterval kKBBackspaceLongPressMinDuration = 0.35;
|
||||||
static const NSTimeInterval kKBBackspaceRepeatInterval = 0.06;
|
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 kKBPreviewShowDuration = 0.08;
|
||||||
static const NSTimeInterval kKBPreviewHideDuration = 0.06;
|
static const NSTimeInterval kKBPreviewHideDuration = 0.06;
|
||||||
@@ -28,6 +33,14 @@ static const CGFloat kKBSpaceWidthMultiplier = 3.0;
|
|||||||
// 第二行字母行的左右占位比例(用于居中)
|
// 第二行字母行的左右占位比例(用于居中)
|
||||||
static const CGFloat kKBLettersRow2EdgeSpacerMultiplier = 0.5;
|
static const CGFloat kKBLettersRow2EdgeSpacerMultiplier = 0.5;
|
||||||
|
|
||||||
|
typedef NS_ENUM(NSInteger, KBBackspaceChunkClass) {
|
||||||
|
KBBackspaceChunkClassUnknown = 0,
|
||||||
|
KBBackspaceChunkClassWhitespace,
|
||||||
|
KBBackspaceChunkClassASCIIWord,
|
||||||
|
KBBackspaceChunkClassPunctuation,
|
||||||
|
KBBackspaceChunkClassOther
|
||||||
|
};
|
||||||
|
|
||||||
@interface KBKeyboardView ()
|
@interface KBKeyboardView ()
|
||||||
@property (nonatomic, strong) UIView *row1;
|
@property (nonatomic, strong) UIView *row1;
|
||||||
@property (nonatomic, strong) UIView *row2;
|
@property (nonatomic, strong) UIView *row2;
|
||||||
@@ -36,6 +49,7 @@ static const CGFloat kKBLettersRow2EdgeSpacerMultiplier = 0.5;
|
|||||||
@property (nonatomic, strong) NSArray<NSArray<KBKey *> *> *keysForRows;
|
@property (nonatomic, strong) NSArray<NSArray<KBKey *> *> *keysForRows;
|
||||||
// 长按退格的一次次删除控制标记(不使用 NSTimer,仅用 GCD 递归调度)
|
// 长按退格的一次次删除控制标记(不使用 NSTimer,仅用 GCD 递归调度)
|
||||||
@property (nonatomic, assign) BOOL backspaceHoldActive;
|
@property (nonatomic, assign) BOOL backspaceHoldActive;
|
||||||
|
@property (nonatomic, assign) NSTimeInterval backspaceHoldStartTime;
|
||||||
@property (nonatomic, strong) KBKeyPreviewView *previewView;
|
@property (nonatomic, strong) KBKeyPreviewView *previewView;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@@ -666,11 +680,12 @@ edgeSpacerMultiplier:(CGFloat)edgeSpacerMultiplier {
|
|||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 长按退格:按住时以小间隔逐个删除;松手停止。(不使用 NSTimer/DisplayLink)
|
// 长按退格:先连续单删,稍后切换为按段删除;松手停止。(不使用 NSTimer/DisplayLink)
|
||||||
- (void)onBackspaceLongPress:(UILongPressGestureRecognizer *)gr {
|
- (void)onBackspaceLongPress:(UILongPressGestureRecognizer *)gr {
|
||||||
switch (gr.state) {
|
switch (gr.state) {
|
||||||
case UIGestureRecognizerStateBegan: {
|
case UIGestureRecognizerStateBegan: {
|
||||||
self.backspaceHoldActive = YES;
|
self.backspaceHoldActive = YES;
|
||||||
|
self.backspaceHoldStartTime = [NSDate date].timeIntervalSinceReferenceDate;
|
||||||
[self kb_backspaceStep];
|
[self kb_backspaceStep];
|
||||||
} break;
|
} break;
|
||||||
case UIGestureRecognizerStateEnded:
|
case UIGestureRecognizerStateEnded:
|
||||||
@@ -692,17 +707,83 @@ edgeSpacerMultiplier:(CGFloat)edgeSpacerMultiplier {
|
|||||||
id<UITextDocumentProxy> proxy = ivc.textDocumentProxy;
|
id<UITextDocumentProxy> proxy = ivc.textDocumentProxy;
|
||||||
NSString *before = proxy.documentContextBeforeInput ?: @"";
|
NSString *before = proxy.documentContextBeforeInput ?: @"";
|
||||||
if (before.length <= 0) { self.backspaceHoldActive = NO; return; }
|
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;
|
__weak typeof(self) weakSelf = self;
|
||||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
|
||||||
(int64_t)(kKBBackspaceRepeatInterval * NSEC_PER_SEC)),
|
(int64_t)(interval * NSEC_PER_SEC)),
|
||||||
dispatch_get_main_queue(), ^{
|
dispatch_get_main_queue(), ^{
|
||||||
__strong typeof(weakSelf) selfStrong = weakSelf;
|
__strong typeof(weakSelf) selfStrong = weakSelf;
|
||||||
[selfStrong kb_backspaceStep];
|
[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
|
#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