简体   繁体   English

iOS:UITextView与自定义NSTextStorage崩溃

[英]iOS: UITextView with custom NSTextStorage crashes

I am writing an iOS 9 application in Swift. 我正在Swift中编写一个iOS 9应用程序。 I have a view controller that hosts a UITextView and I am assigning a custom NSTextStorage object to it. 我有一个托管UITextView的视图控制器,我正在为它分配一个自定义的NSTextStorage对象。 The NSTextStorage is just basic right now. NSTextStorage现在是基本的。 Everything works correctly and the text shows up in the UITextView, but when I tap on the text to edit it, the application crashes with the following exception: 一切正常,文本显示在UITextView中,但是当我点击文本进行编辑时,应用程序崩溃时出现以下异常:

2016-01-10 11:24:32.931 PagesWriter[23750:6939530] requesting caretRectForPosition: with a position beyond the NSTextStorage (529)
2016-01-10 11:24:33.040 PagesWriter[23750:6939530] requesting caretRectForPosition: with a position beyond the NSTextStorage (529)
2016-01-10 11:24:33.168 PagesWriter[23750:6939530] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSCFString _getBlockStart:end:contentsEnd:forRange:stopAtLineSeparators:]: Range {529, 0} out of bounds; string length 245'
*** First throw call stack:
(0x181ea5900 0x181513f80 0x181ea5848 0x18278a02c 0x1827e963c 0x186cc6124 0x186b1c818 0x186cc5fbc 0x186d59e68 0x186bdeb60 0x186bde454 0x186fac1f8 0x187501f64 0x186bc2e84 0x186bc4f40 0x186fa65ac 0x186faea5c 0x186bc5c18 0x186bbf1f8 0x186bbed2c 0x186c2047c 0x186c20828 0x186d5811c 0x186d57f94 0x186d57448 0x187118dbc 0x186d3c5b8 0x186bca9b0 0x18711a3bc 0x186b89b58 0x186b868dc 0x186bc8820 0x186bc7e1c 0x186b984cc 0x186b96794 0x181e5cefc 0x181e5c990 0x181e5a690 0x181d89680 0x183298088 0x186c00d90 0x1000615ec 0x18192a8b8)
libc++abi.dylib: terminating with uncaught exception of type NSException

If I remove my custom NSTextStorage object from the UITextView, it works. 如果我从UITextView中删除我的自定义NSTextStorage对象,它的工作原理。

My view controller gets loaded using a modal segue in the storyboard. 我的视图控制器使用故事板中的模态segue加载。 I load the text content and configure the UITextView and NSTextStorage objects in the viewDidLoad method: 我加载文本内容并在viewDidLoad方法中配置UITextView和NSTextStorage对象:

class TextEditorViewController: UIViewController {
  @IBOutlet weak var textView: UITextView!

  // Set by parent view controller during transition
  var URL: NSURL?

  var textStorage: NSTextStorage?

  override func viewDidLoad() {
    super.viewDidLoad()

    textStorage = CustomTextStorage()
    textView.textStorage.removeLayoutManager(textView.layoutManager)
    textStorage!.addLayoutManager(textView.layoutManager)
    dispatch_async(UtilityQueue) {
      do {
        let text = try NSString(contentsOfURL: self.URL!, encoding: NSUTF8StringEncoding)
        dispatch_async(MainQueue) {
          self.textStorage!.replaceCharactersInRange(NSRange(location: 0, length: 0), withString: text as String)
        }
      } catch { /* handle error */ }
    }
  }
}

UtilityQueue and MainQueue are helper contents that hold a reference to the corresponding dispatch queue. UtilityQueueMainQueue是辅助内容,用于保存对相应调度队列的引用。

My custom NSTextStorage class looks like this: 我的自定义NSTextStorage类如下所示:

class CustomTextStorage: NSTextStorage {
  let backingStore = NSMutableAttributedString()

  override var string: String {
    return backingStore.string
  }

  override func attributedAtIndex(location: Int, effectiveRange range: NSRangePointer) -> [String: AnyObject] {
    return backingStore.attributesAtIndex(location, effectiveRange: range)
  }

  override func replaceCharactersInRange(range: NSRange, withString str: String) {
    backingStore.replaceCharactersInRange(range, withString: str)
    edited(.EditedCharacters, range: range, changeInLength: str.characters.count - range.length)
  }

  override func setAttributes(attires: [String: AnyObject]?, range: NSRange) {
    backingStore.setAttributes(attires, range: range)
    edited(.EditedAttributes, range: range, changeInLength: 0)
  }

  override func processEditing() {
    /* Placeholder; will be adding code here in the future. */
    super.processEditing()
  }
}

Any ideas? 有任何想法吗? Thank you in advance for your assistance. 提前感谢您的协助。

I don't know if it is still relevant, but you are almost right. 我不知道它是否仍然相关,但你几乎是对的。

You need to call beginEditing() and endEditing() respectively. 您需要分别调用beginEditing()endEditing()

Cast String to NSString in length calculation for editing callback (otherwise emoji and some other cases will not work properly). 在长度计算中将字符串转换为NSString以编辑回调(否则表情符号和其他一些情况将无法正常工作)。

Also please use NSTextStorage as a backingStore because of performance issues. 另外,由于性能问题,请使用NSTextStorage作为backingStore。

You will get something like this: 你会得到这样的东西:

final class ExampleStorage : NSTextStorage {

    private let container = NSTextStorage()

    override var string: String {
        return container.string
    }

    override func attributes(at location: Int, effectiveRange range: NSRangePointer?) -> [NSAttributedStringKey : Any] {
        return container.attributes(at: location, effectiveRange: range)
    }

    override func replaceCharacters(in range: NSRange, with str: String) {
        beginEditing()
        container.replaceCharacters(in: range, with: str)
        edited([.editedAttributes, .editedCharacters], range: range, changeInLength: (str as NSString).length - range.length)
        endEditing()
    }

    override func setAttributes(_ attrs: [NSAttributedStringKey : Any]?, range: NSRange) {
        beginEditing()
        container.setAttributes(attrs, range: range)
        edited([.editedAttributes], range: range, changeInLength: 0)
        endEditing()
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM