Files
keyboard/CustomKeyboard/Utils/KBBackspaceLongPressHandler.m

609 lines
25 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// KBBackspaceLongPressHandler.m
// CustomKeyboard
//
#import "KBBackspaceLongPressHandler.h"
#import "KBResponderUtils.h"
#import "KBSkinManager.h"
#import "KBBackspaceUndoManager.h"
#import "KBInputBufferManager.h"
static const NSTimeInterval kKBBackspaceLongPressMinDuration = 0.35;
static const NSTimeInterval kKBBackspaceRepeatInterval = 0.06;
static const NSTimeInterval kKBBackspaceChunkStartDelay = 1.0;
static const NSTimeInterval kKBBackspaceChunkRepeatInterval = 0.1;
static const NSTimeInterval kKBBackspaceChunkFastDelay = 1.4;
static const NSInteger kKBBackspaceChunkSize = 6;
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 CGFloat kKBBackspaceClearLabelHorizontalInset = 6.0;
static const NSTimeInterval kKBBackspaceClearBatchInterval = 0.02;
static const NSInteger kKBBackspaceClearMaxDeletes = 10000;
static const NSInteger kKBBackspaceClearEmptyContextMaxRounds = 40;
static const NSInteger kKBBackspaceClearMaxStep = 80;
static const NSInteger kKBBackspaceClearDeletesPerTick = 10;
typedef NS_ENUM(NSInteger, KBBackspaceChunkClass) {
KBBackspaceChunkClassUnknown = 0,
KBBackspaceChunkClassWhitespace,
KBBackspaceChunkClassASCIIWord,
KBBackspaceChunkClassPunctuation,
KBBackspaceChunkClassOther
};
typedef NS_ENUM(NSInteger, KBClearPhase) {
KBClearPhaseSkipWhitespace = 0,
KBClearPhaseSkipTrailingBoundary,
KBClearPhaseDeleteUntilBoundary
};
@interface KBBackspaceLongPressHandler ()
@property (nonatomic, weak) UIView *containerView;
@property (nonatomic, weak) UIView *backspaceButton;
@property (nonatomic, strong) UILongPressGestureRecognizer *longPress;
@property (nonatomic, assign) BOOL showClearLabelEnabled;
@property (nonatomic, assign) BOOL backspaceHoldActive;
@property (nonatomic, assign) NSTimeInterval backspaceHoldStartTime;
@property (nonatomic, assign) BOOL backspaceChunkModeActive;
@property (nonatomic, assign) BOOL backspaceClearHighlighted;
@property (nonatomic, assign) NSUInteger backspaceHoldToken;
@property (nonatomic, assign) BOOL backspaceHasLastTouchPoint;
@property (nonatomic, assign) CGPoint backspaceLastTouchPointInSelf;
@property (nonatomic, assign) NSUInteger backspaceClearToken;
@property (nonatomic, strong) UILabel *backspaceClearLabel;
@property (nonatomic, copy) NSString *pendingClearBefore;
@property (nonatomic, copy) NSString *pendingClearAfter;
@property (nonatomic, assign) KBClearPhase backspaceClearPhase;
@end
@implementation KBBackspaceLongPressHandler
- (instancetype)initWithContainerView:(UIView *)containerView {
if (self = [super init]) {
_containerView = containerView;
_backspaceClearPhase = KBClearPhaseSkipWhitespace;
}
return self;
}
- (void)bindDeleteButton:(UIView *)button showClearLabel:(BOOL)showClearLabel {
if (self.backspaceButton == button) { return; }
if (self.longPress && self.backspaceButton) {
[self.backspaceButton removeGestureRecognizer:self.longPress];
}
self.backspaceButton = button;
self.showClearLabelEnabled = showClearLabel;
self.backspaceHoldActive = NO;
self.backspaceChunkModeActive = NO;
self.backspaceClearHighlighted = NO;
self.backspaceHasLastTouchPoint = NO;
self.backspaceHoldToken += 1;
[self kb_hideBackspaceClearLabel];
self.pendingClearBefore = nil;
self.pendingClearAfter = nil;
if (!button) { return; }
self.longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self
action:@selector(onBackspaceLongPress:)];
self.longPress.minimumPressDuration = kKBBackspaceLongPressMinDuration;
self.longPress.allowableMovement = CGFLOAT_MAX;
self.longPress.cancelsTouchesInView = YES;
[button addGestureRecognizer:self.longPress];
}
- (void)performClearAction {
[self kb_clearAllInput];
}
#pragma mark - Long Press
- (void)onBackspaceLongPress:(UILongPressGestureRecognizer *)gr {
UIView *hostView = [self kb_hostView];
if (!hostView) { return; }
if (gr) {
self.backspaceLastTouchPointInSelf = [gr locationInView:hostView];
self.backspaceHasLastTouchPoint = YES;
}
switch (gr.state) {
case UIGestureRecognizerStateBegan: {
UIResponder *start = (UIResponder *)([self kb_hostView] ?: self.backspaceButton);
UIInputViewController *ivc = KBFindInputViewController(start);
if (ivc) {
id<UITextDocumentProxy> proxy = ivc.textDocumentProxy;
[[KBInputBufferManager shared] refreshFromProxyIfPossible:proxy];
[[KBInputBufferManager shared] prepareSnapshotForDeleteWithContextBefore:proxy.documentContextBeforeInput
after:proxy.documentContextAfterInput];
}
if (self.showClearLabelEnabled) {
[self kb_capturePendingClearSnapshotIfNeeded];
[[KBInputBufferManager shared] beginPendingClearSnapshot];
}
self.backspaceHoldToken += 1;
NSUInteger token = self.backspaceHoldToken;
self.backspaceHoldActive = YES;
self.backspaceHoldStartTime = [NSDate date].timeIntervalSinceReferenceDate;
self.backspaceChunkModeActive = NO;
[self kb_setBackspaceClearHighlighted:NO];
[self kb_hideBackspaceClearLabel];
if (self.showClearLabelEnabled) {
[self kb_showBackspaceClearLabelIfNeeded];
}
[self kb_backspaceStepForToken:token];
} break;
case UIGestureRecognizerStateChanged: {
[self kb_handleBackspaceLongPressChanged:gr];
} break;
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled:
case UIGestureRecognizerStateFailed: {
[self kb_handleBackspaceLongPressEnded:gr];
} break;
default: break;
}
}
#pragma mark - Delete Steps
- (void)kb_backspaceStepForToken:(NSUInteger)token {
if (!self.backspaceHoldActive) { return; }
if (token != self.backspaceHoldToken) { return; }
UIResponder *start = (UIResponder *)([self kb_hostView] ?: self.backspaceButton);
UIInputViewController *ivc = KBFindInputViewController(start);
if (!ivc) { self.backspaceHoldActive = NO; return; }
id<UITextDocumentProxy> proxy = ivc.textDocumentProxy;
NSString *before = proxy.documentContextBeforeInput ?: @"";
if (before.length == 0) { before = [KBInputBufferManager shared].liveText ?: @""; }
NSTimeInterval elapsed = [NSDate date].timeIntervalSinceReferenceDate - self.backspaceHoldStartTime;
NSInteger deleteCount = 1;
if (before.length > 0) {
deleteCount = [self kb_backspaceDeleteCountForContext:before elapsed:elapsed];
}
if (!self.backspaceChunkModeActive && elapsed >= kKBBackspaceChunkStartDelay) {
self.backspaceChunkModeActive = YES;
if (self.showClearLabelEnabled) {
[self kb_showBackspaceClearLabelIfNeeded];
}
}
[[KBBackspaceUndoManager shared] captureAndDeleteBackwardFromProxy:proxy count:(NSUInteger)deleteCount];
[[KBInputBufferManager shared] applyHoldDeleteCount:(NSUInteger)deleteCount];
NSTimeInterval interval = [self kb_backspaceRepeatIntervalForElapsed:elapsed];
__weak typeof(self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
(int64_t)(interval * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
__strong typeof(weakSelf) selfStrong = weakSelf;
[selfStrong kb_backspaceStepForToken:token];
});
}
- (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);
}
- (NSInteger)kb_clearDeleteCountForContext:(NSString *)context
hitBoundary:(BOOL *)hitBoundary {
if (context.length == 0) {
if (hitBoundary) { *hitBoundary = NO; }
return 1;
}
static NSCharacterSet *sentenceBoundarySet = nil;
static NSCharacterSet *whitespaceSet = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sentenceBoundarySet = [NSCharacterSet characterSetWithCharactersInString:@".!?。!?"];
whitespaceSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
});
NSInteger length = context.length;
NSInteger end = length;
while (end > 0) {
unichar ch = [context characterAtIndex:end - 1];
if ([whitespaceSet characterIsMember:ch]) {
end -= 1;
} else {
break;
}
}
NSInteger searchEnd = end;
while (searchEnd > 0) {
unichar ch = [context characterAtIndex:searchEnd - 1];
if ([sentenceBoundarySet characterIsMember:ch]) {
searchEnd -= 1;
} else {
break;
}
}
NSInteger boundaryIndex = NSNotFound;
for (NSInteger i = searchEnd - 1; i >= 0; i--) {
unichar ch = [context characterAtIndex:i];
if ([sentenceBoundarySet characterIsMember:ch]) {
boundaryIndex = i;
break;
}
}
BOOL boundaryFound = (boundaryIndex != NSNotFound);
NSInteger deleteCount = length;
if (boundaryIndex != NSNotFound) {
deleteCount = length - (boundaryIndex + 1);
}
deleteCount = MAX(deleteCount, 1);
if (hitBoundary) {
*hitBoundary = boundaryFound;
}
return MIN(deleteCount, kKBBackspaceClearMaxStep);
}
#pragma mark - Long Press State
- (void)kb_handleBackspaceLongPressChanged:(UILongPressGestureRecognizer *)gr {
if (!self.backspaceHoldActive) { return; }
if (!self.showClearLabelEnabled) { return; }
[self kb_showBackspaceClearLabelIfNeeded];
UIView *hostView = [self kb_hostView];
if (!hostView) { return; }
CGPoint point = [gr locationInView:hostView];
self.backspaceLastTouchPointInSelf = point;
self.backspaceHasLastTouchPoint = YES;
BOOL inside = [self kb_isPointInsideBackspaceClearLabel:point];
[self kb_setBackspaceClearHighlighted:inside];
}
- (void)kb_handleBackspaceLongPressEnded:(UILongPressGestureRecognizer *)gr {
BOOL shouldClear = NO;
if (self.showClearLabelEnabled) {
shouldClear = self.backspaceClearHighlighted;
if (!shouldClear) {
UIView *hostView = [self kb_hostView];
CGPoint point = CGPointZero;
if (gr && hostView) {
point = [gr locationInView:hostView];
} else if (self.backspaceHasLastTouchPoint) {
point = self.backspaceLastTouchPointInSelf;
}
shouldClear = [self kb_isPointInsideBackspaceClearLabel:point];
}
}
#if DEBUG
NSLog(@"[kb_handleBackspaceLongPressEnded] shouldClear=%@ highlighted=%@ labelHidden=%@",
shouldClear ? @"YES" : @"NO",
self.backspaceClearHighlighted ? @"YES" : @"NO",
self.backspaceClearLabel.hidden ? @"YES" : @"NO");
#endif
self.backspaceHoldActive = NO;
self.backspaceChunkModeActive = NO;
self.backspaceHoldToken += 1;
self.backspaceHasLastTouchPoint = NO;
[self kb_hideBackspaceClearLabel];
if (shouldClear) {
[self kb_clearAllInput];
} else {
self.pendingClearBefore = nil;
self.pendingClearAfter = nil;
[[KBInputBufferManager shared] clearPendingClearSnapshot];
[[KBInputBufferManager shared] commitLiveToManual];
}
}
#pragma mark - Clear Label
- (void)kb_showBackspaceClearLabelIfNeeded {
UIView *hostView = [self kb_hostView];
if (!hostView || !self.backspaceButton) { return; }
UILabel *label = self.backspaceClearLabel;
[self kb_refreshBackspaceClearLabelColors];
if (!label.superview) {
[hostView addSubview:label];
}
[self kb_updateBackspaceClearLabelFrame];
[hostView bringSubviewToFront:label];
if (label.hidden) {
label.alpha = 0.0;
label.hidden = NO;
[self kb_playLightHaptic];
[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 {
UIView *hostView = [self kb_hostView];
if (!hostView || !self.backspaceButton || !self.backspaceClearLabel) { return; }
CGRect btnFrame = [self.backspaceButton convertRect:self.backspaceButton.bounds toView:hostView];
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 < kKBBackspaceClearLabelHorizontalInset) { x = kKBBackspaceClearLabelHorizontalInset; }
CGFloat maxX = CGRectGetWidth(hostView.bounds) - kKBBackspaceClearLabelHorizontalInset - width;
if (x > maxX) { x = maxX; }
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 kb_updateBackspaceClearLabelFrame];
CGRect hitFrame = CGRectInset(self.backspaceClearLabel.frame, -12.0, -10.0);
return CGRectContainsPoint(hitFrame, 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];
}
- (void)kb_playLightHaptic {
if (@available(iOS 10.0, *)) {
UIImpactFeedbackGenerator *gen = [[UIImpactFeedbackGenerator alloc] initWithStyle:UIImpactFeedbackStyleLight];
[gen prepare];
[gen impactOccurred];
}
}
- (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;
}
#pragma mark - Clear
- (void)kb_clearAllInput {
UIResponder *start = (UIResponder *)([self kb_hostView] ?: self.backspaceButton);
UIInputViewController *ivc = KBFindInputViewController(start);
if (ivc) {
id<UITextDocumentProxy> proxy = ivc.textDocumentProxy;
[[KBInputBufferManager shared] refreshFromProxyIfPossible:proxy];
}
self.pendingClearBefore = nil;
self.pendingClearAfter = nil;
[[KBInputBufferManager shared] clearPendingClearSnapshot];
self.backspaceClearToken += 1;
self.backspaceClearPhase = KBClearPhaseSkipWhitespace;
NSUInteger token = self.backspaceClearToken;
[self kb_clearAllInputStepForToken:token guard:0 emptyRounds:0];
}
- (void)kb_clearAllInputStepForToken:(NSUInteger)token
guard:(NSInteger)guard
emptyRounds:(NSInteger)emptyRounds {
if (token != self.backspaceClearToken) { return; }
UIResponder *start = (UIResponder *)([self kb_hostView] ?: self.backspaceButton);
UIInputViewController *ivc = KBFindInputViewController(start);
if (!ivc) { return; }
id<UITextDocumentProxy> proxy = ivc.textDocumentProxy;
NSInteger nextEmptyRounds = emptyRounds;
static NSCharacterSet *sentenceBoundarySet = nil;
static NSCharacterSet *whitespaceSet = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sentenceBoundarySet = [NSCharacterSet characterSetWithCharactersInString:@".!?。!?"];
whitespaceSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
});
KBClearPhase phase = self.backspaceClearPhase;
NSInteger deletedThisTick = 0;
BOOL shouldStop = NO;
NSString *lastBefore = nil;
for (NSInteger i = 0; i < kKBBackspaceClearDeletesPerTick; i++) {
NSString *before = proxy.documentContextBeforeInput ?: @"";
if (before.length == 0) {
nextEmptyRounds += 1;
// 即使 context 为空,也尝试删一次(某些宿主延迟更新)
[[KBBackspaceUndoManager shared] captureAndDeleteBackwardFromProxy:proxy count:1];
[[KBInputBufferManager shared] applyClearDeleteCount:1];
deletedThisTick += 1;
break;
}
nextEmptyRounds = 0;
if (lastBefore && [before isEqualToString:lastBefore] && deletedThisTick > 0) {
// 宿主未及时刷新 context留到下一 tick 再继续,避免越界/重复记录
break;
}
lastBefore = before;
// 取最后一个组合字符
__block NSString *lastChar = @"";
[before enumerateSubstringsInRange:NSMakeRange(0, before.length)
options:NSStringEnumerationByComposedCharacterSequences | NSStringEnumerationReverse
usingBlock:^(NSString *substring, __unused NSRange substringRange, __unused NSRange enclosingRange, BOOL *stop) {
lastChar = substring ?: @"";
*stop = YES;
}];
if (lastChar.length == 0) { break; }
BOOL isWhitespace = ([lastChar rangeOfCharacterFromSet:whitespaceSet].location != NSNotFound);
BOOL isBoundary = ([lastChar rangeOfCharacterFromSet:sentenceBoundarySet].location != NSNotFound);
if (phase == KBClearPhaseSkipWhitespace) {
if (isWhitespace) {
[[KBBackspaceUndoManager shared] captureAndDeleteBackwardFromProxy:proxy count:1];
[[KBInputBufferManager shared] applyClearDeleteCount:1];
deletedThisTick += 1;
continue;
}
phase = KBClearPhaseSkipTrailingBoundary;
}
if (phase == KBClearPhaseSkipTrailingBoundary) {
if (isBoundary) {
[[KBBackspaceUndoManager shared] captureAndDeleteBackwardFromProxy:proxy count:1];
[[KBInputBufferManager shared] applyClearDeleteCount:1];
deletedThisTick += 1;
continue;
}
phase = KBClearPhaseDeleteUntilBoundary;
}
// phase == DeleteUntilBoundary
if (isBoundary) {
shouldStop = YES; // 保留该句末符号
break;
}
[[KBBackspaceUndoManager shared] captureAndDeleteBackwardFromProxy:proxy count:1];
[[KBInputBufferManager shared] applyClearDeleteCount:1];
deletedThisTick += 1;
if (guard + deletedThisTick >= kKBBackspaceClearMaxDeletes) { break; }
if (deletedThisTick >= kKBBackspaceClearMaxStep) { break; }
}
self.backspaceClearPhase = phase;
NSInteger nextGuard = guard + deletedThisTick;
if (nextGuard >= kKBBackspaceClearMaxDeletes ||
nextEmptyRounds > kKBBackspaceClearEmptyContextMaxRounds ||
shouldStop) {
return;
}
__weak typeof(self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
(int64_t)(kKBBackspaceClearBatchInterval * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
__strong typeof(weakSelf) selfStrong = weakSelf;
[selfStrong kb_clearAllInputStepForToken:token
guard:nextGuard
emptyRounds:nextEmptyRounds];
});
}
#pragma mark - Helpers
- (UIView *)kb_hostView {
if (self.containerView) { return self.containerView; }
return self.backspaceButton.superview;
}
- (void)kb_captureDeletionSnapshotIfNeeded {
if ([KBBackspaceUndoManager shared].hasUndo) { return; }
UIResponder *start = (UIResponder *)([self kb_hostView] ?: self.backspaceButton);
UIInputViewController *ivc = KBFindInputViewController(start);
if (!ivc) { return; }
id<UITextDocumentProxy> proxy = ivc.textDocumentProxy;
[[KBBackspaceUndoManager shared] recordDeletionSnapshotBefore:proxy.documentContextBeforeInput
after:proxy.documentContextAfterInput];
}
- (void)kb_capturePendingClearSnapshotIfNeeded {
if (self.pendingClearBefore.length > 0 || self.pendingClearAfter.length > 0) { return; }
UIResponder *start = (UIResponder *)([self kb_hostView] ?: self.backspaceButton);
UIInputViewController *ivc = KBFindInputViewController(start);
if (!ivc) { return; }
id<UITextDocumentProxy> proxy = ivc.textDocumentProxy;
self.pendingClearBefore = proxy.documentContextBeforeInput ?: @"";
self.pendingClearAfter = proxy.documentContextAfterInput ?: @"";
#if DEBUG
NSLog(@"[kb_capturePendingClearSnapshotIfNeeded/before] len=%lu text=%@", (unsigned long)self.pendingClearBefore.length, self.pendingClearBefore);
NSLog(@"[kb_capturePendingClearSnapshotIfNeeded/after] len=%lu text=%@", (unsigned long)self.pendingClearAfter.length, self.pendingClearAfter);
#endif
}
@end