Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Markdownをリアルタイムに解析する

 Markdownをリアルタイムに解析する

Markdownを解析する時にGitHubで探せば簡単にライブラリを何個も見つけることができます。しかし、をそれをリアルタイムとなるとなかなかみつけることができません。私は端末間で同期できるメモアプリを作成し、機能の一つに入力しながらMarkdown形式に色付けをする機能を実装しました。本トークではリアルタイムに文章を解析し、リッチな表現をどのようにして行っているのかを解説します。

https://fortee.jp/iosdc-japan-2018/proposal/a2e20820-d4f6-43e5-b34b-1b9e6fec7806

nakajijapan

August 31, 2018
Tweet

More Decks by nakajijapan

Other Decks in Technology

Transcript

  1. // MARK: - NSTextStorageDelegate extension TextViewController: NSTextStorageDelegate {  func

    textStorage(_ textStorage: NSTextStorage, willProcessEditing editedMask: NSTextStorageEditActions, range editedRange: NSRange, changeInLength delta: Int) { self.editedRange = editedRange } func textStorage(_ textStorage: NSTextStorage, didProcessEditing editedMask: NSTextStorageEditActions, range editedRange: NSRange, changeInLength delta: Int) {  } }
  2. // MARK: - NSTextStorageDelegate extension TextViewController: NSTextStorageDelegate {  func

    textStorage(_ textStorage: NSTextStorage, willProcessEditing editedMask: NSTextStorageEditActions, range editedRange: NSRange, changeInLength delta: Int) { self.editedRange = editedRange } func textStorage(_ textStorage: NSTextStorage, didProcessEditing editedMask: NSTextStorageEditActions, range editedRange: NSRange, changeInLength delta: Int) {  } }
  3. // MARK: - NSTextStorageDelegate extension TextViewController: NSTextStorageDelegate {  func

    textStorage(_ textStorage: NSTextStorage, willProcessEditing editedMask: NSTextStorageEditActions, range editedRange: NSRange, changeInLength delta: Int) { self.editedRange = editedRange } func textStorage(_ textStorage: NSTextStorage, didProcessEditing editedMask: NSTextStorageEditActions, range editedRange: NSRange, changeInLength delta: Int) {  } } ⚠ฤूͨ͠ྖҬ
  4. func rangeOfCurrentLine(location: Int) -> NSRange? { let string = internalTextStorage.string

    let endNSRange = NSRange(location: location, length: 0) let lineRange: Range<String.Index> if let endRange = endNSRange.range(string: string) { lineRange = string.lineRange(for: endRange) } else { return nil } let lower = String.UTF16View.Index(lineRange.lowerBound, within: string.utf16)! let upper = String.UTF16View.Index(lineRange.upperBound, within: string.utf16)! let lineNSRange = NSRange(lower..<upper, in: string) return lineNSRange }
  5. func rangeOfCurrentLine(location: Int) -> NSRange? { let string = internalTextStorage.string

    let endNSRange = NSRange(location: location, length: 0) let lineRange: Range<String.Index> if let endRange = endNSRange.range(string: string) { lineRange = string.lineRange(for: endRange) } else { return nil } let lower = String.UTF16View.Index(lineRange.lowerBound, within: string.utf16)! let upper = String.UTF16View.Index(lineRange.upperBound, within: string.utf16)! let lineNSRange = NSRange(lower..<upper, in: string) return lineNSRange }
  6. func rangeOfCurrentLine(location: Int) -> NSRange? { let string = internalTextStorage.string

    let endNSRange = NSRange(location: location, length: 0) let lineRange: Range<String.Index> if let endRange = endNSRange.range(string: string) { lineRange = string.lineRange(for: endRange) } else { return nil } let lower = String.UTF16View.Index(lineRange.lowerBound, within: string.utf16)! let upper = String.UTF16View.Index(lineRange.upperBound, within: string.utf16)! let lineNSRange = NSRange(lower..<upper, in: string) return lineNSRange } վߦͰ෼ׂͨ͠ߦͷ࠷ॳͷҐஔΛऔಘ
  7. func rangeOfCurrentLine(location: Int) -> NSRange? { let string = internalTextStorage.string

    let endNSRange = NSRange(location: location, length: 0) let lineRange: Range<String.Index> if let endRange = endNSRange.range(string: string) { lineRange = string.lineRange(for: endRange) } else { return nil } let lower = String.UTF16View.Index(lineRange.lowerBound, within: string.utf16)! let upper = String.UTF16View.Index(lineRange.upperBound, within: string.utf16)! let lineNSRange = NSRange(lower..<upper, in: string) return lineNSRange } վߦͰ෼ׂͨ͠ߦͷ࠷ॳͷҐஔΛऔಘ લ೔͸લ໷ࡇͩͬͨɻ  ࠓ೔͸J04%$ͩʂ ໌೔΋J04%$ͩʂ ໌ޙ೔΋J04%$ͩ
  8. func rangeOfCurrentLine(location: Int) -> NSRange? { let string = internalTextStorage.string

    let endNSRange = NSRange(location: location, length: 0) let lineRange: Range<String.Index> if let endRange = endNSRange.range(string: string) { lineRange = string.lineRange(for: endRange) } else { return nil } let lower = String.UTF16View.Index(lineRange.lowerBound, within: string.utf16)! let upper = String.UTF16View.Index(lineRange.upperBound, within: string.utf16)! let lineNSRange = NSRange(lower..<upper, in: string) return lineNSRange } ର৅ͷߦશମΛऔಘ վߦͰ෼ׂͨ͠ߦͷ࠷ॳͷҐஔΛऔಘ
  9. func rangeOfCurrentLine(location: Int) -> NSRange? { let string = internalTextStorage.string

    let endNSRange = NSRange(location: location, length: 0) let lineRange: Range<String.Index> if let endRange = endNSRange.range(string: string) { lineRange = string.lineRange(for: endRange) } else { return nil } let lower = String.UTF16View.Index(lineRange.lowerBound, within: string.utf16)! let upper = String.UTF16View.Index(lineRange.upperBound, within: string.utf16)! let lineNSRange = NSRange(lower..<upper, in: string) return lineNSRange } ର৅ͷߦશମΛऔಘ վߦͰ෼ׂͨ͠ߦͷ࠷ॳͷҐஔΛऔಘ લ೔͸લ໷ࡇͩͬͨɻ  ࠓ೔͸J04%$ͩʂ ໌೔΋J04%$ͩʂ ໌ޙ೔΋J04%$ͩ
  10. lineRange(for:) Returns the range of characters representing the line or

    lines containing a given range. func lineRange(for range: NSRange) -> NSRange ศ ར
  11. "཮".count // 1 "཮".unicodeScalars.count // 1 "཮".utf16.count // 1 "཮".utf8.count

    // 3 "".count // 1 "".unicodeScalars.count // 1 "".utf16.count // 2 "".utf8.count // 4 ")".count // 1 ")".unicodeScalars.count // 2 ")".utf16.count // 4 ")".utf8.count // 8
  12. let snowy = "❄ Let it snow! ☃" let nsrange

    = NSRange(location: 3, length: 12) if let range = Range(nsrange, in: snowy) { print(snowy[range]) } // Prints "Let it snow!"
  13. let snowy = "❄ Let it snow! ☃" print(snowy.prefix(5)) //

    Prints "❄ Let" print(snowy.suffix(1)) // Prints "☃"
  14. class NSTextView { func insertTab(_ sender: Any?) func insertBacktab(_ sender:

    Any?) func insertNewline(_ sender: Any?) } public protocol NSTextInputClient { public func insertText(_ string: Any, replacementRange: NSRange) } طʹظ଴͞Εͨؔ਺͕༻ҙ͞Ε͍ͯΔ ΧελϚΠζͨ͠จࣈྻΛૠೖ͢Δ
  15. class TextView: NS { override func insertTab(_ sender: Any?) {

    var indent = "" let (spaces, lineRange) = getPrefixStringIfLineHasListTag(regex: TextRegex.insertTab) if useIndentWithSpaceKey { indent = String(repeating: " ", count: indentWidth) if !spaces.isEmpty { super.insertText(indent, replacementRange: NSRange(location: lineRange.location, length: 0)) return } super.insertText(indent, replacementRange: self.rangeForUserTextChange) return } indent = "\t" if !spaces.isEmpty { super.insertText(indent, replacementRange: NSRange(location: lineRange.location, length: 0)) return } super.insertTab(sender) } }
  16. class TextView: NS { override func insertTab(_ sender: Any?) {

    var indent = "" let (spaces, lineRange) = getPrefixStringIfLineHasListTag(regex: TextRegex.insertTab) if useIndentWithSpaceKey { indent = String(repeating: " ", count: indentWidth) if !spaces.isEmpty { super.insertText(indent, replacementRange: NSRange(location: lineRange.location, length: 0)) return } super.insertText(indent, replacementRange: self.rangeForUserTextChange) return } indent = "\t" if !spaces.isEmpty { super.insertText(indent, replacementRange: NSRange(location: lineRange.location, length: 0)) return } super.insertTab(sender) } } Πϯσϯτର৅ͱͳ Δઌ಄෦෼Λऔಘ
  17. class TextView: NS { override func insertTab(_ sender: Any?) {

    var indent = "" let (spaces, lineRange) = getPrefixStringIfLineHasListTag(regex: TextRegex.insertTab) if useIndentWithSpaceKey { indent = String(repeating: " ", count: indentWidth) if !spaces.isEmpty { super.insertText(indent, replacementRange: NSRange(location: lineRange.location, length: 0)) return } super.insertText(indent, replacementRange: self.rangeForUserTextChange) return } indent = "\t" if !spaces.isEmpty { super.insertText(indent, replacementRange: NSRange(location: lineRange.location, length: 0)) return } super.insertTab(sender) } } ਌ॲཧʹ೚ͤΔ
  18. class NSTextView { func insertTab(_ sender: Any?) func insertBacktab(_ sender:

    Any?) func insertNewline(_ sender: Any?) } public protocol NSTextInputClient { public func insertText(_ string: Any, replacementRange: NSRange) } ༻ҙ͞Ε͍ͯͳ͍ɻɻɻ
  19. class NSTextView { func insertTab(_ sender: Any?) func insertBacktab(_ sender:

    Any?) func insertNewline(_ sender: Any?) } public protocol NSTextInputClient { public func insertText(_ string: Any, replacementRange: NSRange) } • ΩʔϘʔυʹλϒ͸ແ͍ • λϒϘλϯͰಉ౳ͷػೳΛ࣮૷
  20. UITextViewͷࢠΫϥε let stringRange = lineRange.range(string: text)! text.insert(contentsOf: indent, at: stringRange.lowerBound)

    wFEJUFE3BOHFͰฤूൣғ͕શͯʹͳͬ ͯ͠·͏ wUFYU%JE$IBOHF͸ίʔϧ͞Εͳ͍ wظ଴͞ΕͨҐஔʹΧʔιϧ͕Ҡಈ͞Ε ͳ͍
  21. public protocol UIKeyInput : UITextInputTraits { public var hasText: Bool

    { get } public func insertText(_ text: String) public func deleteBackward() }
  22. func insertTab(rangeForTextChange: NSRange) { var indent = "" let (spaces,

    lineRange) = getPrefixStringIfLineHasList(regex: TextInputRegex.insertTab, rangeForTextChange: rangeForTextChange) if useIndentWithSpaceKey { indent = String(repeating: " ", count: indentWidth) if !spaces.isEmpty { selectedRange = NSRange(location: lineRange.location, length: 0) insertText(indent) selectedRange = NSRange(location: rangeForTextChange.location + indentWidth, length: 0) return } super.insertText(indent) return } indent = "\t" if !spaces.isEmpty { selectedRange = NSRange(location: lineRange.location, length: 0) insertText(indent) selectedRange = NSRange(location: rangeForTextChange.location + 1, length: 0) return } }
  23. func insertTab(rangeForTextChange: NSRange) { var indent = "" let (spaces,

    lineRange) = getPrefixStringIfLineHasList(regex: TextInputRegex.insertTab, rangeForTextChange: rangeForTextChange) if useIndentWithSpaceKey { indent = String(repeating: " ", count: indentWidth) if !spaces.isEmpty { selectedRange = NSRange(location: lineRange.location, length: 0) insertText(indent) selectedRange = NSRange(location: rangeForTextChange.location + indentWidth, length: 0) return } super.insertText(indent) return } indent = "\t" if !spaces.isEmpty { selectedRange = NSRange(location: lineRange.location, length: 0) insertText(indent) selectedRange = NSRange(location: rangeForTextChange.location + 1, length: 0) return } } Χʔιϧ͕ظ଴ͨ͠Ґஔʹ͍ͳ͍ͷͰઌ಄ʹҠಈɺૠೖޙɺ ૠೖޙͷҐஔʹҠಈ͢Δඞཁ͕͋Δɻ