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. #iOSDC2018
    .BSLEPXOΛ
    ϦΞϧλΠϜʹ
    ղੳ͢Δ

    View full-size slide

  2. !OBLBKJKBQBO
    4PGUXBSF&OHJOFFS
    $".1'*3&J04%$4UB⒎/,+*OUFSOFU
    J04"OESPJENBD04

    View full-size slide

  3. .BSLEPXOΛ
    ϦΞϧλΠϜʹ
    ղੳ͢Δ

    View full-size slide

  4. .BSLEPXOΛ
    ϦΞϧλΠϜʹ
    ղੳ͢Δ
    ͖͔͚ͬ

    View full-size slide

  5. Izumo
    Markdown Note Tool

    View full-size slide

  6. ͖͔͚ͬ
    wࣗ෼ʹͱͬͯྑ͍ΤσΟλ͕ͳ͔ͬͨ
    wϦονͷදݱํ๏ʹෆຬ
    w৭͚ͩมΘͬͯ͘ΕΕ͹͍͍
    wಉظͯ͠ཉ͍͠

    View full-size slide

  7. ΍Γ͍ͨ͜ͱ

    View full-size slide

  8. wΧϥʔϦϯά
    wฤूิࠤ

    View full-size slide

  9. ΧϥʔϦϯά
    ߦ໨͸λΠτϧߦɺݟग़͠ɺڧௐɺΠϯ
    ϥΠϯදࣔɺϦετɺ50%0Ϧετɺը૾
    ଧͪফ͠ɺςʔϒϧɺਫฏઢɺҾ༻ɺίʔ
    υϒϩοΫɺҾ༻

    View full-size slide

  10. ฤूิࠤ
    wࣗಈΠϯσϯτ
    wλϒͰͷϦετҠಈ

    View full-size slide

  11. IUUQTHJUIVCDPN
    TFBSDI
    RNBSLEPXOTXJGU

    View full-size slide

  12. wϦονͳදݱʹม׵ͯ͠͠·͏
    wશจΛಡΈࠐΉඞཁ͕͋Γ

    View full-size slide

  13. ࠷খݶͷൣғͰղੳ
    ॲཧΛߦ͍͍ͨ

    View full-size slide

  14. w͍ͭʁ
    wԿΛʁ
    wͲͷΑ͏ʹͯ͠ʁ

    View full-size slide

  15. wςΩετ͕ೖྗ͞Εͨ
    wॳظԽ͞Εͨ
    wϑΥϯτ͕มߋ͞Εͨ

    View full-size slide

  16. wςΩετ͕ೖྗ͞Εͨ
    wॳظԽ͞Εͨ
    wϑΥϯτ͕มߋ͞Εͨ

    View full-size slide

  17. /45FYU4UPSBHF%FMFHBUF

    View full-size slide

  18. // 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) {

    }
    }

    View full-size slide

  19. ม׵͢Δ΂͖ྖҬ

    View full-size slide

  20. // 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) {

    }
    }

    View full-size slide

  21. // 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) {

    }
    }
    ⚠ฤूͨ͠ྖҬ

    View full-size slide

  22. લ೔͸લ໷ࡇͩͬͨɻ
    ࠓ೔͸J04%$ͩ

    View full-size slide

  23. લ೔͸લ໷ࡇͩͬͨɻ
    ࠓ೔͸J04%$ͩʂ

    View full-size slide

  24. લ೔͸લ໷ࡇͩͬͨɻ
    ࠓ೔͸J04%$ͩ

    View full-size slide

  25. લ೔͸લ໷ࡇͩͬͨɻ
    ࠓ೔͸J04%$ͩʂ
    ໌೔΋J04%$ͩʂ
    ໌ޙ೔΋J04%$ͩ

    View full-size slide

  26. Ұؾʹෳ਺ߦ͕
    ฤू͞Εͨ৔߹
    Λߟྀ͢Δ

    View full-size slide

  27. ͲͷΑ͏ʹͯ͠ʁ

    View full-size slide

  28. ͲͷΑ͏ʹͯ͠ʁ
    wฤूͨ͠ൣғΛऔಘ͢Δ
    wߦຖʹ෼ׂ͢Δ
    wߦͷશͯͷൣғΛऔಘ͢Δ
    wม׵
    wશମͷஔ׵

    View full-size slide

  29. ͲͷΑ͏ʹͯ͠ʁ
    wฤूͨ͠ൣғΛऔಘ͢Δ
    wߦຖʹ෼ׂ͢Δ
    wߦͷશͯͷൣғΛऔಘ͢Δ
    wม׵
    wશମͷஔ׵
    ෳ਺ߦͷ৔߹͕͋Δ

    View full-size slide

  30. ͲͷΑ͏ʹͯ͠ʁ
    wฤूͨ͠ൣғΛऔಘ͢Δ
    wߦຖʹ෼ׂ͢Δ
    wߦͷશͯͷൣғΛऔಘ͢Δ
    wม׵
    wશମͷஔ׵

    View full-size slide

  31. લ೔͸લ໷ࡇͩͬͨɻ
    ࠓ೔͸J04%$ͩ

    View full-size slide

  32. લ೔͸લ໷ࡇͩͬͨɻ
    ࠓ೔͸J04%$ͩʂ

    View full-size slide

  33. લ೔͸લ໷ࡇͩͬͨɻ
    ࠓ೔͸J04%$ͩʂ

    View full-size slide

  34. લ೔͸લ໷ࡇͩͬͨɻ
    ࠓ೔͸J04%$ͩʂ
    ໌೔΋J04%$ͩʂ
    ໌ޙ೔΋J04%$ͩ

    View full-size slide

  35. લ೔͸લ໷ࡇͩͬͨɻ
    ࠓ೔͸J04%$ͩʂ
    ໌೔΋J04%$ͩʂ
    ໌ޙ೔΋J04%$ͩ

    View full-size slide

  36. func rangeOfCurrentLine(location: Int) -> NSRange? {
    let string = internalTextStorage.string
    let endNSRange = NSRange(location: location, length: 0)
    let lineRange: Range
    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..return lineNSRange
    }

    View full-size slide

  37. func rangeOfCurrentLine(location: Int) -> NSRange? {
    let string = internalTextStorage.string
    let endNSRange = NSRange(location: location, length: 0)
    let lineRange: Range
    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..return lineNSRange
    }

    View full-size slide

  38. func rangeOfCurrentLine(location: Int) -> NSRange? {
    let string = internalTextStorage.string
    let endNSRange = NSRange(location: location, length: 0)
    let lineRange: Range
    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..return lineNSRange
    }
    վߦͰ෼ׂͨ͠ߦͷ࠷ॳͷҐஔΛऔಘ

    View full-size slide

  39. func rangeOfCurrentLine(location: Int) -> NSRange? {
    let string = internalTextStorage.string
    let endNSRange = NSRange(location: location, length: 0)
    let lineRange: Range
    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..return lineNSRange
    }
    վߦͰ෼ׂͨ͠ߦͷ࠷ॳͷҐஔΛऔಘ
    લ೔͸લ໷ࡇͩͬͨɻ
    ࠓ೔͸J04%$ͩʂ
    ໌೔΋J04%$ͩʂ
    ໌ޙ೔΋J04%$ͩ

    View full-size slide

  40. func rangeOfCurrentLine(location: Int) -> NSRange? {
    let string = internalTextStorage.string
    let endNSRange = NSRange(location: location, length: 0)
    let lineRange: Range
    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..return lineNSRange
    }
    ର৅ͷߦશମΛऔಘ
    վߦͰ෼ׂͨ͠ߦͷ࠷ॳͷҐஔΛऔಘ

    View full-size slide

  41. func rangeOfCurrentLine(location: Int) -> NSRange? {
    let string = internalTextStorage.string
    let endNSRange = NSRange(location: location, length: 0)
    let lineRange: Range
    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..return lineNSRange
    }
    ର৅ͷߦશମΛऔಘ
    վߦͰ෼ׂͨ͠ߦͷ࠷ॳͷҐஔΛऔಘ
    લ೔͸લ໷ࡇͩͬͨɻ
    ࠓ೔͸J04%$ͩʂ
    ໌೔΋J04%$ͩʂ
    ໌ޙ೔΋J04%$ͩ

    View full-size slide

  42. lineRange(for:)
    Returns the range of characters representing the line or lines
    containing a given range.
    func lineRange(for range: NSRange) -> NSRange


    View full-size slide

  43. ͲͷΑ͏ʹͯ͠ʁ
    wฤूͨ͠ൣғΛऔಘ͢Δ
    wߦຖʹ෼ׂ͢Δ
    wߦͷશͯͷൣғΛऔಘ͢Δ
    wม׵
    wશମͷஔ׵

    View full-size slide

  44. ͲͷΑ͏ʹͯ͠ʁ
    wฤूͨ͠ൣғΛऔಘ͢Δ
    wߦຖʹ෼ׂ͢Δ
    wߦͷશͯͷൣғΛऔಘ͢Δ
    wม׵
    wશମͷஔ׵ ίʔυϒϩοΫ͸શ
    ମΛݕࡧඞཁ͕͋Δ

    View full-size slide

  45. ͲͷΑ͏ʹͯ͠ʁ
    wฤूͨ͠ൣғΛऔಘ͢Δ
    wߦຖʹ෼ׂ͢Δ
    wߦͷશͯͷൣғΛऔಘ͢Δ
    wม׵
    wશମͷஔ׵
    ޙ͸֘౰͢ΔจࣈྻΛந
    ग़ͯ͠ద੾ͳ৭ʹม׵͢
    Δ͚ͩɻ

    View full-size slide

  46. ࠷খݶͷൣғͰղ
    ੳ͢Δ͜ͱ͕େ੾

    View full-size slide

  47. ֆจࣈ
    w/44USJOHɺ/43BOHFͷར༻࣌
    w୯७ʹจࣈྻΛΧ΢ϯτͯ͠͸❌
    w65'Λҙࣝͨ͠จࣈྻૢ࡞Λ͢Δ

    View full-size slide


  48. 6' Y%% Y%&
    6OJDPEF 65'

    View full-size slide

  49. "཮".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

    View full-size slide

  50. w಺෦తʹ͸65'Ͱό
    ΠτྻΛ؅ཧ
    /44USJOH

    View full-size slide

  51. 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!"

    View full-size slide

  52. NSRange(location: 0, length: line.utf16.count)

    View full-size slide

  53. wόΠτྻ͸Ӆณ
    4USJOH

    View full-size slide

  54. let snowy = "❄ Let it snow! ☃"
    print(snowy.prefix(5))
    // Prints "❄ Let"
    print(snowy.suffix(1))
    // Prints "☃"

    View full-size slide

  55. ೖྗิࠤ

    View full-size slide

  56. wΦʔτΠϯσϯτ
    wλϒͰͷҠಈ

    View full-size slide

  57. ΠϕϯτϋϯυϦϯά
    wλϒ
    wόοΫλϒ
    wվߦ

    View full-size slide

  58. w/45FYU7JFX
    w6*5FYU7JFX

    View full-size slide

  59. ઐ༻ͷؔ਺
    ͕༻ҙ͞Ε͍ͯΔ

    View full-size slide

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

    View full-size slide

  61. 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)
    }
    }

    View full-size slide

  62. 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)
    }
    }
    Πϯσϯτର৅ͱͳ
    Δઌ಄෦෼Λऔಘ

    View full-size slide

  63. લ೔͸લ໷ࡇͩͬͨɻ
    ࠓ೔͸J04%$ͩ
    લ೔͸લ໷ࡇͩͬͨɻ
    ࠓ೔͸J04%$ͩ

    View full-size slide

  64. 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)
    }
    }
    ਌ॲཧʹ೚ͤΔ

    View full-size slide

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

    View full-size slide

  66. class NSTextView {
    func insertTab(_ sender: Any?)
    func insertBacktab(_ sender: Any?)
    func insertNewline(_ sender: Any?)
    }
    public protocol NSTextInputClient {
    public func insertText(_ string: Any, replacementRange: NSRange)
    }
    • ΩʔϘʔυʹλϒ͸ແ͍
    • λϒϘλϯͰಉ౳ͷػೳΛ࣮૷

    View full-size slide

  67. insertTab insertBackTab

    View full-size slide

  68. ςΩετૠೖ࣌

    View full-size slide

  69. 6*5FYU7JFXͷࢠΫϥε
    let stringRange = lineRange.range(string: text)!
    text.insert(contentsOf: indent, at: stringRange.lowerBound)

    View full-size slide

  70. 6*5FYU7JFXͷࢠΫϥε
    let stringRange = lineRange.range(string: text)!
    text.insert(contentsOf: indent, at: stringRange.lowerBound)

    View full-size slide

  71. UITextViewͷࢠΫϥε
    let stringRange = lineRange.range(string: text)!
    text.insert(contentsOf: indent, at: stringRange.lowerBound)
    wFEJUFE3BOHFͰฤूൣғ͕શͯʹͳͬ
    ͯ͠·͏
    wUFYU%JE$IBOHF͸ίʔϧ͞Εͳ͍
    wظ଴͞ΕͨҐஔʹΧʔιϧ͕Ҡಈ͞Ε
    ͳ͍

    View full-size slide

  72. public protocol UIKeyInput : UITextInputTraits {
    public var hasText: Bool { get }
    public func insertText(_ text: String)
    public func deleteBackward()
    }

    View full-size slide

  73. 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
    }
    }

    View full-size slide

  74. 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
    }
    } Χʔιϧ͕ظ଴ͨ͠Ґஔʹ͍ͳ͍ͷͰઌ಄ʹҠಈɺૠೖޙɺ
    ૠೖޙͷҐஔʹҠಈ͢Δඞཁ͕͋Δɻ

    View full-size slide

  75. ·ͱΊ
    wղੳ͸ͳΔ΂͘࠷খݶͷൣғͰղੳ͢Δ

    View full-size slide

  76. ·ͱΊ
    wղੳ͸ͳΔ΂͘࠷খݶͷൣғͰߦ͏
    wจࣈίʔυΛҙࣝͨ͠จࣈྻૢ࡞
    w⚠/44USJOH /43BOHF

    View full-size slide

  77. ·ͱΊ
    wղੳ͸ͳΔ΂͘࠷খݶͷൣғͰߦ͏
    wจࣈίʔυΛҙࣝͨ͠จࣈྻૢ࡞
    w⚠/44USJOH /43BOHF
    wೖྗิࠤ࣌ͷJ04Ͱͷઃܭ
    wΧελϚΠζ͕ඞਢ

    View full-size slide