{"id":2021,"url":"https://github.com/SimonFairbairn/SwiftyMarkdown","last_synced_at":"2025-08-02T05:33:29.784Z","repository":{"id":37470776,"uuid":"53193036","full_name":"SimonFairbairn/SwiftyMarkdown","owner":"SimonFairbairn","description":"Converts Markdown files and strings into NSAttributedStrings with lots of customisation options.","archived":false,"fork":false,"pushed_at":"2024-08-07T04:55:05.000Z","size":335,"stargazers_count":1710,"open_issues_count":54,"forks_count":285,"subscribers_count":25,"default_branch":"master","last_synced_at":"2025-07-31T17:52:54.800Z","etag":null,"topics":["character-styles","formatting","markdown","markdown-converter"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SimonFairbairn.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-03-05T09:09:08.000Z","updated_at":"2025-07-29T16:59:25.000Z","dependencies_parsed_at":"2024-04-23T19:06:02.630Z","dependency_job_id":"b54a47d5-25e2-4d58-885e-51395369a5b5","html_url":"https://github.com/SimonFairbairn/SwiftyMarkdown","commit_stats":{"total_commits":210,"total_committers":19,"mean_commits":"11.052631578947368","dds":"0.16666666666666663","last_synced_commit":"dde451ab4ed9b77b328e21baa471fdfa0cf61369"},"previous_names":[],"tags_count":29,"template":false,"template_full_name":null,"purl":"pkg:github/SimonFairbairn/SwiftyMarkdown","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SimonFairbairn%2FSwiftyMarkdown","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SimonFairbairn%2FSwiftyMarkdown/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SimonFairbairn%2FSwiftyMarkdown/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SimonFairbairn%2FSwiftyMarkdown/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SimonFairbairn","download_url":"https://codeload.github.com/SimonFairbairn/SwiftyMarkdown/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SimonFairbairn%2FSwiftyMarkdown/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268339405,"owners_count":24234544,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-02T02:00:12.353Z","response_time":74,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["character-styles","formatting","markdown","markdown-converter"],"created_at":"2024-01-05T20:16:01.437Z","updated_at":"2025-08-02T05:33:29.486Z","avatar_url":"https://github.com/SimonFairbairn.png","language":"Swift","funding_links":[],"categories":["Swift","UI","Text"],"sub_categories":["Layout","Other free courses","Other Testing","Keychain"],"readme":"# SwiftyMarkdown 1.0\n\nSwiftyMarkdown converts Markdown files and strings into `NSAttributedString`s using sensible defaults and a Swift-style syntax. It uses dynamic type to set the font size correctly with whatever font you'd like to use.\n\n- [What's New](#fully-rebuilt-for-2020)\n- [Installation](#installation)\n- [How to Use](#how-to-use-swiftymarkdown)\n- [Screenshot](#screenshot)\n- [Front Matter](#front-matter)\n- [Appendix](#appendix)\n\n## Fully Rebuilt For 2020!\n\nSwiftyMarkdown now features a more robust and reliable rules-based line processing and character tokenisation engine. It has added support for images stored in the bundle (`![Image](\u003cName In bundle\u003e)`), codeblocks, blockquotes, and unordered lists!\n\nLine-level attributes can now have a paragraph alignment applied to them (e.g. `h2.aligment = .center`), and links can be optionally underlined by setting `underlineLinks` to `true`. \n\nIt also uses the system color `.label` as the default font color on iOS 13 and above for Dark Mode support out of the box. \n\nSupport for all of Apple's platforms has been enabled.\n\n## Installation\n\n### CocoaPods:\n\n`pod 'SwiftyMarkdown'`\n\n### SPM: \n\nIn Xcode, `File -\u003e Swift Packages -\u003e Add Package Dependency` and add the GitHub URL. \n\n## How To Use SwiftyMarkdown\n\nRead Markdown from a text string...\n\n```swift\nlet md = SwiftyMarkdown(string: \"# Heading\\nMy *Markdown* string\")\nmd.attributedString()\n```\n\n...or from a URL.\n\n```swift\nif let url = Bundle.main.url(forResource: \"file\", withExtension: \"md\"), md = SwiftyMarkdown(url: url ) {\n\tmd.attributedString()\n}\n```\n\nIf you want to use a different string once SwiftyMarkdown has been initialised, you can now do so like this:\n\n```swift\nlet md = SwiftyMarkdown(string: \"# Heading\\nMy *Markdown* string\")\nmd.attributedString(from: \"A **SECOND** Markdown string. *Fancy!*\")\n```\n\nThe attributed string can then be assigned to any label or text control that has support for attributed text. \n\n```swift\nlet md = SwiftyMarkdown(string: \"# Heading\\nMy *Markdown* string\")\nlet label = UILabel()\nlabel.attributedText = md.attributedString()\n```\n\n## Supported Markdown Features\n\n    *italics* or _italics_\n    **bold** or __bold__\n    ~~Linethrough~~Strikethroughs. \n    `code`\n\n    # Header 1\n\n\tor\n\n    Header 1\n    ====\n\n    ## Header 2\n\n\tor\n\n    Header 2\n    ---\n\n    ### Header 3\n    #### Header 4\n    ##### Header 5 #####\n    ###### Header 6 ######\n\n\t\tIndented code blocks (spaces or tabs)\n\n    [Links](http://voyagetravelapps.com/)\n    ![Images](\u003cName of asset in bundle\u003e)\n    \n    [Referenced Links][1]\n    ![Referenced Images][2]\n    \n    [1]: http://voyagetravelapps.com/\n    [2]: \u003cName of asset in bundle\u003e\n    \n    \u003e Blockquotes\n\t\n\t- Bulleted\n\t- Lists\n\t\t- Including indented lists\n\t\t\t- Up to three levels\n\t- Neat!\n\t\n\t1. Ordered\n\t1. Lists\n\t\t1. Including indented lists\n\t\t\t- Up to three levels\n\n\t\n\t\t\nCompound rules also work, for example:\n\t\t\n\tIt recognises **[Bold Links](http://voyagetravelapps.com/)**\n\t\n\tOr [**Bold Links**](http://voyagetravelapps.com/)\n\nImages will be inserted into the returned `NSAttributedString` as an `NSTextAttachment` (sadly, this will not work on watchOS as `NSTextAttachment` is not available). \n\n## Customisation \n\nSet the attributes of every paragraph and character style type using straightforward dot syntax:\n\n```swift\nmd.body.fontName = \"AvenirNextCondensed-Medium\"\n\nmd.h1.color = UIColor.redColor()\nmd.h1.fontName = \"AvenirNextCondensed-Bold\"\nmd.h1.fontSize = 16\nmd.h1.alignmnent = .center\n\nmd.italic.color = UIColor.blueColor()\n\nmd.underlineLinks = true\n\nmd.bullet = \"🍏\"\n```\n\nOn iOS, Specified font sizes will be adjusted relative to the the user's dynamic type settings.\n\n## Screenshot\n\n![Screenshot](https://cl.ly/779e6964257a/swiftymarkdown-2020.png)\n\nThere's an example project included in the repository. Open the `Example/SwiftyMarkdown.xcodeproj` file to get started.\n\n## Front Matter\n\nSwiftyMarkdown recognises YAML front matter and will populate the `frontMatterAttributes` property with the key-value pairs that it fines. \n\n## Appendix \n\n### A) All Customisable Properties \n\n```swift\nh1.fontName : String\nh1.fontSize : CGFloat\nh1.color : UI/NSColor\nh1.fontStyle : FontStyle\nh1.alignment : NSTextAlignment\n\nh2.fontName : String\nh2.fontSize : CGFloat\nh2.color : UI/NSColor\nh2.fontStyle : FontStyle\nh2.alignment : NSTextAlignment\n\nh3.fontName : String\nh3.fontSize : CGFloat\nh3.color : UI/NSColor\nh3.fontStyle : FontStyle\nh3.alignment : NSTextAlignment\n\nh4.fontName : String\nh4.fontSize : CGFloat\nh4.color : UI/NSColor\nh4.fontStyle : FontStyle\nh4.alignment : NSTextAlignment\n\nh5.fontName : String\nh5.fontSize : CGFloat\nh5.color : UI/NSColor\nh5.fontStyle : FontStyle\nh5.alignment : NSTextAlignment\n\nh6.fontName : String\nh6.fontSize : CGFloat\nh6.color : UI/NSColor\nh6.fontStyle : FontStyle\nh6.alignment : NSTextAlignment\n\nbody.fontName : String\nbody.fontSize : CGFloat\nbody.color : UI/NSColor\nbody.fontStyle : FontStyle\nbody.alignment : NSTextAlignment\n\nblockquotes.fontName : String\nblockquotes.fontSize : CGFloat\nblockquotes.color : UI/NSColor\nblockquotes.fontStyle : FontStyle\nblockquotes.alignment : NSTextAlignment\n\nlink.fontName : String\nlink.fontSize : CGFloat\nlink.color : UI/NSColor\nlink.fontStyle : FontStyle\n\nbold.fontName : String\nbold.fontSize : CGFloat\nbold.color : UI/NSColor\nbold.fontStyle : FontStyle\n\nitalic.fontName : String\nitalic.fontSize : CGFloat\nitalic.color : UI/NSColor\nitalic.fontStyle : FontStyle\n\ncode.fontName : String\ncode.fontSize : CGFloat\ncode.color : UI/NSColor\ncode.fontStyle : FontStyle\n\nstrikethrough.fontName : String\nstrikethrough.fontSize : CGFloat\nstrikethrough.color : UI/NSColor\nstrikethrough.fontStyle : FontStyle\n\nunderlineLinks : Bool\n\nbullet : String\n```\n\n`FontStyle` is an enum with these cases: `normal`, `bold`, `italic`, and `bolditalic` to give you more precise control over how lines and character styles should look. For example, perhaps you want blockquotes to default to having the italic style:\n\n```swift\nmd.blockquotes.fontStyle = .italic\n```\nOr, if you like a bit of chaos:\n\n```swift\nmd.bold.fontStyle = .italic\nmd.italic.fontStyle = .bold\n```\n\n### B) Advanced Customisation\n\nSwiftyMarkdown uses a rules-based line processing and customisation engine that is no longer limited to Markdown. Rules are processed in order, from top to bottom. Line processing happens first, then character styles are applied based on the character rules. \n\nFor example, here's how a small subset of Markdown line tags are set up within SwiftyMarkdown:\n\n```swift\nenum MarkdownLineStyle : LineStyling {\n\tcase h1\n\tcase h2\n\tcase previousH1\n\tcase codeblock\n\tcase body\n\t\n\tvar shouldTokeniseLine: Bool {\n\t\tswitch self {\n\t\tcase .codeblock:\n\t\t\treturn false\n\t\tdefault:\n\t\t\treturn true\n\t\t}\n\t}\n\t\n\tfunc styleIfFoundStyleAffectsPreviousLine() -\u003e LineStyling? {\n\t\tswitch self {\n\t\tcase .previousH1:\n\t\t\treturn MarkdownLineStyle.h1\n\t\tdefault :\n\t\t\treturn nil\n\t\t}\n\t}\n}\n\nstatic public var lineRules = [\n\tLineRule(token: \"    \",type : MarkdownLineStyle.codeblock, removeFrom: .leading),\n\tLineRule(token: \"=\",type : MarkdownLineStyle.previousH1, removeFrom: .entireLine, changeAppliesTo: .previous),\n\tLineRule(token: \"## \",type : MarkdownLineStyle.h2, removeFrom: .both),\n\tLineRule(token: \"# \",type : MarkdownLineStyle.h1, removeFrom: .both)\n]\n\nlet lineProcessor = SwiftyLineProcessor(rules: SwiftyMarkdown.lineRules, default: MarkdownLineStyle.body)\n```\n\nSimilarly, the character styles all follow rules:\n\n```swift\nenum CharacterStyle : CharacterStyling {\n\tcase link, bold, italic, code\n}\n\nstatic public var characterRules = [\n    CharacterRule(primaryTag: CharacterRuleTag(tag: \"[\", type: .open), otherTags: [\n\t\t\tCharacterRuleTag(tag: \"]\", type: .close),\n\t\t\tCharacterRuleTag(tag: \"[\", type: .metadataOpen),\n\t\t\tCharacterRuleTag(tag: \"]\", type: .metadataClose)\n\t], styles: [1 : CharacterStyle.link], metadataLookup: true, definesBoundary: true),\n\tCharacterRule(primaryTag: CharacterRuleTag(tag: \"`\", type: .repeating), otherTags: [], styles: [1 : CharacterStyle.code], shouldCancelRemainingTags: true, balancedTags: true),\n\tCharacterRule(primaryTag: CharacterRuleTag(tag: \"*\", type: .repeating), otherTags: [], styles: [1 : CharacterStyle.italic, 2 : CharacterStyle.bold], minTags:1 , maxTags:2),\n\tCharacterRule(primaryTag: CharacterRuleTag(tag: \"_\", type: .repeating), otherTags: [], styles: [1 : CharacterStyle.italic, 2 : CharacterStyle.bold], minTags:1 , maxTags:2)\n]\n```\n\nThese Character Rules are defined by SwiftyMarkdown:\n\n\tpublic struct CharacterRule : CustomStringConvertible {\n\n\t\tpublic let primaryTag : CharacterRuleTag\n\t\tpublic let tags : [CharacterRuleTag]\n\t\tpublic let escapeCharacters : [Character]\n\t\tpublic let styles : [Int : CharacterStyling]\n\t\tpublic let minTags : Int\n\t\tpublic let maxTags : Int\n\t\tpublic var metadataLookup : Bool = false\n\t\tpublic var definesBoundary = false\n\t\tpublic var shouldCancelRemainingRules = false\n\t\tpublic var balancedTags = false\n\t}\n\n1. `primaryTag`: Each rule must have at least one tag and it can be one of `repeating`, `open`, `close`, `metadataOpen`, or `metadataClose`. `repeating` tags are tags that have identical open and close characters (and often have more than 1 style depending on how many are in a group). For example, the `*` tag used in Markdown.\n2. `tags`: An array of other tags that the rule can look for. This is where you would put the `close` tag for a custom rule, for example.\n3. `escapeCharacters`: The characters that appear prior to any of the tag characters that tell the scanner to ignore the tag. \n4. `styles`: The styles that should be applied to every character between the opening and closing tags. \n5. `minTags`: The minimum number of repeating characters to be considered a successful match. For example, setting the `primaryTag` to `*` and the `minTag` to 2 would mean that `**foo**` would be a successful match wheras `*bar*` would not.\n6. `maxTags`: The maximum number of repeating characters to be considered a successful match. \n7. `metadataLookup`: Used for Markdown reference links. Tells the scanner to try to look up the metadata from this dictionary, rather than from the inline result. \n8. `definesBoundary`: In order for open and close tags to be successful, the `boundaryCount` for a given location in the string needs to be the same. Setting this property to `true` means that this rule will increase the `boundaryCount` for every character between its opening and closing tags. For example, the `[` rule defines a boundary. After it is applied, the string `*foo[bar*]` becomes `*foobar*` with a boundaryCount `00001111`. Applying the `*` rule results in the output `*foobar*` because the opening `*` tag and the closing `*` tag now have different `boundaryCount` values. It's basically a way to fix the `**[should not be bold**](url)` problem in Markdown. \n9. `shouldCancelRemainingTags`: A successful match will mark every character between the opening and closing tags as complete, thereby preventing any further rules from being applied to those characters.\n10. `balancedTags`: This flag requires that the opening and closing tags be of exactly equal length. E.g. If this is set to true,  `**foo*` would result in `**foo*`. If it was false, the output would be `*foo`.\n\n\n\n#### Rule Subsets\n\nIf you want to only support a small subset of Markdown, it's now easy to do. \n\nThis example would only process strings with `*` and `_` characters, ignoring links, images, code, and all line-level attributes (headings, blockquotes, etc.)\n```swift\nSwiftyMarkdown.lineRules = []\n\nSwiftyMarkdown.characterRules = [\n\tCharacterRule(primaryTag: CharacterRuleTag(tag: \"*\", type: .repeating), otherTags: [], styles: [1 : CharacterStyle.italic, 2 : CharacterStyle.bold], minTags:1 , maxTags:2),\n\tCharacterRule(primaryTag: CharacterRuleTag(tag: \"_\", type: .repeating), otherTags: [], styles: [1 : CharacterStyle.italic, 2 : CharacterStyle.bold], minTags:1 , maxTags:2)\n]\n```\n\n#### Custom Rules\n\nIf you wanted to create a rule that applied a style of `Elf` to a range of characters between \"The elf will speak now: %Here is my elf speaking%\", you could set things up like this:\n\n```swift\nenum Characters : CharacterStyling {\n\tcase elf\n\n\tfunc isEqualTo( _ other : CharacterStyling) -\u003e Bool {\n\t\tif let other = other as? Characters else {\n\t\t\treturn false\n\t\t}\n\t\treturn other == self\n\t}\n}\n\nlet characterRules = [\n\tCharacterRule(primaryTag: CharacterRuleTag(tag: \"%\", type: .repeating), otherTags: [], styles: [1 : CharacterStyle.elf])\n]\n\nlet processor = SwiftyTokeniser( with : characterRules )\nlet string = \"The elf will speak now: %Here is my elf speaking%\"\nlet tokens = processor.process(string)\n```\n\nThe output is an array of tokens would be equivalent to:\n\n```swift\n[\n\tToken(type: .string, inputString: \"The elf will speak now: \", characterStyles: []),\n\tToken(type: .repeatingTag, inputString: \"%\", characterStyles: []),\n\tToken(type: .string, inputString: \"Here is my elf speaking\", characterStyles: [.elf]),\n\tToken(type: .repeatingTag, inputString: \"%\", characterStyles: [])\n]\n```\n\n### C) SpriteKit Support\n\nDid you know that `SKLabelNode` supports attributed text? I didn't.\n\n```swift\nlet smd = SwiftyMarkdown(string: \"My Character's **Dialogue**\")\n\nlet label = SKLabelNode()\nlabel.preferredMaxLayoutWidth = 500\nlabel.numberOfLines = 0\nlabel.attributedText = smd.attributedString()\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FSimonFairbairn%2FSwiftyMarkdown","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FSimonFairbairn%2FSwiftyMarkdown","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FSimonFairbairn%2FSwiftyMarkdown/lists"}