iOS拖拽控件到UITextView进行复制粘贴奔溃

前言

iOS 15以后,我们可以通过拖拽一个控件(UITextFieldUITextView)的形式将其内容复制粘贴到另一个UITextView,在拖拽之前,如果UITextView的内容为空,例如:@""@" "(含空格),在设置代理- (void)textViewDidChange:(UITextView *)textView时,并且在代理中改变UITextView的内容后, 或者 - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text返回NO如果不做特殊处理的话,一定会发生奔溃!

主要奔溃信息是:NSInternalInconsistencyException: Invalid parameter not satisfying: pos

奔溃详情如下

CrashDoctor Diagnosis: Application threw exception NSInternalInconsistencyException: Invalid parameter not satisfying: pos
Thread 0 Crashed:
0   CoreFoundation                      0x00000001a8909d78 __exceptionPreprocess
1   libobjc.A.dylib                     0x00000001c156e734 objc_exception_throw
2   Foundation                          0x00000001aa18f1f0 _userInfoForFileAndLine
3   UIKitCore                           0x00000001abe850d4 -[_UITextKitTextPosition compare:]
4   UIKitCore                           0x00000001aad027d0 -[UITextInputController comparePosition:toPosition:]
5   UIKitCore                           0x00000001abe90a9c -[UITextView comparePosition:toPosition:]
6   UIKitCore                           0x00000001abe58dec -[UITextPasteController _clampRange:]
7   UIKitCore                           0x00000001abe595d8 __87-[UITextPasteController _performPasteOfAttributedString:toRange:forSession:completion:]_block_invoke
8   UIKitCore                           0x00000001abe597f0 __87-[UITextPasteController _performPasteOfAttributedString:toRange:forSession:completion:]_block_invoke.177
9   UIKitCore                           0x00000001abe81204 -[UITextInputController _pasteAttributedString:toRange:completion:]
10  UIKitCore                           0x00000001abe59524 -[UITextPasteController _performPasteOfAttributedString:toRange:forSession:completion:]
11  UIKitCore                           0x00000001abe587c4 __49-[UITextPasteController _executePasteForSession:]_block_invoke
12  libdispatch.dylib                   0x00000001a856ee68 _dispatch_call_block_and_release
13  libdispatch.dylib                   0x00000001a8570a2c _dispatch_client_callout
14  libdispatch.dylib                   0x00000001a857ef48 _dispatch_main_queue_drain
15  libdispatch.dylib                   0x00000001a857eb98 _dispatch_main_queue_callback_4CF
....................................................................

虽然这种情况很特殊,很少有用户去这样复制粘贴内容,但是一旦用户量大的话,或者用户总是使用这种方式去复制粘贴的话,奔溃的影响还是很大的。

例如,我们将第一个UITextView拖拽并复制内容Hello world到另一个UITextView,即发生奔溃,奔溃演示动画: 动画演示链接

环境

  • Xcode 13.2.1
  • iPhone 13 Pro Max(模拟器)
  • iOS 15.2
  • M1 macOS Monterey 12.2.1

代码重现

新建一个工程,代码如下

@interface ViewController ()<UITextViewDelegate>

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = UIColor.whiteColor;
    
    UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(50, 100, 120, 50)];
    textView.text = @"Hello world";
    textView.backgroundColor = UIColor.greenColor;
    textView.font = [UIFont systemFontOfSize:19];
    [self.view addSubview:textView];
    
    UITextView *textView1 = [[UITextView alloc] initWithFrame:CGRectMake(50, 180, 250, 200)];
    textView1.delegate = self;
    textView1.backgroundColor = UIColor.cyanColor;
    [self.view addSubview:textView1];
}

// MARK: - UITextViewDelegate

- (void)textViewDidChange:(UITextView *)textView {
    textView.text = @"Hello";
}

@end

在上面的代码中,我们拖拽textView并复制其内容Hello worldtextView1,并在- (void)textViewDidChange:(UITextView *)textView中设置textView.text = @"Hello";,即改变textView1的内容。然后,运行项目,拖拽textViewtextView1,即发生奔溃。

解决

目前,可以有2种方案解决。

方案一(推荐)

对要复制到的UITextViewtextView1设置代理UITextPasteDelegate,如下

@interface ViewController ()<UITextViewDelegate, UITextPasteDelegate>

@end
 
textView1.pasteDelegate = self;

实现代理方法

// MARK: - UITextPasteDelegate

// Fix a crash where app will crash on iOS 15 or later if the user drags the UITextField or UITextView control to copy and paste.
- (UITextRange *)textPasteConfigurationSupporting:(id<UITextPasteConfigurationSupporting>)textPasteConfigurationSupporting
                   performPasteOfAttributedString:(NSAttributedString *)attributedString
                                          toRange:(UITextRange *)textRange {
    if ([textPasteConfigurationSupporting isKindOfClass:[UITextView class]]) {
        [(UITextView *)textPasteConfigurationSupporting replaceRange:textRange withText:attributedString.string];
    }
    return textRange;
}

在这个复制粘贴代理方法中,我们通过在复制粘贴的时候,对UITextView要复制粘贴的内容进行替换,就可以解决奔溃的问题。

方案二(不推荐)

方案一唯一不同的是代理方法中的处理方式

- (UITextRange *)textPasteConfigurationSupporting:(id<UITextPasteConfigurationSupporting>)textPasteConfigurationSupporting
                   performPasteOfAttributedString:(NSAttributedString *)attributedString
                                          toRange:(UITextRange *)textRange {
    if ([textPasteConfigurationSupporting isKindOfClass:[UITextView class]] && UIPasteboard.generalPasteboard.hasStrings) { 
        [(UITextView *)textPasteConfigurationSupporting insertText:UIPasteboard.generalPasteboard.string ?: @""];
    }
    return textRange;
}

不推荐方案二的原因是,虽然避免了奔溃,我们无法获取到被拖拽的控件textView中的内容,也就是即使我们拖拽textViewtextView1textView1的内容依旧不会变化。但是要特别注意,有一种情况会变化,如果我们在手机中的任何地方已经复制了文本,然后通过拖拽textViewtextView1textView1会获取到已经复制的文本!例如,我们已经在短信中复制了“好的”,然后在应用中拖拽textViewtextView1,如果textView的内容为空,textView1的内容就会变成“好的”,如果不为空,例如textView的内容为明白,,那么textView1的内容就会变成“明白,好的”也就是说,通过方案二,拖拽控件永远只是执行粘贴操作

注意:方案二需要用真机进行测试。

完整代码

@interface ViewController ()<UITextViewDelegate, UITextPasteDelegate>

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = UIColor.whiteColor;
    
    UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(50, 100, 120, 50)];
    textView.text = @"Hello world";
    textView.backgroundColor = UIColor.greenColor;
    textView.font = [UIFont systemFontOfSize:19];
    [self.view addSubview:textView];
    
    UITextView *textView1 = [[UITextView alloc] initWithFrame:CGRectMake(50, 180, 250, 200)];
    textView1.delegate = self; 
    textView1.pasteDelegate = self;
    textView1.backgroundColor = UIColor.cyanColor;
    [self.view addSubview:textView1];
}

// MARK: - UITextViewDelegate

- (void)textViewDidChange:(UITextView *)textView {
    if (textView.text.length > 2) {
        textView.text = [textView.text substringFromIndex:2];
    } else {
        textView.text = @"";
    }
}

// MARK: - UITextPasteDelegate

//// Fix a crash where app will crash on iOS 15 or later if the user drags the UITextField or UITextView control to copy and paste.
- (UITextRange *)textPasteConfigurationSupporting:(id<UITextPasteConfigurationSupporting>)textPasteConfigurationSupporting
                   performPasteOfAttributedString:(NSAttributedString *)attributedString
                                          toRange:(UITextRange *)textRange {
    if ([textPasteConfigurationSupporting isKindOfClass:[UITextView class]]) {
        [(UITextView *)textPasteConfigurationSupporting replaceRange:textRange withText:attributedString.string];
    }
    return textRange;
}

/**
- (UITextRange *)textPasteConfigurationSupporting:(id<UITextPasteConfigurationSupporting>)textPasteConfigurationSupporting
                   performPasteOfAttributedString:(NSAttributedString *)attributedString
                                          toRange:(UITextRange *)textRange {
    if ([textPasteConfigurationSupporting isKindOfClass:[UITextView class]] && UIPasteboard.generalPasteboard.hasStrings) {
        [(UITextView *)textPasteConfigurationSupporting insertText:UIPasteboard.generalPasteboard.string ?: @""];
    }
    return textRange;
}
*/

@end

小结

  • 拖拽前,如果要复制的UITextView(这里textView1)内容不为空(含空格),如"a"、"123",无论是在- (void)textViewDidChange:(UITextView *)textView中改变内容或者- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text返回NO,一定不会奔溃。
  • 拖拽前,如果要复制的UITextView(这里textView1)内容为空(如""、" "),无论是在- (void)textViewDidChange:(UITextView *)textView中改变内容或者- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text返回NO,一定会奔溃。
  • 方案一和方案二都可以解决奔溃的问题,推荐方案一。

附录

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容