美文网首页
iOS拖拽控件到UITextView进行复制粘贴奔溃

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

作者: langkee | 来源:发表于2022-06-12 23:10 被阅读0次

    前言

    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,一定会奔溃。
    • 方案一和方案二都可以解决奔溃的问题,推荐方案一。

    附录

    相关文章

      网友评论

          本文标题:iOS拖拽控件到UITextView进行复制粘贴奔溃

          本文链接:https://www.haomeiwen.com/subject/pymnmrtx.html