Contents

bdewey/textmarkupkit

Many iOS applications give you the ability to write plain text in a UITextView and format that text based upon simple rules. TextMarkupKit makes it easy to add "format as you type" capabilities to any iOS application.

Installation

Install TextMarkupKit using Swift Package Manager.

  dependencies: [
    .package(url: "https://github.com/bdewey/TextMarkupKit", from: "0.7.0"),
  ],

Please note that TextMarkupKit is not yet at Version 1.0.0 -- the API is changing frequently and dramatically as I adopt code written for one specific application for general use.

Using TextMarkupKit -- the absolute basics

While TextMarkupKit is designed to support custom formatting and custom markup languages, you can get started with a subset of Markdown out-of-the box. Using UIKit:

import TextMarkupKit
import UIKit

// textStorage will hold the characters and formatting (determined by the markup rules).
//
// MiniMarkdownGrammar.defaultEditingStyle():
// - Tells `ParsedAttributedString` to use the rules of MiniMarkdownGrammar to parse the text
// - Provides a default set of formatters to style the parsed text.
let textStorage = ParsedAttributedString(string: "# Hello, world!\n", style: MiniMarkdownGrammar.defaultEditingStyle())

// MarkupFormattingTextView is a subclass of UITextView and you can use it anywhere you would use a UITextView.
let textView = MarkupFormattingTextView(parsedAttributedString: textStorage)

Using SwiftUI:

import SwiftUI
import TextMarkupKit

struct ContentView: View {
  @Binding var document: TextMarkupKitSampleDocument

  var body: some View {
    // `MarkupFormattedTextEditor` is a SwiftUI wrapper around `MarkupFormattingTextView` that commits its changes back to the
    // text binding when editing is complete. By default it uses `MiniMarkdownGrammar.defaultEditingStyle()`, but you can provide
    // a custom style with the `style:` parameter.
    MarkupFormattedTextEditor(text: $document.text)
  }
}

That's it! You now have a view that will format plain text and automatically adjust as the content changes. Check out the sample application to see this in action.

[TextMarkupKit Sample App]

Further Reading

Since this project started for personal use, documentation is sparse. While I build it up, this is an overview of the important areas of code.

Parsing

  • ParsingRule is an abstract base class. The job of a parsing rule is to evaluate the input text at specific offset and produce a ParsingResult, which is a struct that indicates:

- If the parsing rule succeeded at that location - If the rule succeeded, how much of the input string is consumed by the rule. Parsing continues after the consumed input. - How much of the input string the ParsingRule had to look at at to make its success/fail decision.

  • PackratGrammar is a protocol something that defines a complete grammar through a graph of ParsingRules. PackratGrammar exposes a single rule, start, that will be used when attempting to parse a string.
  • MemoizationTable implements the core incremental packrat parsing algorithm.

Additionally, ParsingRule.swift defines many simple rules that you can combine to build much more complex rules for constructing your grammar.

  • DotRule matches any character.
  • Characters matches any character defined by a CharacterSet.
  • Literal matches a string literal.
  • InOrder takes an array of child rules and succeeds if every one of the child rules succeeds in sequence.
  • Choice also takes an array of child rules, but matches the first of the child rules in the array.
  • AssertionRule takes a single child rule. It succeeds if its child rule succeeds but it does not consume the input.
  • NotAssertionRule, like AssertionRule, takes a single child rule. It will succeed if its child rule fails and vice versa, and never consumes input.
  • RangeRule takes a single child rule and will try repeatedly match the rule to the input. It succeeds if the number of successful repetitions of the child rule falls within a specified range.

TextStorage

  • PieceTable implements the piece table data structure for efficient text editing.
  • PieceTableString is a subclass of NSMutableString that uses a PieceTable for its internal storage.
  • ParsedAttributedString is a subclass of NSMutableAttributedString that:

1. Uses a PieceTableString for character storage; 2. Uses a MemoizationTable to parse the string and incrementally re-parse the string on each change; 3. Applies a set of formatting rules based upon the parsed syntax tree to determine the formatting of the string.

  • ObjectiveCTextStorageWrapper is an NSTextStorage implementation that lets you use a ParsedAttributedString as the backing storage for TextKit, like UITextView.

Package Metadata

Repository: bdewey/textmarkupkit

Default branch: master

README: README.md