Files
keyboard/CustomKeyboard/View/KBFunctionView.m

648 lines
28 KiB
Mathematica
Raw Normal View History

2025-10-28 14:30:03 +08:00
//
// KBFunctionView.m
// CustomKeyboard
//
// Created by Mac on 2025/10/28.
//
#import "KBFunctionView.h"
2025-11-04 16:37:24 +08:00
#import "KBResponderUtils.h" // UIInputViewController
2025-10-28 14:30:03 +08:00
#import "KBFunctionBarView.h"
#import "KBFunctionPasteView.h"
#import "KBFunctionTagCell.h"
#import "Masonry.h"
2025-10-30 20:23:34 +08:00
#import <MBProgressHUD.h>
2025-10-30 20:46:54 +08:00
#import "KBFullAccessGuideView.h"
2025-11-03 13:25:41 +08:00
#import "KBFullAccessManager.h"
2025-11-04 21:01:46 +08:00
#import "KBSkinManager.h"
#import "KBURLOpenBridge.h" // openURL:
2025-11-11 20:24:13 +08:00
#import "KBStreamTextView.h" //
2025-11-12 14:18:56 +08:00
#import "KBStreamFetcher.h" //
2025-10-28 14:30:03 +08:00
static NSString * const kKBFunctionTagCellId = @"KBFunctionTagCellId";
2025-11-12 14:18:56 +08:00
@interface KBFunctionView () <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, KBFunctionBarViewDelegate>
2025-10-28 14:30:03 +08:00
// UI
@property (nonatomic, strong) KBFunctionBarView *barViewInternal;
@property (nonatomic, strong) KBFunctionPasteView *pasteViewInternal;
@property (nonatomic, strong) UICollectionView *collectionViewInternal;
@property (nonatomic, strong) UIView *rightButtonContainer; //
@property (nonatomic, strong) UIButton *pasteButtonInternal;
@property (nonatomic, strong) UIButton *deleteButtonInternal;
@property (nonatomic, strong) UIButton *clearButtonInternal;
@property (nonatomic, strong) UIButton *sendButtonInternal;
2025-11-11 20:24:13 +08:00
//
@property (nonatomic, strong, nullable) KBStreamTextView *streamTextView;
@property (nonatomic, strong, nullable) UIButton *streamDeleteButton;
2025-11-11 21:48:26 +08:00
// &
@property (nonatomic, strong, nullable) NSTimer *streamMockTimer;
@property (nonatomic, strong, nullable) NSArray<NSString *> *streamMockChunks;
@property (nonatomic, assign) NSInteger streamMockIndex;
@property (nonatomic, copy, nullable) NSString *streamMockSource; // \t
@property (nonatomic, assign) NSInteger streamMockCursor; //
2025-11-11 20:24:13 +08:00
2025-11-12 14:18:56 +08:00
//
@property (nonatomic, strong, nullable) KBStreamFetcher *streamFetcher;
@property (nonatomic, assign) BOOL streamHasOutput; // \t
2025-11-12 13:43:48 +08:00
2025-10-28 14:30:03 +08:00
// Data
@property (nonatomic, strong) NSArray<NSString *> *itemsInternal;
2025-10-29 15:49:43 +08:00
//
@property (nonatomic, strong) NSTimer *pasteboardTimer; // 线
@property (nonatomic, assign) NSInteger lastHandledPBCount; // changeCount
2025-10-28 14:30:03 +08:00
@end
@implementation KBFunctionView
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
2025-11-04 21:01:46 +08:00
// 使
[self kb_applyTheme];
2025-10-28 14:30:03 +08:00
[self setupUI];
[self reloadDemoData];
2025-10-29 15:49:43 +08:00
//
_lastHandledPBCount = [UIPasteboard generalPasteboard].changeCount;
// 访访 TCC/XPC
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(kb_fullAccessChanged) name:KBFullAccessChangedNotification object:nil];
2025-10-28 14:30:03 +08:00
}
return self;
}
2025-11-04 21:01:46 +08:00
#pragma mark - Theme
- (void)kb_applyTheme {
KBSkinManager *mgr = [KBSkinManager shared];
UIColor *accent = mgr.current.accentColor ?: [UIColor colorWithRed:0.77 green:0.93 blue:0.82 alpha:1.0];
BOOL hasImg = ([mgr currentBackgroundImage] != nil);
self.backgroundColor = hasImg ? [accent colorWithAlphaComponent:0.65] : accent;
}
2025-10-29 15:49:43 +08:00
- (void)dealloc {
[self stopPasteboardMonitor];
2025-11-12 13:43:48 +08:00
[self kb_stopNetworkStreaming];
2025-11-11 21:48:26 +08:00
[self kb_stopMockStreaming];
[[NSNotificationCenter defaultCenter] removeObserver:self];
2025-10-29 15:49:43 +08:00
}
2025-10-28 14:30:03 +08:00
#pragma mark - UI
- (void)setupUI {
// 1. Bar
[self addSubview:self.barViewInternal];
[self.barViewInternal mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.equalTo(self);
make.top.equalTo(self.mas_top).offset(6);
make.height.mas_equalTo(48);
}];
//
[self addSubview:self.rightButtonContainer];
[self.rightButtonContainer mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.equalTo(self.mas_right).offset(-12);
make.top.equalTo(self.barViewInternal.mas_bottom).offset(8);
make.bottom.equalTo(self.mas_bottom).offset(-10);
make.width.mas_equalTo(72);
}];
//
[self.rightButtonContainer addSubview:self.pasteButtonInternal];
[self.rightButtonContainer addSubview:self.deleteButtonInternal];
[self.rightButtonContainer addSubview:self.clearButtonInternal];
[self.rightButtonContainer addSubview:self.sendButtonInternal];
//
2025-10-28 14:30:03 +08:00
CGFloat smallH = 44;
CGFloat bigH = 56;
// 10 276 8 AutoLayout
CGFloat vSpace = 8;
2025-10-28 14:30:03 +08:00
[self.pasteButtonInternal mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.rightButtonContainer.mas_top);
make.left.right.equalTo(self.rightButtonContainer);
make.height.mas_equalTo(smallH);
}];
[self.deleteButtonInternal mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.pasteButtonInternal.mas_bottom).offset(vSpace);
make.left.right.equalTo(self.rightButtonContainer);
make.height.equalTo(self.pasteButtonInternal);
}];
[self.clearButtonInternal mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.deleteButtonInternal.mas_bottom).offset(vSpace);
make.left.right.equalTo(self.rightButtonContainer);
make.height.equalTo(self.pasteButtonInternal);
}];
[self.sendButtonInternal mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.clearButtonInternal.mas_bottom).offset(vSpace);
make.left.right.equalTo(self.rightButtonContainer);
// smallH
make.height.greaterThanOrEqualTo(@(smallH));
make.height.lessThanOrEqualTo(@(bigH));
make.bottom.lessThanOrEqualTo(self.rightButtonContainer.mas_bottom);
2025-10-28 14:30:03 +08:00
}];
// 2.
[self addSubview:self.pasteViewInternal];
[self.pasteViewInternal mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.mas_left).offset(12);
make.right.equalTo(self.rightButtonContainer.mas_left).offset(-12);
make.top.equalTo(self.barViewInternal.mas_bottom).offset(8);
make.height.mas_equalTo(48);
}];
// 3. CollectionView
[self addSubview:self.collectionViewInternal];
[self.collectionViewInternal mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.mas_left).offset(12);
make.right.equalTo(self.rightButtonContainer.mas_left).offset(-12);
make.top.equalTo(self.pasteViewInternal.mas_bottom).offset(10);
make.bottom.equalTo(self.mas_bottom).offset(-10);
}];
}
#pragma mark - Data
- (void)reloadDemoData {
//
self.itemsInternal = @[@"高情商", @"暖味拉扯", @"风趣幽默", @"撩女生", @"社交惬匿", @"情场高手", @"一枚暖男", @"聊天搭子", @"表达爱意", @"更多话术"];
[self.collectionViewInternal reloadData];
}
#pragma mark - UICollectionView
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.itemsInternal.count;
}
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
KBFunctionTagCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kKBFunctionTagCellId forIndexPath:indexPath];
cell.titleLabel.text = self.itemsInternal[indexPath.item];
return cell;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
//
CGFloat totalW = collectionView.bounds.size.width;
CGFloat space = 10.0;
NSInteger columns = 3;
CGFloat width = floor((totalW - space * (columns - 1)) / columns);
return CGSizeMake(width, 48);
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section {
return 10.0;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section {
return 12.0;
}
2025-11-11 20:24:13 +08:00
- (void)kb_showStreamTextViewIfNeededWithTitle:(NSString *)title {
//
if (self.streamTextView.superview) { return; }
// 使
self.collectionViewInternal.hidden = YES;
KBStreamTextView *sv = [[KBStreamTextView alloc] init];
sv.backgroundColor = [UIColor colorWithWhite:1.0 alpha:0.92];
sv.layer.cornerRadius = 12.0; sv.layer.masksToBounds = YES;
//
if (title.length > 0) {
[sv appendStreamText:[NSString stringWithFormat:@"%@\t", title]]; //
}
[self addSubview:sv];
[sv mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.mas_left).offset(12);
2025-11-11 21:48:26 +08:00
make.right.equalTo(self.rightButtonContainer.mas_left).offset(-12);
2025-11-11 20:24:13 +08:00
make.top.equalTo(self.pasteViewInternal.mas_bottom).offset(10);
make.bottom.equalTo(self.mas_bottom).offset(-10);
}];
self.streamTextView = sv;
//
UIButton *del = [UIButton buttonWithType:UIButtonTypeSystem];
del.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.35];
del.layer.cornerRadius = 14; del.layer.masksToBounds = YES;
del.titleLabel.font = [UIFont systemFontOfSize:13 weight:UIFontWeightSemibold];
[del setTitle:@"删除" forState:UIControlStateNormal];
[del setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[del addTarget:self action:@selector(kb_onTapStreamDelete) forControlEvents:UIControlEventTouchUpInside];
[sv addSubview:del];
[del mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(sv.mas_top).offset(8);
make.right.equalTo(sv.mas_right).offset(-8);
make.height.mas_equalTo(28);
make.width.mas_greaterThanOrEqualTo(56);
}];
self.streamDeleteButton = del;
2025-11-11 21:48:26 +08:00
2025-11-12 13:43:48 +08:00
// GET
[self kb_startNetworkStreamingWithSeed:title fallbackToMock:YES];
2025-11-11 20:24:13 +08:00
}
- (void)kb_onTapStreamDelete {
//
2025-11-12 13:43:48 +08:00
[self kb_stopNetworkStreaming];
2025-11-11 21:48:26 +08:00
[self kb_stopMockStreaming];
2025-11-11 20:24:13 +08:00
[self.streamDeleteButton removeFromSuperview];
self.streamDeleteButton = nil;
[self.streamTextView removeFromSuperview];
self.streamTextView = nil;
self.collectionViewInternal.hidden = NO;
}
2025-11-11 21:48:26 +08:00
#pragma mark - Mock Streaming
// KBStreamTextView
- (void)kb_startMockStreamingWithSeed:(NSString *)seedTitle {
[self kb_stopMockStreaming];
// \t \n 1~3
NSString *t = seedTitle ?: @"高情商";
self.streamMockSource = [NSString stringWithFormat:
@"选择了标签:%@\n"
"下面演示流式输出,每隔一小段时间来一截。第一段结束。\t"
"第二段开始:这是一个较长的句子,会被拆成多个小块推送,直到遇到分隔符就换段。\n第二段结束。\t"
"最后一段:右上角可随时关闭展示。\n演示完成。\t",
t];
self.streamMockCursor = 0;
self.streamMockChunks = nil; //
self.streamMockIndex = 0;
// 线 1~3
__weak typeof(self) weakSelf = self;
self.streamMockTimer = [NSTimer scheduledTimerWithTimeInterval:0.20 repeats:YES block:^(NSTimer * _Nonnull timer) {
__strong typeof(weakSelf) self = weakSelf; if (!self) { [timer invalidate]; return; }
if (!self.streamTextView) { [timer invalidate]; return; }
NSInteger total = (NSInteger)self.streamMockSource.length;
if (self.streamMockCursor >= total) {
//
[self.streamTextView finishStreaming];
[timer invalidate];
self.streamMockTimer = nil;
return;
}
// 1~3
uint32_t step = arc4random_uniform(3) + 1; // 1..3
NSInteger remain = total - self.streamMockCursor;
NSInteger take = MIN((NSInteger)step, remain);
NSString *piece = [self.streamMockSource substringWithRange:NSMakeRange(self.streamMockCursor, take)];
self.streamMockCursor += take;
[self.streamTextView appendStreamText:piece ?: @""];
}];
}
- (void)kb_stopMockStreaming {
[self.streamMockTimer invalidate];
self.streamMockTimer = nil;
self.streamMockChunks = nil;
self.streamMockIndex = 0;
self.streamMockSource = nil;
self.streamMockCursor = 0;
}
2025-11-12 13:43:48 +08:00
#pragma mark - Network Streaming (GET)
// ATS target Info.plist HTTP
static NSString * const kKBStreamDemoURL = @"http://192.168.1.144:7529/api/demo/talk";
- (void)kb_startNetworkStreamingWithSeed:(NSString *)seedTitle fallbackToMock:(BOOL)fallback {
[self kb_stopNetworkStreaming];
2025-11-12 15:31:22 +08:00
if (![[KBFullAccessManager shared] hasFullAccess]) { if (fallback) /*[self kb_startMockStreamingWithSeed:seedTitle]*/; return; }
2025-11-12 13:43:48 +08:00
NSURL *url = [NSURL URLWithString:kKBStreamDemoURL];
if (!url) { if (fallback) [self kb_startMockStreamingWithSeed:seedTitle]; return; }
2025-11-12 14:18:56 +08:00
self.streamHasOutput = NO; //
__weak typeof(self) weakSelf = self;
KBStreamFetcher *fetcher = [KBStreamFetcher fetcherWithURL:url];
// /t->\t \tfetcher
fetcher.disableCompression = YES;
fetcher.acceptEventStream = NO; // SSE
2025-11-12 15:31:22 +08:00
// \t \t Fetcher UI
fetcher.treatSlashTAsTab = YES;
fetcher.trimLeadingTabOnce = YES;
fetcher.flushInterval = 0.05; //
2025-11-12 14:18:56 +08:00
fetcher.onChunk = ^(NSString *chunk) {
__strong typeof(weakSelf) self = weakSelf; if (!self) return;
[self kb_appendChunkToStreamView:chunk];
};
fetcher.onFinish = ^(NSError * _Nullable error) {
__strong typeof(weakSelf) self = weakSelf; if (!self) return;
if (error && fallback && !self.streamHasOutput) {
[KBHUD showInfo:@"拉取失败,使用本地演示"]; //
[self kb_startMockStreamingWithSeed:nil];
} else {
[self.streamTextView finishStreaming];
2025-11-12 13:43:48 +08:00
}
2025-11-12 14:18:56 +08:00
};
self.streamFetcher = fetcher;
[self.streamFetcher start];
2025-11-12 13:43:48 +08:00
}
2025-11-12 14:18:56 +08:00
- (void)kb_stopNetworkStreaming {
[self.streamFetcher cancel];
self.streamFetcher = nil;
self.streamHasOutput = NO;
2025-11-12 13:43:48 +08:00
}
2025-11-12 14:18:56 +08:00
#pragma mark - Helpers
/// KBStreamTextView
2025-11-12 15:31:22 +08:00
/// - KBStreamFetcher /t->\t \t
/// - UI
2025-11-12 14:18:56 +08:00
- (void)kb_appendChunkToStreamView:(NSString *)chunk {
if (chunk.length == 0 || !self.streamTextView) return;
2025-11-12 15:31:22 +08:00
[self.streamTextView appendStreamText:chunk];
2025-11-12 14:18:56 +08:00
self.streamHasOutput = YES;
2025-11-12 13:43:48 +08:00
}
2025-11-11 20:24:13 +08:00
// UL App Scheme访
// 访 KBStreamTextView
2025-10-30 20:23:34 +08:00
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
2025-11-11 20:24:13 +08:00
// + 访访
if ([[KBFullAccessManager shared] hasFullAccess]) {
NSString *title = (indexPath.item < self.itemsInternal.count) ? self.itemsInternal[indexPath.item] : @"";
[self kb_showStreamTextViewIfNeededWithTitle:title];
return;
}
2025-10-31 15:08:30 +08:00
[KBHUD showInfo:@"处理中…"];
2025-11-11 20:24:13 +08:00
2025-11-04 16:37:24 +08:00
UIInputViewController *ivc = KBFindInputViewController(self);
2025-10-30 20:23:34 +08:00
if (!ivc) return;
NSString *title = (indexPath.item < self.itemsInternal.count) ? self.itemsInternal[indexPath.item] : @"";
NSString *encodedTitle = [title stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]] ?: @"";
2025-10-30 20:46:54 +08:00
2025-10-30 20:53:44 +08:00
NSURL *ul = [NSURL URLWithString:[NSString stringWithFormat:@"%@?src=functionView&index=%ld&title=%@", KB_UL_LOGIN, (long)indexPath.item, encodedTitle]];
2025-10-30 20:46:54 +08:00
if (!ul) return;
2025-10-30 20:23:34 +08:00
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.05 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
2025-10-30 20:46:54 +08:00
[ivc.extensionContext openURL:ul completionHandler:^(BOOL ok) {
if (ok) return; // Universal Link
// 使 App Scheme
NSURL *scheme = [NSURL URLWithString:[NSString stringWithFormat:@"%@@//login?src=functionView&index=%ld&title=%@", KB_APP_SCHEME, (long)indexPath.item, encodedTitle]];
2025-10-30 20:46:54 +08:00
[ivc.extensionContext openURL:scheme completionHandler:^(BOOL ok2) {
if (ok2) return;
// openURL:
// 宿
BOOL bridged = NO;
@try {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability"
bridged = [KBURLOpenBridge openURLViaResponder:scheme from:self];
#pragma clang diagnostic pop
} @catch (__unused NSException *e) { bridged = NO; }
if (!bridged) {
2025-11-03 13:25:41 +08:00
// 访宿 Manager
dispatch_async(dispatch_get_main_queue(), ^{ [[KBFullAccessManager shared] ensureFullAccessOrGuideInView:self]; });
2025-10-30 20:46:54 +08:00
}
}];
2025-10-30 20:23:34 +08:00
}];
});
}
2025-10-28 14:30:03 +08:00
#pragma mark - Button Actions
2025-10-29 15:49:43 +08:00
- (void)onTapPaste {
//
// - iOS16+ App
// - iOS15
// viewDidLoad
UIPasteboard *pb = [UIPasteboard generalPasteboard];
NSString *text = pb.string; //
if (text.length > 0) {
//
self.pasteView.placeholderLabel.text = text;
//
// self.pasteView.placeholderLabel.numberOfLines = 0;
} else {
//
NSLog(@"粘贴板无可用文本或未授权粘贴");
}
}
#pragma mark -
//
// -
// - changeCount pasteboard.string
// * iOS16+
// * iOS15
// - / changeCount
- (void)startPasteboardMonitor {
// 访宿/
if (![[KBFullAccessManager shared] hasFullAccess]) return;
2025-10-29 15:49:43 +08:00
if (self.pasteboardTimer) return;
2025-11-10 15:38:30 +08:00
KBWeakSelf
2025-10-29 15:49:43 +08:00
self.pasteboardTimer = [NSTimer scheduledTimerWithTimeInterval:0.5 repeats:YES block:^(NSTimer * _Nonnull timer) {
__strong typeof(weakSelf) self = weakSelf; if (!self) return;
UIPasteboard *pb = [UIPasteboard generalPasteboard];
NSInteger cc = pb.changeCount;
if (cc <= self.lastHandledPBCount) return; //
self.lastHandledPBCount = cc; //
// iOS16+
NSString *text = pb.string;
if (text.length > 0) {
self.pasteView.placeholderLabel.text = text;
}
}];
}
- (void)stopPasteboardMonitor {
[self.pasteboardTimer invalidate];
self.pasteboardTimer = nil;
}
- (void)didMoveToWindow {
[super didMoveToWindow];
[self kb_refreshPasteboardMonitor];
2025-10-29 15:49:43 +08:00
}
- (void)setHidden:(BOOL)hidden {
BOOL wasHidden = self.isHidden;
[super setHidden:hidden];
if (wasHidden != hidden) {
[self kb_refreshPasteboardMonitor];
}
}
// 访
- (void)kb_refreshPasteboardMonitor {
BOOL visible = (self.window && !self.isHidden);
if (visible && [[KBFullAccessManager shared] hasFullAccess]) {
[self startPasteboardMonitor];
} else {
[self stopPasteboardMonitor];
2025-10-29 15:49:43 +08:00
}
}
- (void)kb_fullAccessChanged {
dispatch_async(dispatch_get_main_queue(), ^{ [self kb_refreshPasteboardMonitor]; });
}
2025-10-30 13:27:09 +08:00
- (void)onTapDelete {
NSLog(@"点击:删除");
2025-11-04 16:37:24 +08:00
UIInputViewController *ivc = KBFindInputViewController(self);
2025-10-30 13:27:09 +08:00
id<UITextDocumentProxy> proxy = ivc.textDocumentProxy;
[proxy deleteBackward];
}
- (void)onTapClear {
NSLog(@"点击:清空");
2025-10-30 18:28:36 +08:00
// pasteView
2025-11-04 16:37:24 +08:00
UIInputViewController *ivc = KBFindInputViewController(self);
2025-10-30 13:27:09 +08:00
id<UITextDocumentProxy> proxy = ivc.textDocumentProxy;
2025-10-30 18:28:36 +08:00
// documentContextBeforeInput 50
NSInteger guard = 0; //
while (guard < 10000) {
2025-10-30 13:27:09 +08:00
NSString *before = proxy.documentContextBeforeInput ?: @"";
2025-10-30 18:28:36 +08:00
NSInteger count = before.length;
if (count <= 0) { break; } //
2025-10-30 13:27:09 +08:00
for (NSInteger i = 0; i < count; i++) {
[proxy deleteBackward];
}
guard += count;
}
}
- (void)onTapSend {
NSLog(@"点击:发送");
// App
2025-11-04 16:37:24 +08:00
UIInputViewController *ivc = KBFindInputViewController(self);
2025-10-30 13:27:09 +08:00
id<UITextDocumentProxy> proxy = ivc.textDocumentProxy;
[proxy insertText:@"\n"];
}
2025-10-28 14:30:03 +08:00
#pragma mark - Lazy
- (KBFunctionBarView *)barViewInternal {
if (!_barViewInternal) {
_barViewInternal = [[KBFunctionBarView alloc] init];
2025-10-28 15:18:12 +08:00
_barViewInternal.delegate = self; // BarView
2025-10-28 14:30:03 +08:00
}
return _barViewInternal;
}
2025-10-28 15:18:12 +08:00
#pragma mark - KBFunctionBarViewDelegate
- (void)functionBarView:(KBFunctionBarView *)bar didTapLeftAtIndex:(NSInteger)index {
//
if ([self.delegate respondsToSelector:@selector(functionView:didTapToolActionAtIndex:)]) {
[self.delegate functionView:self didTapToolActionAtIndex:index];
}
}
- (void)functionBarView:(KBFunctionBarView *)bar didTapRightAtIndex:(NSInteger)index {
// /
}
2025-10-28 14:30:03 +08:00
- (KBFunctionPasteView *)pasteViewInternal {
if (!_pasteViewInternal) {
_pasteViewInternal = [[KBFunctionPasteView alloc] init];
}
return _pasteViewInternal;
}
- (UICollectionView *)collectionViewInternal {
if (!_collectionViewInternal) {
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.sectionInset = UIEdgeInsetsZero; //
_collectionViewInternal = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
_collectionViewInternal.backgroundColor = [UIColor clearColor];
_collectionViewInternal.dataSource = self;
_collectionViewInternal.delegate = self;
[_collectionViewInternal registerClass:[KBFunctionTagCell class] forCellWithReuseIdentifier:kKBFunctionTagCellId];
}
return _collectionViewInternal;
}
- (UIView *)rightButtonContainer {
if (!_rightButtonContainer) {
_rightButtonContainer = [[UIView alloc] init];
_rightButtonContainer.backgroundColor = [UIColor clearColor];
}
return _rightButtonContainer;
}
- (UIButton *)buildRightButtonWithTitle:(NSString *)title color:(UIColor *)color {
UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
btn.backgroundColor = color;
btn.layer.cornerRadius = 12.0;
btn.layer.masksToBounds = YES;
btn.titleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightSemibold];
[btn setTitle:title forState:UIControlStateNormal];
[btn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
return btn;
}
- (UIButton *)pasteButtonInternal {
if (!_pasteButtonInternal) {
_pasteButtonInternal = [self buildRightButtonWithTitle:@"粘贴" color:[UIColor colorWithRed:0.13 green:0.73 blue:0.60 alpha:1.0]];
[_pasteButtonInternal addTarget:self action:@selector(onTapPaste) forControlEvents:UIControlEventTouchUpInside];
}
return _pasteButtonInternal;
}
- (UIButton *)deleteButtonInternal {
if (!_deleteButtonInternal) {
//
_deleteButtonInternal = [UIButton buttonWithType:UIButtonTypeSystem];
_deleteButtonInternal.backgroundColor = [UIColor colorWithWhite:0.92 alpha:1.0];
_deleteButtonInternal.layer.cornerRadius = 12.0;
_deleteButtonInternal.layer.masksToBounds = YES;
_deleteButtonInternal.titleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightSemibold];
[_deleteButtonInternal setTitle:@"删除" forState:UIControlStateNormal];
[_deleteButtonInternal setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[_deleteButtonInternal addTarget:self action:@selector(onTapDelete) forControlEvents:UIControlEventTouchUpInside];
}
return _deleteButtonInternal;
}
- (UIButton *)clearButtonInternal {
if (!_clearButtonInternal) {
_clearButtonInternal = [UIButton buttonWithType:UIButtonTypeSystem];
_clearButtonInternal.backgroundColor = [UIColor colorWithWhite:0.92 alpha:1.0];
_clearButtonInternal.layer.cornerRadius = 12.0;
_clearButtonInternal.layer.masksToBounds = YES;
_clearButtonInternal.titleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightSemibold];
[_clearButtonInternal setTitle:@"清空" forState:UIControlStateNormal];
[_clearButtonInternal setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[_clearButtonInternal addTarget:self action:@selector(onTapClear) forControlEvents:UIControlEventTouchUpInside];
}
return _clearButtonInternal;
}
- (UIButton *)sendButtonInternal {
if (!_sendButtonInternal) {
_sendButtonInternal = [self buildRightButtonWithTitle:@"发送" color:[UIColor colorWithRed:0.13 green:0.73 blue:0.60 alpha:1.0]];
[_sendButtonInternal addTarget:self action:@selector(onTapSend) forControlEvents:UIControlEventTouchUpInside];
}
return _sendButtonInternal;
}
#pragma mark - Expose
- (UICollectionView *)collectionView { return self.collectionViewInternal; }
- (NSArray<NSString *> *)items { return self.itemsInternal; }
- (KBFunctionBarView *)barView { return self.barViewInternal; }
- (KBFunctionPasteView *)pasteView { return self.pasteViewInternal; }
- (UIButton *)pasteButton { return self.pasteButtonInternal; }
- (UIButton *)deleteButton { return self.deleteButtonInternal; }
- (UIButton *)clearButton { return self.clearButtonInternal; }
- (UIButton *)sendButton { return self.sendButtonInternal; }
2025-10-30 13:27:09 +08:00
#pragma mark - Find Owner Controller
2025-11-04 16:37:24 +08:00
// KBResponderUtils.h
2025-10-30 13:27:09 +08:00
2025-10-28 14:30:03 +08:00
@end