From 1c8834caf60c5313556da2b5fe7e13c33f3ffeae Mon Sep 17 00:00:00 2001 From: CodeST <694468528@qq.com> Date: Fri, 19 Dec 2025 19:21:08 +0800 Subject: [PATCH] =?UTF-8?q?=E9=94=AE=E7=9B=98=E6=B7=BB=E5=8A=A0=E6=92=A4?= =?UTF-8?q?=E9=94=80=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CustomKeyboard/KeyboardViewController.m | 9 + .../Utils/KBBackspaceLongPressHandler.m | 8 + CustomKeyboard/Utils/KBBackspaceUndoManager.h | 29 +++ CustomKeyboard/Utils/KBBackspaceUndoManager.m | 170 ++++++++++++++++++ CustomKeyboard/View/KBFunctionView.m | 3 + CustomKeyboard/View/KBKeyBoardMainView.h | 2 + CustomKeyboard/View/KBKeyBoardMainView.m | 6 + CustomKeyboard/View/KBToolBar.h | 3 + CustomKeyboard/View/KBToolBar.m | 111 ++++++++++-- keyBoard.xcodeproj/project.pbxproj | 6 + 10 files changed, 332 insertions(+), 15 deletions(-) create mode 100644 CustomKeyboard/Utils/KBBackspaceUndoManager.h create mode 100644 CustomKeyboard/Utils/KBBackspaceUndoManager.m diff --git a/CustomKeyboard/KeyboardViewController.m b/CustomKeyboard/KeyboardViewController.m index 109164e..fb04d13 100644 --- a/CustomKeyboard/KeyboardViewController.m +++ b/CustomKeyboard/KeyboardViewController.m @@ -19,6 +19,7 @@ #import "KBHostAppLauncher.h" #import "KBKeyboardSubscriptionView.h" #import "KBKeyboardSubscriptionProduct.h" +#import "KBBackspaceUndoManager.h" // 提前声明一个类别,使编译器在 static 回调中识别 kb_consumePendingShopSkin 方法。 @interface KeyboardViewController (KBSkinShopBridge) @@ -249,6 +250,9 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center, // MARK: - KBKeyBoardMainViewDelegate - (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView didTapKey:(KBKey *)key { + if (key.type != KBKeyTypeShift && key.type != KBKeyTypeModeChange) { + [[KBBackspaceUndoManager shared] registerNonClearAction]; + } switch (key.type) { case KBKeyTypeCharacter: [self.textDocumentProxy insertText:key.output ?: key.title ?: @""]; break; @@ -285,9 +289,14 @@ static void KBSkinInstallNotificationCallback(CFNotificationCenterRef center, - (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView didSelectEmoji:(NSString *)emoji { if (emoji.length == 0) { return; } + [[KBBackspaceUndoManager shared] registerNonClearAction]; [self.textDocumentProxy insertText:emoji]; } +- (void)keyBoardMainViewDidTapUndo:(KBKeyBoardMainView *)keyBoardMainView { + [[KBBackspaceUndoManager shared] performUndoFromResponder:self.view]; +} + - (void)keyBoardMainViewDidTapEmojiSearch:(KBKeyBoardMainView *)keyBoardMainView { [KBHUD showInfo:KBLocalized(@"Search coming soon")]; } diff --git a/CustomKeyboard/Utils/KBBackspaceLongPressHandler.m b/CustomKeyboard/Utils/KBBackspaceLongPressHandler.m index 34d7247..4cef420 100644 --- a/CustomKeyboard/Utils/KBBackspaceLongPressHandler.m +++ b/CustomKeyboard/Utils/KBBackspaceLongPressHandler.m @@ -6,6 +6,7 @@ #import "KBBackspaceLongPressHandler.h" #import "KBResponderUtils.h" #import "KBSkinManager.h" +#import "KBBackspaceUndoManager.h" static const NSTimeInterval kKBBackspaceLongPressMinDuration = 0.35; static const NSTimeInterval kKBBackspaceRepeatInterval = 0.06; @@ -96,6 +97,7 @@ typedef NS_ENUM(NSInteger, KBBackspaceChunkClass) { } switch (gr.state) { case UIGestureRecognizerStateBegan: { + [[KBBackspaceUndoManager shared] registerNonClearAction]; self.backspaceHoldToken += 1; NSUInteger token = self.backspaceHoldToken; self.backspaceHoldActive = YES; @@ -406,6 +408,12 @@ typedef NS_ENUM(NSInteger, KBBackspaceChunkClass) { #pragma mark - Clear - (void)kb_clearAllInput { + UIResponder *start = (UIResponder *)([self kb_hostView] ?: self.backspaceButton); + UIInputViewController *ivc = KBFindInputViewController(start); + if (ivc) { + NSString *before = ivc.textDocumentProxy.documentContextBeforeInput ?: @""; + [[KBBackspaceUndoManager shared] recordClearWithContext:before]; + } self.backspaceClearToken += 1; NSUInteger token = self.backspaceClearToken; [self kb_clearAllInputStepForToken:token guard:0 emptyRounds:0]; diff --git a/CustomKeyboard/Utils/KBBackspaceUndoManager.h b/CustomKeyboard/Utils/KBBackspaceUndoManager.h new file mode 100644 index 0000000..9b11946 --- /dev/null +++ b/CustomKeyboard/Utils/KBBackspaceUndoManager.h @@ -0,0 +1,29 @@ +// +// KBBackspaceUndoManager.h +// CustomKeyboard +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +extern NSNotificationName const KBBackspaceUndoStateDidChangeNotification; + +@interface KBBackspaceUndoManager : NSObject + +@property (nonatomic, readonly) BOOL hasUndo; + ++ (instancetype)shared; + +/// 记录一次“立刻清空”删除的内容(基于 documentContextBeforeInput) +- (void)recordClearWithContext:(NSString *)context; + +/// 在指定 responder 处执行撤销(向光标处插回删除的内容) +- (void)performUndoFromResponder:(UIResponder *)responder; + +/// 非清空行为触发时,清理撤销状态 +- (void)registerNonClearAction; + +@end + +NS_ASSUME_NONNULL_END diff --git a/CustomKeyboard/Utils/KBBackspaceUndoManager.m b/CustomKeyboard/Utils/KBBackspaceUndoManager.m new file mode 100644 index 0000000..f6144a4 --- /dev/null +++ b/CustomKeyboard/Utils/KBBackspaceUndoManager.m @@ -0,0 +1,170 @@ +// +// KBBackspaceUndoManager.m +// CustomKeyboard +// + +#import "KBBackspaceUndoManager.h" +#import "KBResponderUtils.h" + +NSNotificationName const KBBackspaceUndoStateDidChangeNotification = @"KBBackspaceUndoStateDidChangeNotification"; + +@interface KBBackspaceUndoManager () +@property (nonatomic, strong) NSMutableArray *segments; // deletion order (last -> first) +@property (nonatomic, assign) BOOL lastActionWasClear; +@property (nonatomic, assign) BOOL hasUndo; +@end + +@implementation KBBackspaceUndoManager + ++ (instancetype)shared { + static KBBackspaceUndoManager *mgr = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + mgr = [[KBBackspaceUndoManager alloc] init]; + }); + return mgr; +} + +- (instancetype)init { + if (self = [super init]) { + _segments = [NSMutableArray array]; + } + return self; +} + +- (void)recordClearWithContext:(NSString *)context { + if (context.length == 0) { return; } + NSString *segment = [self kb_segmentForClearFromContext:context]; + if (segment.length == 0) { return; } + + if (!self.lastActionWasClear) { + [self.segments removeAllObjects]; + } + [self.segments addObject:segment]; + self.lastActionWasClear = YES; + [self kb_updateHasUndo:YES]; +} + +- (void)performUndoFromResponder:(UIResponder *)responder { + if (self.segments.count == 0) { return; } + UIInputViewController *ivc = KBFindInputViewController(responder); + if (!ivc) { return; } + id proxy = ivc.textDocumentProxy; + NSString *text = [self kb_buildUndoText]; + if (text.length == 0) { return; } + [proxy insertText:text]; + + [self.segments removeAllObjects]; + self.lastActionWasClear = NO; + [self kb_updateHasUndo:NO]; +} + +- (void)registerNonClearAction { + self.lastActionWasClear = NO; + if (self.segments.count == 0) { return; } + [self.segments removeAllObjects]; + [self kb_updateHasUndo:NO]; +} + +#pragma mark - Helpers + +- (void)kb_updateHasUndo:(BOOL)hasUndo { + if (self.hasUndo == hasUndo) { return; } + self.hasUndo = hasUndo; + [[NSNotificationCenter defaultCenter] postNotificationName:KBBackspaceUndoStateDidChangeNotification object:self]; +} + +- (NSString *)kb_segmentForClearFromContext:(NSString *)context { + NSInteger length = context.length; + if (length == 0) { return @""; } + + static NSCharacterSet *sentenceBoundarySet = nil; + static NSCharacterSet *whitespaceSet = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sentenceBoundarySet = [NSCharacterSet characterSetWithCharactersInString:@".!?;。!?;…\n"]; + whitespaceSet = [NSCharacterSet whitespaceAndNewlineCharacterSet]; + }); + + 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; + } + } + + NSInteger start = (boundaryIndex == NSNotFound) ? 0 : (boundaryIndex + 1); + if (start >= length) { return @""; } + return [context substringFromIndex:start]; +} + +- (NSString *)kb_buildUndoText { + if (self.segments.count == 0) { return @""; } + NSArray *ordered = [[self.segments reverseObjectEnumerator] allObjects]; + NSMutableString *result = [NSMutableString string]; + for (NSInteger i = 0; i < ordered.count; i++) { + NSString *segment = ordered[i] ?: @""; + if (segment.length == 0) { continue; } + if (i < ordered.count - 1) { + segment = [self kb_replaceTrailingBoundaryWithComma:segment]; + } + [result appendString:segment]; + } + return result; +} + +- (NSString *)kb_replaceTrailingBoundaryWithComma:(NSString *)segment { + if (segment.length == 0) { return segment; } + + static NSCharacterSet *boundarySet = nil; + static NSCharacterSet *englishBoundarySet = nil; + static NSCharacterSet *whitespaceSet = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + boundarySet = [NSCharacterSet characterSetWithCharactersInString:@".!?;。!?;…\n"]; + englishBoundarySet = [NSCharacterSet characterSetWithCharactersInString:@".!?;"]; + whitespaceSet = [NSCharacterSet whitespaceAndNewlineCharacterSet]; + }); + + NSInteger idx = segment.length - 1; + while (idx >= 0) { + unichar ch = [segment characterAtIndex:idx]; + if ([whitespaceSet characterIsMember:ch]) { + idx -= 1; + continue; + } + if (![boundarySet characterIsMember:ch]) { + return segment; + } + NSString *comma = [englishBoundarySet characterIsMember:ch] ? @"," : @","; + NSMutableString *mutable = [segment mutableCopy]; + NSRange r = NSMakeRange(idx, 1); + [mutable replaceCharactersInRange:r withString:comma]; + return mutable; + } + + return segment; +} + +@end diff --git a/CustomKeyboard/View/KBFunctionView.m b/CustomKeyboard/View/KBFunctionView.m index 9a9d19d..3a18530 100644 --- a/CustomKeyboard/View/KBFunctionView.m +++ b/CustomKeyboard/View/KBFunctionView.m @@ -26,6 +26,7 @@ #import #import "KBBizCode.h" #import "KBBackspaceLongPressHandler.h" +#import "KBBackspaceUndoManager.h" @interface KBFunctionView () // UI @@ -769,6 +770,7 @@ static void KBULDarwinCallback(CFNotificationCenterRef center, void *observer, C } - (void)onTapDelete { NSLog(@"点击:删除"); + [[KBBackspaceUndoManager shared] registerNonClearAction]; UIInputViewController *ivc = KBFindInputViewController(self); id proxy = ivc.textDocumentProxy; [proxy deleteBackward]; @@ -779,6 +781,7 @@ static void KBULDarwinCallback(CFNotificationCenterRef center, void *observer, C } - (void)onTapSend { NSLog(@"点击:发送"); + [[KBBackspaceUndoManager shared] registerNonClearAction]; // 发送:插入换行。大多数聊天类 App 会把回车视为“发送” UIInputViewController *ivc = KBFindInputViewController(self); id proxy = ivc.textDocumentProxy; diff --git a/CustomKeyboard/View/KBKeyBoardMainView.h b/CustomKeyboard/View/KBKeyBoardMainView.h index 7de5de0..fef4574 100644 --- a/CustomKeyboard/View/KBKeyBoardMainView.h +++ b/CustomKeyboard/View/KBKeyBoardMainView.h @@ -23,6 +23,8 @@ NS_ASSUME_NONNULL_BEGIN /// 点击了右侧设置按钮 - (void)keyBoardMainViewDidTapSettings:(KBKeyBoardMainView *)keyBoardMainView; +/// 点击了撤销删除按钮 +- (void)keyBoardMainViewDidTapUndo:(KBKeyBoardMainView *)keyBoardMainView; /// emoji 视图里选择了一个表情 - (void)keyBoardMainView:(KBKeyBoardMainView *)keyBoardMainView didSelectEmoji:(NSString *)emoji; diff --git a/CustomKeyboard/View/KBKeyBoardMainView.m b/CustomKeyboard/View/KBKeyBoardMainView.m index 99d695c..fc1a505 100644 --- a/CustomKeyboard/View/KBKeyBoardMainView.m +++ b/CustomKeyboard/View/KBKeyBoardMainView.m @@ -115,6 +115,12 @@ } } +- (void)toolBarDidTapUndo:(KBToolBar *)toolBar { + if ([self.delegate respondsToSelector:@selector(keyBoardMainViewDidTapUndo:)]) { + [self.delegate keyBoardMainViewDidTapUndo:self]; + } +} + #pragma mark - KBKeyboardViewDelegate - (void)keyboardView:(KBKeyboardView *)keyboard didTapKey:(KBKey *)key { diff --git a/CustomKeyboard/View/KBToolBar.h b/CustomKeyboard/View/KBToolBar.h index 8286c46..5813fad 100644 --- a/CustomKeyboard/View/KBToolBar.h +++ b/CustomKeyboard/View/KBToolBar.h @@ -17,6 +17,8 @@ NS_ASSUME_NONNULL_BEGIN - (void)toolBar:(KBToolBar *)toolBar didTapActionAtIndex:(NSInteger)index; /// 右侧设置按钮点击 - (void)toolBarDidTapSettings:(KBToolBar *)toolBar; +/// 右侧撤销删除按钮点击 +- (void)toolBarDidTapUndo:(KBToolBar *)toolBar; @end /// 顶部工具栏:左侧 4 个按钮,右侧 1 个设置按钮。 @@ -30,6 +32,7 @@ NS_ASSUME_NONNULL_BEGIN /// 暴露按钮以便外部定制(只读;首次访问时懒加载创建) @property (nonatomic, strong, readonly) NSArray *leftButtons; @property (nonatomic, strong, readonly) UIButton *settingsButton; +@property (nonatomic, strong, readonly) UIButton *undoButton; @end diff --git a/CustomKeyboard/View/KBToolBar.m b/CustomKeyboard/View/KBToolBar.m index 9c0e84c..d27500d 100644 --- a/CustomKeyboard/View/KBToolBar.m +++ b/CustomKeyboard/View/KBToolBar.m @@ -7,12 +7,16 @@ #import "KBToolBar.h" #import "KBResponderUtils.h" // 查找 UIInputViewController,用于系统切换输入法 +#import "KBBackspaceUndoManager.h" @interface KBToolBar () @property (nonatomic, strong) UIView *leftContainer; @property (nonatomic, strong) NSArray *leftButtonsInternal; //@property (nonatomic, strong) UIButton *settingsButtonInternal; @property (nonatomic, strong) UIButton *globeButtonInternal; // 可选:系统“切换输入法”键 +@property (nonatomic, strong) UIButton *undoButtonInternal; // 右侧撤销删除 +@property (nonatomic, assign) BOOL kbNeedsInputModeSwitchKey; +@property (nonatomic, assign) BOOL kbUndoVisible; @end @implementation KBToolBar @@ -22,10 +26,18 @@ self.backgroundColor = [UIColor clearColor]; _leftButtonTitles = @[KBLocalized(@"Recharge Now")]; // 默认标题 [self setupUI]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(kb_undoStateChanged) + name:KBBackspaceUndoStateDidChangeNotification + object:nil]; } return self; } +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + #pragma mark - Public @@ -33,6 +45,10 @@ return self.leftButtonsInternal; } +- (UIButton *)undoButton { + return self.undoButtonInternal; +} + //- (UIButton *)settingsButton { // return self.settingsButtonInternal; //} @@ -53,6 +69,7 @@ [self addSubview:self.leftContainer]; // [self addSubview:self.settingsButtonInternal]; [self addSubview:self.globeButtonInternal]; + [self addSubview:self.undoButtonInternal]; // 右侧设置按钮 // [self.settingsButtonInternal mas_makeConstraints:^(MASConstraintMaker *make) { @@ -68,14 +85,15 @@ make.width.height.mas_equalTo(32); }]; - // 左侧容器占用剩余空间 - [self.leftContainer mas_makeConstraints:^(MASConstraintMaker *make) { - make.left.equalTo(self.globeButtonInternal.mas_right).offset(8); - make.right.equalTo(self).offset(-12); + // 右侧撤销按钮 + [self.undoButtonInternal mas_makeConstraints:^(MASConstraintMaker *make) { + make.right.equalTo(self.mas_right).offset(-12); make.centerY.equalTo(self.mas_centerY); make.height.mas_equalTo(32); }]; + [self kb_updateLeftContainerConstraints]; + // 在左侧容器中创建按钮(数量 = 标题数量) NSMutableArray *buttons = [NSMutableArray arrayWithCapacity:_leftButtonTitles.count]; UIView *previous = nil; @@ -110,6 +128,7 @@ // 初始刷新地球键的可见性与事件绑定 [self kb_refreshGlobeVisibility]; + [self kb_updateUndoVisibilityAnimated:NO]; } - (UIButton *)buildActionButtonAtIndex:(NSInteger)idx { @@ -147,6 +166,12 @@ } } +- (void)onUndo { + if ([self.delegate respondsToSelector:@selector(toolBarDidTapUndo:)]) { + [self.delegate toolBarDidTapUndo:self]; + } +} + #pragma mark - Lazy - (UIView *)leftContainer { @@ -182,6 +207,23 @@ return _globeButtonInternal; } +- (UIButton *)undoButtonInternal { + if (!_undoButtonInternal) { + _undoButtonInternal = [UIButton buttonWithType:UIButtonTypeSystem]; + _undoButtonInternal.layer.cornerRadius = 16; + _undoButtonInternal.layer.masksToBounds = YES; + _undoButtonInternal.backgroundColor = [UIColor colorWithWhite:1 alpha:0.9]; + _undoButtonInternal.titleLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium]; + [_undoButtonInternal setTitle:@"撤销删除" forState:UIControlStateNormal]; + [_undoButtonInternal setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; + _undoButtonInternal.contentEdgeInsets = UIEdgeInsetsMake(0, 10, 0, 10); + _undoButtonInternal.hidden = YES; + _undoButtonInternal.alpha = 0.0; + [_undoButtonInternal addTarget:self action:@selector(onUndo) forControlEvents:UIControlEventTouchUpInside]; + } + return _undoButtonInternal; +} + #pragma mark - Globe (Input Mode Switch) // 根据宿主是否已提供系统切换键,决定是否显示地球按钮;并绑定系统事件。 @@ -193,18 +235,9 @@ } self.globeButtonInternal.hidden = !needSwitchKey; + self.kbNeedsInputModeSwitchKey = needSwitchKey; - // 重新调整 leftContainer 的左约束:若不需要地球键,则贴左边距 12 - [self.leftContainer mas_remakeConstraints:^(MASConstraintMaker *make) { - if (needSwitchKey) { - make.left.equalTo(self.globeButtonInternal.mas_right).offset(8); - } else { - make.left.equalTo(self.mas_left).offset(12); - } - make.right.equalTo(self).offset(-12); - make.centerY.equalTo(self.mas_centerY); - make.height.mas_equalTo(32); - }]; + [self kb_updateLeftContainerConstraints]; // 绑定系统提供的输入法切换处理(点按切换、长按弹出列表) // 仅在需要时绑定,避免多余的事件转发 @@ -220,6 +253,54 @@ } } +- (void)kb_updateLeftContainerConstraints { + [self.leftContainer mas_remakeConstraints:^(MASConstraintMaker *make) { + if (self.kbNeedsInputModeSwitchKey) { + make.left.equalTo(self.globeButtonInternal.mas_right).offset(8); + } else { + make.left.equalTo(self.mas_left).offset(12); + } + if (self.kbUndoVisible) { + make.right.equalTo(self.undoButtonInternal.mas_left).offset(-8); + } else { + make.right.equalTo(self).offset(-12); + } + make.centerY.equalTo(self.mas_centerY); + make.height.mas_equalTo(32); + }]; +} + +- (void)kb_undoStateChanged { + [self kb_updateUndoVisibilityAnimated:YES]; +} + +- (void)kb_updateUndoVisibilityAnimated:(BOOL)animated { + BOOL visible = [KBBackspaceUndoManager shared].hasUndo; + if (self.kbUndoVisible == visible) { return; } + self.kbUndoVisible = visible; + self.undoButtonInternal.hidden = NO; + + [self kb_updateLeftContainerConstraints]; + void (^changes)(void) = ^{ + self.undoButtonInternal.alpha = visible ? 1.0 : 0.0; + [self layoutIfNeeded]; + }; + void (^finish)(BOOL) = ^(BOOL finished) { + self.undoButtonInternal.hidden = !visible; + }; + + if (animated) { + [UIView animateWithDuration:0.18 + delay:0 + options:UIViewAnimationOptionCurveEaseInOut + animations:changes + completion:finish]; + } else { + changes(); + finish(YES); + } +} + - (void)didMoveToWindow { [super didMoveToWindow]; [self kb_refreshGlobeVisibility]; diff --git a/keyBoard.xcodeproj/project.pbxproj b/keyBoard.xcodeproj/project.pbxproj index 149ec77..74c22d9 100644 --- a/keyBoard.xcodeproj/project.pbxproj +++ b/keyBoard.xcodeproj/project.pbxproj @@ -179,6 +179,7 @@ 04FC956D2EB054B7007BD342 /* KBKeyboardView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC956C2EB054B7007BD342 /* KBKeyboardView.m */; }; 04FC95702EB09516007BD342 /* KBFunctionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC956F2EB09516007BD342 /* KBFunctionView.m */; }; A1B2C9032FBD000100000001 /* KBBackspaceLongPressHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C9022FBD000100000001 /* KBBackspaceLongPressHandler.m */; }; + A1B2C9052FBD000200000001 /* KBBackspaceUndoManager.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C9042FBD000200000001 /* KBBackspaceUndoManager.m */; }; 04FC95732EB09570007BD342 /* KBFunctionBarView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC95722EB09570007BD342 /* KBFunctionBarView.m */; }; 04FC95762EB095DE007BD342 /* KBFunctionPasteView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC95752EB095DE007BD342 /* KBFunctionPasteView.m */; }; 04FC95792EB09BC8007BD342 /* KBKeyBoardMainView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC95782EB09BC8007BD342 /* KBKeyBoardMainView.m */; }; @@ -541,6 +542,8 @@ 04FC956F2EB09516007BD342 /* KBFunctionView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBFunctionView.m; sourceTree = ""; }; A1B2C9012FBD000100000001 /* KBBackspaceLongPressHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBBackspaceLongPressHandler.h; sourceTree = ""; }; A1B2C9022FBD000100000001 /* KBBackspaceLongPressHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBBackspaceLongPressHandler.m; sourceTree = ""; }; + A1B2C9032FBD000200000001 /* KBBackspaceUndoManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBBackspaceUndoManager.h; sourceTree = ""; }; + A1B2C9042FBD000200000001 /* KBBackspaceUndoManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBBackspaceUndoManager.m; sourceTree = ""; }; 04FC95712EB09570007BD342 /* KBFunctionBarView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBFunctionBarView.h; sourceTree = ""; }; 04FC95722EB09570007BD342 /* KBFunctionBarView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBFunctionBarView.m; sourceTree = ""; }; 04FC95742EB095DE007BD342 /* KBFunctionPasteView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBFunctionPasteView.h; sourceTree = ""; }; @@ -810,6 +813,8 @@ children = ( A1B2C9012FBD000100000001 /* KBBackspaceLongPressHandler.h */, A1B2C9022FBD000100000001 /* KBBackspaceLongPressHandler.m */, + A1B2C9032FBD000200000001 /* KBBackspaceUndoManager.h */, + A1B2C9042FBD000200000001 /* KBBackspaceUndoManager.m */, ); path = Utils; sourceTree = ""; @@ -1898,6 +1903,7 @@ 049FB22F2EC34EB900FAB05D /* KBStreamTextView.m in Sources */, 04FC95702EB09516007BD342 /* KBFunctionView.m in Sources */, A1B2C9032FBD000100000001 /* KBBackspaceLongPressHandler.m in Sources */, + A1B2C9052FBD000200000001 /* KBBackspaceUndoManager.m in Sources */, 049FB23F2EC4B6EF00FAB05D /* KBULBridgeNotification.m in Sources */, 04791F992ED49CE7004E8522 /* KBFont.m in Sources */, 04FC956D2EB054B7007BD342 /* KBKeyboardView.m in Sources */,