├── .gitignore ├── .travis.yml ├── LICENSE ├── MarkdownTextView.xcodeproj ├── .gitignore ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── MarkdownTextView.xcscheme │ └── MarkdownTextViewKit.xcscheme ├── MarkdownTextView ├── AppDelegate.swift ├── Base.lproj │ ├── LaunchScreen.xib │ └── Main.storyboard ├── Images.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── big.imageset │ │ ├── Contents.json │ │ └── big.png │ └── small.imageset │ │ ├── Contents.json │ │ └── small.jpeg ├── Info.plist ├── MarkdownTextStorage.swift ├── TableViewController.swift └── ViewController.swift ├── MarkdownTextViewKit ├── Info.plist └── MarkdownTextViewKit.h ├── MarkdownTextViewKitTests ├── Info.plist └── MarkdownTextViewKitTests.swift ├── MarkdownTextViewTests ├── Info.plist └── MarkdownTextViewTests.swift └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | Carthage 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode8 3 | script: 4 | - xcodebuild -workspace MarkdownTextView.xcodeproj/project.xcworkspace -scheme MarkdownTextViewKit -sdk iphonesimulator 5 | - xcodebuild test -workspace MarkdownTextView.xcodeproj/project.xcworkspace -scheme MarkdownTextViewKit -sdk iphonesimulator 6 | 7 | 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Many parts inspired by if not directly ripped from MarkdownSharp: 2 | 3 | 4 | MarkdownSharp 5 | ------------- 6 | a C# Markdown processor 7 | 8 | Markdown is a text-to-HTML conversion tool for web writers 9 | Copyright (c) 2004 John Gruber 10 | http://daringfireball.net/projects/markdown/ 11 | 12 | Markdown.NET 13 | Copyright (c) 2004-2009 Milan Negovan 14 | http://www.aspnetresources.com 15 | http://aspnetresources.com/blog/markdown_announced.aspx 16 | 17 | MarkdownSharp 18 | Copyright (c) 2009-2011 Jeff Atwood 19 | http://stackoverflow.com 20 | http://www.codinghorror.com/blog/ 21 | http://code.google.com/p/markdownsharp/ 22 | 23 | History: Milan ported the Markdown processor to C#. He granted license to me so I can open source it 24 | and let the community contribute to and improve MarkdownSharp. 25 | 26 | MarkdownTextView 27 | ---------------- 28 | Copyright (c) 2015 Jesper Christensen 29 | 30 | Permission is hereby granted, free of charge, to any person obtaining a copy 31 | of this software and associated documentation files (the "Software"), to deal 32 | in the Software without restriction, including without limitation the rights 33 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 34 | copies of the Software, and to permit persons to whom the Software is 35 | furnished to do so, subject to the following conditions: 36 | 37 | The above copyright notice and this permission notice shall be included in 38 | all copies or substantial portions of the Software. 39 | 40 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 41 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 42 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 43 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 44 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 45 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 46 | THE SOFTWARE. 47 | -------------------------------------------------------------------------------- /MarkdownTextView.xcodeproj/.gitignore: -------------------------------------------------------------------------------- 1 | xcuserdata/ 2 | project.xcworkspace/ 3 | -------------------------------------------------------------------------------- /MarkdownTextView.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 811999261ACF9C8200809293 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 811999251ACF9C8200809293 /* AppDelegate.swift */; }; 11 | 811999281ACF9C8200809293 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 811999271ACF9C8200809293 /* ViewController.swift */; }; 12 | 8119992B1ACF9C8200809293 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 811999291ACF9C8200809293 /* Main.storyboard */; }; 13 | 8119992D1ACF9C8200809293 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8119992C1ACF9C8200809293 /* Images.xcassets */; }; 14 | 811999301ACF9C8200809293 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8119992E1ACF9C8200809293 /* LaunchScreen.xib */; }; 15 | 817CC4221ADC362A00EAA5D5 /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 817CC4211ADC362A00EAA5D5 /* TableViewController.swift */; }; 16 | 81D544141BB91F60003F826C /* MarkdownTextViewKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 81D544131BB91F60003F826C /* MarkdownTextViewKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 17 | 81D5441B1BB91F60003F826C /* MarkdownTextViewKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81D544111BB91F60003F826C /* MarkdownTextViewKit.framework */; }; 18 | 81D544221BB91F60003F826C /* MarkdownTextViewKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 81D544211BB91F60003F826C /* MarkdownTextViewKitTests.swift */; }; 19 | 81D544261BB91F60003F826C /* MarkdownTextViewKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 81D544111BB91F60003F826C /* MarkdownTextViewKit.framework */; }; 20 | 81D544271BB91F60003F826C /* MarkdownTextViewKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 81D544111BB91F60003F826C /* MarkdownTextViewKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 21 | 81D5442F1BB91F78003F826C /* MarkdownTextStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 811999451ACF9EA600809293 /* MarkdownTextStorage.swift */; }; 22 | 81D544301BB92019003F826C /* MarkdownTextViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8119993B1ACF9C8200809293 /* MarkdownTextViewTests.swift */; }; 23 | /* End PBXBuildFile section */ 24 | 25 | /* Begin PBXContainerItemProxy section */ 26 | 811999361ACF9C8200809293 /* PBXContainerItemProxy */ = { 27 | isa = PBXContainerItemProxy; 28 | containerPortal = 811999181ACF9C8200809293 /* Project object */; 29 | proxyType = 1; 30 | remoteGlobalIDString = 8119991F1ACF9C8200809293; 31 | remoteInfo = MarkdownTextView; 32 | }; 33 | 81D5441C1BB91F60003F826C /* PBXContainerItemProxy */ = { 34 | isa = PBXContainerItemProxy; 35 | containerPortal = 811999181ACF9C8200809293 /* Project object */; 36 | proxyType = 1; 37 | remoteGlobalIDString = 81D544101BB91F60003F826C; 38 | remoteInfo = MarkdownTextViewKit; 39 | }; 40 | 81D5441E1BB91F60003F826C /* PBXContainerItemProxy */ = { 41 | isa = PBXContainerItemProxy; 42 | containerPortal = 811999181ACF9C8200809293 /* Project object */; 43 | proxyType = 1; 44 | remoteGlobalIDString = 8119991F1ACF9C8200809293; 45 | remoteInfo = MarkdownTextView; 46 | }; 47 | 81D544241BB91F60003F826C /* PBXContainerItemProxy */ = { 48 | isa = PBXContainerItemProxy; 49 | containerPortal = 811999181ACF9C8200809293 /* Project object */; 50 | proxyType = 1; 51 | remoteGlobalIDString = 81D544101BB91F60003F826C; 52 | remoteInfo = MarkdownTextViewKit; 53 | }; 54 | /* End PBXContainerItemProxy section */ 55 | 56 | /* Begin PBXCopyFilesBuildPhase section */ 57 | 81D5442D1BB91F60003F826C /* Embed Frameworks */ = { 58 | isa = PBXCopyFilesBuildPhase; 59 | buildActionMask = 2147483647; 60 | dstPath = ""; 61 | dstSubfolderSpec = 10; 62 | files = ( 63 | 81D544271BB91F60003F826C /* MarkdownTextViewKit.framework in Embed Frameworks */, 64 | ); 65 | name = "Embed Frameworks"; 66 | runOnlyForDeploymentPostprocessing = 0; 67 | }; 68 | /* End PBXCopyFilesBuildPhase section */ 69 | 70 | /* Begin PBXFileReference section */ 71 | 811999201ACF9C8200809293 /* MarkdownTextView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MarkdownTextView.app; sourceTree = BUILT_PRODUCTS_DIR; }; 72 | 811999241ACF9C8200809293 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 73 | 811999251ACF9C8200809293 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 74 | 811999271ACF9C8200809293 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 75 | 8119992A1ACF9C8200809293 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 76 | 8119992C1ACF9C8200809293 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 77 | 8119992F1ACF9C8200809293 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 78 | 811999351ACF9C8200809293 /* MarkdownTextViewTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MarkdownTextViewTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 79 | 8119993A1ACF9C8200809293 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 80 | 8119993B1ACF9C8200809293 /* MarkdownTextViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = MarkdownTextViewTests.swift; path = ../MarkdownTextViewTests/MarkdownTextViewTests.swift; sourceTree = ""; }; 81 | 811999451ACF9EA600809293 /* MarkdownTextStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MarkdownTextStorage.swift; path = ../MarkdownTextView/MarkdownTextStorage.swift; sourceTree = ""; }; 82 | 817CC4211ADC362A00EAA5D5 /* TableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = ""; }; 83 | 81D544111BB91F60003F826C /* MarkdownTextViewKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MarkdownTextViewKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 84 | 81D544131BB91F60003F826C /* MarkdownTextViewKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MarkdownTextViewKit.h; sourceTree = ""; }; 85 | 81D544151BB91F60003F826C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 86 | 81D5441A1BB91F60003F826C /* MarkdownTextViewKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MarkdownTextViewKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 87 | 81D544211BB91F60003F826C /* MarkdownTextViewKitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownTextViewKitTests.swift; sourceTree = ""; }; 88 | 81D544231BB91F60003F826C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 89 | /* End PBXFileReference section */ 90 | 91 | /* Begin PBXFrameworksBuildPhase section */ 92 | 8119991D1ACF9C8200809293 /* Frameworks */ = { 93 | isa = PBXFrameworksBuildPhase; 94 | buildActionMask = 2147483647; 95 | files = ( 96 | 81D544261BB91F60003F826C /* MarkdownTextViewKit.framework in Frameworks */, 97 | ); 98 | runOnlyForDeploymentPostprocessing = 0; 99 | }; 100 | 811999321ACF9C8200809293 /* Frameworks */ = { 101 | isa = PBXFrameworksBuildPhase; 102 | buildActionMask = 2147483647; 103 | files = ( 104 | ); 105 | runOnlyForDeploymentPostprocessing = 0; 106 | }; 107 | 81D5440D1BB91F60003F826C /* Frameworks */ = { 108 | isa = PBXFrameworksBuildPhase; 109 | buildActionMask = 2147483647; 110 | files = ( 111 | ); 112 | runOnlyForDeploymentPostprocessing = 0; 113 | }; 114 | 81D544171BB91F60003F826C /* Frameworks */ = { 115 | isa = PBXFrameworksBuildPhase; 116 | buildActionMask = 2147483647; 117 | files = ( 118 | 81D5441B1BB91F60003F826C /* MarkdownTextViewKit.framework in Frameworks */, 119 | ); 120 | runOnlyForDeploymentPostprocessing = 0; 121 | }; 122 | /* End PBXFrameworksBuildPhase section */ 123 | 124 | /* Begin PBXGroup section */ 125 | 811999171ACF9C8200809293 = { 126 | isa = PBXGroup; 127 | children = ( 128 | 811999221ACF9C8200809293 /* MarkdownTextView */, 129 | 811999381ACF9C8200809293 /* MarkdownTextViewTests */, 130 | 81D544121BB91F60003F826C /* MarkdownTextViewKit */, 131 | 81D544201BB91F60003F826C /* MarkdownTextViewKitTests */, 132 | 811999211ACF9C8200809293 /* Products */, 133 | ); 134 | sourceTree = ""; 135 | }; 136 | 811999211ACF9C8200809293 /* Products */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | 811999201ACF9C8200809293 /* MarkdownTextView.app */, 140 | 811999351ACF9C8200809293 /* MarkdownTextViewTests.xctest */, 141 | 81D544111BB91F60003F826C /* MarkdownTextViewKit.framework */, 142 | 81D5441A1BB91F60003F826C /* MarkdownTextViewKitTests.xctest */, 143 | ); 144 | name = Products; 145 | sourceTree = ""; 146 | }; 147 | 811999221ACF9C8200809293 /* MarkdownTextView */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | 811999271ACF9C8200809293 /* ViewController.swift */, 151 | 817CC4211ADC362A00EAA5D5 /* TableViewController.swift */, 152 | 811999291ACF9C8200809293 /* Main.storyboard */, 153 | 811999231ACF9C8200809293 /* Supporting Files */, 154 | ); 155 | path = MarkdownTextView; 156 | sourceTree = ""; 157 | }; 158 | 811999231ACF9C8200809293 /* Supporting Files */ = { 159 | isa = PBXGroup; 160 | children = ( 161 | 811999251ACF9C8200809293 /* AppDelegate.swift */, 162 | 8119992C1ACF9C8200809293 /* Images.xcassets */, 163 | 8119992E1ACF9C8200809293 /* LaunchScreen.xib */, 164 | 811999241ACF9C8200809293 /* Info.plist */, 165 | ); 166 | name = "Supporting Files"; 167 | sourceTree = ""; 168 | }; 169 | 811999381ACF9C8200809293 /* MarkdownTextViewTests */ = { 170 | isa = PBXGroup; 171 | children = ( 172 | 811999391ACF9C8200809293 /* Supporting Files */, 173 | ); 174 | path = MarkdownTextViewTests; 175 | sourceTree = ""; 176 | }; 177 | 811999391ACF9C8200809293 /* Supporting Files */ = { 178 | isa = PBXGroup; 179 | children = ( 180 | 8119993A1ACF9C8200809293 /* Info.plist */, 181 | ); 182 | name = "Supporting Files"; 183 | sourceTree = ""; 184 | }; 185 | 81D544121BB91F60003F826C /* MarkdownTextViewKit */ = { 186 | isa = PBXGroup; 187 | children = ( 188 | 811999451ACF9EA600809293 /* MarkdownTextStorage.swift */, 189 | 81D544131BB91F60003F826C /* MarkdownTextViewKit.h */, 190 | 81D544151BB91F60003F826C /* Info.plist */, 191 | ); 192 | path = MarkdownTextViewKit; 193 | sourceTree = ""; 194 | }; 195 | 81D544201BB91F60003F826C /* MarkdownTextViewKitTests */ = { 196 | isa = PBXGroup; 197 | children = ( 198 | 81D544211BB91F60003F826C /* MarkdownTextViewKitTests.swift */, 199 | 8119993B1ACF9C8200809293 /* MarkdownTextViewTests.swift */, 200 | 81D544231BB91F60003F826C /* Info.plist */, 201 | ); 202 | path = MarkdownTextViewKitTests; 203 | sourceTree = ""; 204 | }; 205 | /* End PBXGroup section */ 206 | 207 | /* Begin PBXHeadersBuildPhase section */ 208 | 81D5440E1BB91F60003F826C /* Headers */ = { 209 | isa = PBXHeadersBuildPhase; 210 | buildActionMask = 2147483647; 211 | files = ( 212 | 81D544141BB91F60003F826C /* MarkdownTextViewKit.h in Headers */, 213 | ); 214 | runOnlyForDeploymentPostprocessing = 0; 215 | }; 216 | /* End PBXHeadersBuildPhase section */ 217 | 218 | /* Begin PBXNativeTarget section */ 219 | 8119991F1ACF9C8200809293 /* MarkdownTextView */ = { 220 | isa = PBXNativeTarget; 221 | buildConfigurationList = 8119993F1ACF9C8200809293 /* Build configuration list for PBXNativeTarget "MarkdownTextView" */; 222 | buildPhases = ( 223 | 8119991C1ACF9C8200809293 /* Sources */, 224 | 8119991D1ACF9C8200809293 /* Frameworks */, 225 | 8119991E1ACF9C8200809293 /* Resources */, 226 | 81D5442D1BB91F60003F826C /* Embed Frameworks */, 227 | ); 228 | buildRules = ( 229 | ); 230 | dependencies = ( 231 | 81D544251BB91F60003F826C /* PBXTargetDependency */, 232 | ); 233 | name = MarkdownTextView; 234 | productName = MarkdownTextView; 235 | productReference = 811999201ACF9C8200809293 /* MarkdownTextView.app */; 236 | productType = "com.apple.product-type.application"; 237 | }; 238 | 811999341ACF9C8200809293 /* MarkdownTextViewTests */ = { 239 | isa = PBXNativeTarget; 240 | buildConfigurationList = 811999421ACF9C8200809293 /* Build configuration list for PBXNativeTarget "MarkdownTextViewTests" */; 241 | buildPhases = ( 242 | 811999311ACF9C8200809293 /* Sources */, 243 | 811999321ACF9C8200809293 /* Frameworks */, 244 | 811999331ACF9C8200809293 /* Resources */, 245 | ); 246 | buildRules = ( 247 | ); 248 | dependencies = ( 249 | 811999371ACF9C8200809293 /* PBXTargetDependency */, 250 | ); 251 | name = MarkdownTextViewTests; 252 | productName = MarkdownTextViewTests; 253 | productReference = 811999351ACF9C8200809293 /* MarkdownTextViewTests.xctest */; 254 | productType = "com.apple.product-type.bundle.unit-test"; 255 | }; 256 | 81D544101BB91F60003F826C /* MarkdownTextViewKit */ = { 257 | isa = PBXNativeTarget; 258 | buildConfigurationList = 81D5442C1BB91F60003F826C /* Build configuration list for PBXNativeTarget "MarkdownTextViewKit" */; 259 | buildPhases = ( 260 | 81D5440C1BB91F60003F826C /* Sources */, 261 | 81D5440D1BB91F60003F826C /* Frameworks */, 262 | 81D5440E1BB91F60003F826C /* Headers */, 263 | 81D5440F1BB91F60003F826C /* Resources */, 264 | ); 265 | buildRules = ( 266 | ); 267 | dependencies = ( 268 | ); 269 | name = MarkdownTextViewKit; 270 | productName = MarkdownTextViewKit; 271 | productReference = 81D544111BB91F60003F826C /* MarkdownTextViewKit.framework */; 272 | productType = "com.apple.product-type.framework"; 273 | }; 274 | 81D544191BB91F60003F826C /* MarkdownTextViewKitTests */ = { 275 | isa = PBXNativeTarget; 276 | buildConfigurationList = 81D5442E1BB91F60003F826C /* Build configuration list for PBXNativeTarget "MarkdownTextViewKitTests" */; 277 | buildPhases = ( 278 | 81D544161BB91F60003F826C /* Sources */, 279 | 81D544171BB91F60003F826C /* Frameworks */, 280 | 81D544181BB91F60003F826C /* Resources */, 281 | ); 282 | buildRules = ( 283 | ); 284 | dependencies = ( 285 | 81D5441D1BB91F60003F826C /* PBXTargetDependency */, 286 | 81D5441F1BB91F60003F826C /* PBXTargetDependency */, 287 | ); 288 | name = MarkdownTextViewKitTests; 289 | productName = MarkdownTextViewKitTests; 290 | productReference = 81D5441A1BB91F60003F826C /* MarkdownTextViewKitTests.xctest */; 291 | productType = "com.apple.product-type.bundle.unit-test"; 292 | }; 293 | /* End PBXNativeTarget section */ 294 | 295 | /* Begin PBXProject section */ 296 | 811999181ACF9C8200809293 /* Project object */ = { 297 | isa = PBXProject; 298 | attributes = { 299 | LastSwiftMigration = 0700; 300 | LastSwiftUpdateCheck = 0700; 301 | LastUpgradeCheck = 0800; 302 | ORGANIZATIONNAME = "Jesper Christensen"; 303 | TargetAttributes = { 304 | 8119991F1ACF9C8200809293 = { 305 | CreatedOnToolsVersion = 6.2; 306 | DevelopmentTeam = 948EVF7J4M; 307 | LastSwiftMigration = 0800; 308 | }; 309 | 811999341ACF9C8200809293 = { 310 | CreatedOnToolsVersion = 6.2; 311 | DevelopmentTeam = 948EVF7J4M; 312 | LastSwiftMigration = 0800; 313 | TestTargetID = 8119991F1ACF9C8200809293; 314 | }; 315 | 81D544101BB91F60003F826C = { 316 | CreatedOnToolsVersion = 7.0; 317 | LastSwiftMigration = 0800; 318 | ProvisioningStyle = Manual; 319 | }; 320 | 81D544191BB91F60003F826C = { 321 | CreatedOnToolsVersion = 7.0; 322 | DevelopmentTeam = 948EVF7J4M; 323 | LastSwiftMigration = 0800; 324 | TestTargetID = 8119991F1ACF9C8200809293; 325 | }; 326 | }; 327 | }; 328 | buildConfigurationList = 8119991B1ACF9C8200809293 /* Build configuration list for PBXProject "MarkdownTextView" */; 329 | compatibilityVersion = "Xcode 3.2"; 330 | developmentRegion = English; 331 | hasScannedForEncodings = 0; 332 | knownRegions = ( 333 | en, 334 | Base, 335 | ); 336 | mainGroup = 811999171ACF9C8200809293; 337 | productRefGroup = 811999211ACF9C8200809293 /* Products */; 338 | projectDirPath = ""; 339 | projectRoot = ""; 340 | targets = ( 341 | 8119991F1ACF9C8200809293 /* MarkdownTextView */, 342 | 811999341ACF9C8200809293 /* MarkdownTextViewTests */, 343 | 81D544101BB91F60003F826C /* MarkdownTextViewKit */, 344 | 81D544191BB91F60003F826C /* MarkdownTextViewKitTests */, 345 | ); 346 | }; 347 | /* End PBXProject section */ 348 | 349 | /* Begin PBXResourcesBuildPhase section */ 350 | 8119991E1ACF9C8200809293 /* Resources */ = { 351 | isa = PBXResourcesBuildPhase; 352 | buildActionMask = 2147483647; 353 | files = ( 354 | 8119992B1ACF9C8200809293 /* Main.storyboard in Resources */, 355 | 811999301ACF9C8200809293 /* LaunchScreen.xib in Resources */, 356 | 8119992D1ACF9C8200809293 /* Images.xcassets in Resources */, 357 | ); 358 | runOnlyForDeploymentPostprocessing = 0; 359 | }; 360 | 811999331ACF9C8200809293 /* Resources */ = { 361 | isa = PBXResourcesBuildPhase; 362 | buildActionMask = 2147483647; 363 | files = ( 364 | ); 365 | runOnlyForDeploymentPostprocessing = 0; 366 | }; 367 | 81D5440F1BB91F60003F826C /* Resources */ = { 368 | isa = PBXResourcesBuildPhase; 369 | buildActionMask = 2147483647; 370 | files = ( 371 | ); 372 | runOnlyForDeploymentPostprocessing = 0; 373 | }; 374 | 81D544181BB91F60003F826C /* Resources */ = { 375 | isa = PBXResourcesBuildPhase; 376 | buildActionMask = 2147483647; 377 | files = ( 378 | ); 379 | runOnlyForDeploymentPostprocessing = 0; 380 | }; 381 | /* End PBXResourcesBuildPhase section */ 382 | 383 | /* Begin PBXSourcesBuildPhase section */ 384 | 8119991C1ACF9C8200809293 /* Sources */ = { 385 | isa = PBXSourcesBuildPhase; 386 | buildActionMask = 2147483647; 387 | files = ( 388 | 811999281ACF9C8200809293 /* ViewController.swift in Sources */, 389 | 811999261ACF9C8200809293 /* AppDelegate.swift in Sources */, 390 | 817CC4221ADC362A00EAA5D5 /* TableViewController.swift in Sources */, 391 | ); 392 | runOnlyForDeploymentPostprocessing = 0; 393 | }; 394 | 811999311ACF9C8200809293 /* Sources */ = { 395 | isa = PBXSourcesBuildPhase; 396 | buildActionMask = 2147483647; 397 | files = ( 398 | ); 399 | runOnlyForDeploymentPostprocessing = 0; 400 | }; 401 | 81D5440C1BB91F60003F826C /* Sources */ = { 402 | isa = PBXSourcesBuildPhase; 403 | buildActionMask = 2147483647; 404 | files = ( 405 | 81D5442F1BB91F78003F826C /* MarkdownTextStorage.swift in Sources */, 406 | ); 407 | runOnlyForDeploymentPostprocessing = 0; 408 | }; 409 | 81D544161BB91F60003F826C /* Sources */ = { 410 | isa = PBXSourcesBuildPhase; 411 | buildActionMask = 2147483647; 412 | files = ( 413 | 81D544301BB92019003F826C /* MarkdownTextViewTests.swift in Sources */, 414 | 81D544221BB91F60003F826C /* MarkdownTextViewKitTests.swift in Sources */, 415 | ); 416 | runOnlyForDeploymentPostprocessing = 0; 417 | }; 418 | /* End PBXSourcesBuildPhase section */ 419 | 420 | /* Begin PBXTargetDependency section */ 421 | 811999371ACF9C8200809293 /* PBXTargetDependency */ = { 422 | isa = PBXTargetDependency; 423 | target = 8119991F1ACF9C8200809293 /* MarkdownTextView */; 424 | targetProxy = 811999361ACF9C8200809293 /* PBXContainerItemProxy */; 425 | }; 426 | 81D5441D1BB91F60003F826C /* PBXTargetDependency */ = { 427 | isa = PBXTargetDependency; 428 | target = 81D544101BB91F60003F826C /* MarkdownTextViewKit */; 429 | targetProxy = 81D5441C1BB91F60003F826C /* PBXContainerItemProxy */; 430 | }; 431 | 81D5441F1BB91F60003F826C /* PBXTargetDependency */ = { 432 | isa = PBXTargetDependency; 433 | target = 8119991F1ACF9C8200809293 /* MarkdownTextView */; 434 | targetProxy = 81D5441E1BB91F60003F826C /* PBXContainerItemProxy */; 435 | }; 436 | 81D544251BB91F60003F826C /* PBXTargetDependency */ = { 437 | isa = PBXTargetDependency; 438 | target = 81D544101BB91F60003F826C /* MarkdownTextViewKit */; 439 | targetProxy = 81D544241BB91F60003F826C /* PBXContainerItemProxy */; 440 | }; 441 | /* End PBXTargetDependency section */ 442 | 443 | /* Begin PBXVariantGroup section */ 444 | 811999291ACF9C8200809293 /* Main.storyboard */ = { 445 | isa = PBXVariantGroup; 446 | children = ( 447 | 8119992A1ACF9C8200809293 /* Base */, 448 | ); 449 | name = Main.storyboard; 450 | sourceTree = ""; 451 | }; 452 | 8119992E1ACF9C8200809293 /* LaunchScreen.xib */ = { 453 | isa = PBXVariantGroup; 454 | children = ( 455 | 8119992F1ACF9C8200809293 /* Base */, 456 | ); 457 | name = LaunchScreen.xib; 458 | sourceTree = ""; 459 | }; 460 | /* End PBXVariantGroup section */ 461 | 462 | /* Begin XCBuildConfiguration section */ 463 | 8119993D1ACF9C8200809293 /* Debug */ = { 464 | isa = XCBuildConfiguration; 465 | buildSettings = { 466 | ALWAYS_SEARCH_USER_PATHS = NO; 467 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 468 | CLANG_CXX_LIBRARY = "libc++"; 469 | CLANG_ENABLE_MODULES = YES; 470 | CLANG_ENABLE_OBJC_ARC = YES; 471 | CLANG_WARN_BOOL_CONVERSION = YES; 472 | CLANG_WARN_CONSTANT_CONVERSION = YES; 473 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 474 | CLANG_WARN_EMPTY_BODY = YES; 475 | CLANG_WARN_ENUM_CONVERSION = YES; 476 | CLANG_WARN_INFINITE_RECURSION = YES; 477 | CLANG_WARN_INT_CONVERSION = YES; 478 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 479 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 480 | CLANG_WARN_UNREACHABLE_CODE = YES; 481 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 482 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 483 | COPY_PHASE_STRIP = NO; 484 | ENABLE_STRICT_OBJC_MSGSEND = YES; 485 | ENABLE_TESTABILITY = YES; 486 | GCC_C_LANGUAGE_STANDARD = gnu99; 487 | GCC_DYNAMIC_NO_PIC = NO; 488 | GCC_NO_COMMON_BLOCKS = YES; 489 | GCC_OPTIMIZATION_LEVEL = 0; 490 | GCC_PREPROCESSOR_DEFINITIONS = ( 491 | "DEBUG=1", 492 | "$(inherited)", 493 | ); 494 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 495 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 496 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 497 | GCC_WARN_UNDECLARED_SELECTOR = YES; 498 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 499 | GCC_WARN_UNUSED_FUNCTION = YES; 500 | GCC_WARN_UNUSED_VARIABLE = YES; 501 | IPHONEOS_DEPLOYMENT_TARGET = 8.2; 502 | MTL_ENABLE_DEBUG_INFO = YES; 503 | ONLY_ACTIVE_ARCH = YES; 504 | SDKROOT = iphoneos; 505 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 506 | TARGETED_DEVICE_FAMILY = "1,2"; 507 | }; 508 | name = Debug; 509 | }; 510 | 8119993E1ACF9C8200809293 /* Release */ = { 511 | isa = XCBuildConfiguration; 512 | buildSettings = { 513 | ALWAYS_SEARCH_USER_PATHS = NO; 514 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 515 | CLANG_CXX_LIBRARY = "libc++"; 516 | CLANG_ENABLE_MODULES = YES; 517 | CLANG_ENABLE_OBJC_ARC = YES; 518 | CLANG_WARN_BOOL_CONVERSION = YES; 519 | CLANG_WARN_CONSTANT_CONVERSION = YES; 520 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 521 | CLANG_WARN_EMPTY_BODY = YES; 522 | CLANG_WARN_ENUM_CONVERSION = YES; 523 | CLANG_WARN_INFINITE_RECURSION = YES; 524 | CLANG_WARN_INT_CONVERSION = YES; 525 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 526 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 527 | CLANG_WARN_UNREACHABLE_CODE = YES; 528 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 529 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 530 | COPY_PHASE_STRIP = NO; 531 | ENABLE_NS_ASSERTIONS = NO; 532 | ENABLE_STRICT_OBJC_MSGSEND = YES; 533 | GCC_C_LANGUAGE_STANDARD = gnu99; 534 | GCC_NO_COMMON_BLOCKS = YES; 535 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 536 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 537 | GCC_WARN_UNDECLARED_SELECTOR = YES; 538 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 539 | GCC_WARN_UNUSED_FUNCTION = YES; 540 | GCC_WARN_UNUSED_VARIABLE = YES; 541 | IPHONEOS_DEPLOYMENT_TARGET = 8.2; 542 | MTL_ENABLE_DEBUG_INFO = NO; 543 | SDKROOT = iphoneos; 544 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 545 | TARGETED_DEVICE_FAMILY = "1,2"; 546 | VALIDATE_PRODUCT = YES; 547 | }; 548 | name = Release; 549 | }; 550 | 811999401ACF9C8200809293 /* Debug */ = { 551 | isa = XCBuildConfiguration; 552 | buildSettings = { 553 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 554 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 555 | DEVELOPMENT_TEAM = 948EVF7J4M; 556 | INFOPLIST_FILE = MarkdownTextView/Info.plist; 557 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 558 | PRODUCT_BUNDLE_IDENTIFIER = "org.kalliope.$(PRODUCT_NAME:rfc1034identifier)"; 559 | PRODUCT_NAME = "$(TARGET_NAME)"; 560 | SWIFT_VERSION = 3.0; 561 | }; 562 | name = Debug; 563 | }; 564 | 811999411ACF9C8200809293 /* Release */ = { 565 | isa = XCBuildConfiguration; 566 | buildSettings = { 567 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 568 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 569 | DEVELOPMENT_TEAM = 948EVF7J4M; 570 | INFOPLIST_FILE = MarkdownTextView/Info.plist; 571 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 572 | PRODUCT_BUNDLE_IDENTIFIER = "org.kalliope.$(PRODUCT_NAME:rfc1034identifier)"; 573 | PRODUCT_NAME = "$(TARGET_NAME)"; 574 | SWIFT_VERSION = 3.0; 575 | }; 576 | name = Release; 577 | }; 578 | 811999431ACF9C8200809293 /* Debug */ = { 579 | isa = XCBuildConfiguration; 580 | buildSettings = { 581 | BUNDLE_LOADER = "$(TEST_HOST)"; 582 | DEVELOPMENT_TEAM = 948EVF7J4M; 583 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 584 | GCC_PREPROCESSOR_DEFINITIONS = ( 585 | "DEBUG=1", 586 | "$(inherited)", 587 | ); 588 | INFOPLIST_FILE = MarkdownTextViewTests/Info.plist; 589 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 590 | PRODUCT_BUNDLE_IDENTIFIER = "org.kalliope.$(PRODUCT_NAME:rfc1034identifier)"; 591 | PRODUCT_NAME = "$(TARGET_NAME)"; 592 | SWIFT_VERSION = 3.0; 593 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MarkdownTextView.app/MarkdownTextView"; 594 | }; 595 | name = Debug; 596 | }; 597 | 811999441ACF9C8200809293 /* Release */ = { 598 | isa = XCBuildConfiguration; 599 | buildSettings = { 600 | BUNDLE_LOADER = "$(TEST_HOST)"; 601 | DEVELOPMENT_TEAM = 948EVF7J4M; 602 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 603 | INFOPLIST_FILE = MarkdownTextViewTests/Info.plist; 604 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 605 | PRODUCT_BUNDLE_IDENTIFIER = "org.kalliope.$(PRODUCT_NAME:rfc1034identifier)"; 606 | PRODUCT_NAME = "$(TARGET_NAME)"; 607 | SWIFT_VERSION = 3.0; 608 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MarkdownTextView.app/MarkdownTextView"; 609 | }; 610 | name = Release; 611 | }; 612 | 81D544281BB91F60003F826C /* Debug */ = { 613 | isa = XCBuildConfiguration; 614 | buildSettings = { 615 | APPLICATION_EXTENSION_API_ONLY = YES; 616 | CURRENT_PROJECT_VERSION = 1; 617 | DEBUG_INFORMATION_FORMAT = dwarf; 618 | DEFINES_MODULE = YES; 619 | DEVELOPMENT_TEAM = ""; 620 | DYLIB_COMPATIBILITY_VERSION = 1; 621 | DYLIB_CURRENT_VERSION = 1; 622 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 623 | GCC_NO_COMMON_BLOCKS = YES; 624 | INFOPLIST_FILE = MarkdownTextViewKit/Info.plist; 625 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 626 | IPHONEOS_DEPLOYMENT_TARGET = 8.2; 627 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 628 | PRODUCT_BUNDLE_IDENTIFIER = org.kalliope.MarkdownTextViewKit; 629 | PRODUCT_NAME = "$(TARGET_NAME)"; 630 | SKIP_INSTALL = YES; 631 | SWIFT_VERSION = 3.0; 632 | VERSIONING_SYSTEM = "apple-generic"; 633 | VERSION_INFO_PREFIX = ""; 634 | }; 635 | name = Debug; 636 | }; 637 | 81D544291BB91F60003F826C /* Release */ = { 638 | isa = XCBuildConfiguration; 639 | buildSettings = { 640 | APPLICATION_EXTENSION_API_ONLY = YES; 641 | CURRENT_PROJECT_VERSION = 1; 642 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 643 | DEFINES_MODULE = YES; 644 | DEVELOPMENT_TEAM = ""; 645 | DYLIB_COMPATIBILITY_VERSION = 1; 646 | DYLIB_CURRENT_VERSION = 1; 647 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 648 | GCC_NO_COMMON_BLOCKS = YES; 649 | INFOPLIST_FILE = MarkdownTextViewKit/Info.plist; 650 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 651 | IPHONEOS_DEPLOYMENT_TARGET = 8.2; 652 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 653 | PRODUCT_BUNDLE_IDENTIFIER = org.kalliope.MarkdownTextViewKit; 654 | PRODUCT_NAME = "$(TARGET_NAME)"; 655 | SKIP_INSTALL = YES; 656 | SWIFT_VERSION = 3.0; 657 | VERSIONING_SYSTEM = "apple-generic"; 658 | VERSION_INFO_PREFIX = ""; 659 | }; 660 | name = Release; 661 | }; 662 | 81D5442A1BB91F60003F826C /* Debug */ = { 663 | isa = XCBuildConfiguration; 664 | buildSettings = { 665 | DEBUG_INFORMATION_FORMAT = dwarf; 666 | DEVELOPMENT_TEAM = 948EVF7J4M; 667 | GCC_NO_COMMON_BLOCKS = YES; 668 | INFOPLIST_FILE = MarkdownTextViewKitTests/Info.plist; 669 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 670 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 671 | PRODUCT_BUNDLE_IDENTIFIER = org.kalliope.MarkdownTextViewKitTests; 672 | PRODUCT_NAME = "$(TARGET_NAME)"; 673 | SWIFT_VERSION = 3.0; 674 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MarkdownTextView.app/MarkdownTextView"; 675 | }; 676 | name = Debug; 677 | }; 678 | 81D5442B1BB91F60003F826C /* Release */ = { 679 | isa = XCBuildConfiguration; 680 | buildSettings = { 681 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 682 | DEVELOPMENT_TEAM = 948EVF7J4M; 683 | GCC_NO_COMMON_BLOCKS = YES; 684 | INFOPLIST_FILE = MarkdownTextViewKitTests/Info.plist; 685 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 686 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 687 | PRODUCT_BUNDLE_IDENTIFIER = org.kalliope.MarkdownTextViewKitTests; 688 | PRODUCT_NAME = "$(TARGET_NAME)"; 689 | SWIFT_VERSION = 3.0; 690 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MarkdownTextView.app/MarkdownTextView"; 691 | }; 692 | name = Release; 693 | }; 694 | /* End XCBuildConfiguration section */ 695 | 696 | /* Begin XCConfigurationList section */ 697 | 8119991B1ACF9C8200809293 /* Build configuration list for PBXProject "MarkdownTextView" */ = { 698 | isa = XCConfigurationList; 699 | buildConfigurations = ( 700 | 8119993D1ACF9C8200809293 /* Debug */, 701 | 8119993E1ACF9C8200809293 /* Release */, 702 | ); 703 | defaultConfigurationIsVisible = 0; 704 | defaultConfigurationName = Release; 705 | }; 706 | 8119993F1ACF9C8200809293 /* Build configuration list for PBXNativeTarget "MarkdownTextView" */ = { 707 | isa = XCConfigurationList; 708 | buildConfigurations = ( 709 | 811999401ACF9C8200809293 /* Debug */, 710 | 811999411ACF9C8200809293 /* Release */, 711 | ); 712 | defaultConfigurationIsVisible = 0; 713 | defaultConfigurationName = Release; 714 | }; 715 | 811999421ACF9C8200809293 /* Build configuration list for PBXNativeTarget "MarkdownTextViewTests" */ = { 716 | isa = XCConfigurationList; 717 | buildConfigurations = ( 718 | 811999431ACF9C8200809293 /* Debug */, 719 | 811999441ACF9C8200809293 /* Release */, 720 | ); 721 | defaultConfigurationIsVisible = 0; 722 | defaultConfigurationName = Release; 723 | }; 724 | 81D5442C1BB91F60003F826C /* Build configuration list for PBXNativeTarget "MarkdownTextViewKit" */ = { 725 | isa = XCConfigurationList; 726 | buildConfigurations = ( 727 | 81D544281BB91F60003F826C /* Debug */, 728 | 81D544291BB91F60003F826C /* Release */, 729 | ); 730 | defaultConfigurationIsVisible = 0; 731 | defaultConfigurationName = Release; 732 | }; 733 | 81D5442E1BB91F60003F826C /* Build configuration list for PBXNativeTarget "MarkdownTextViewKitTests" */ = { 734 | isa = XCConfigurationList; 735 | buildConfigurations = ( 736 | 81D5442A1BB91F60003F826C /* Debug */, 737 | 81D5442B1BB91F60003F826C /* Release */, 738 | ); 739 | defaultConfigurationIsVisible = 0; 740 | defaultConfigurationName = Release; 741 | }; 742 | /* End XCConfigurationList section */ 743 | }; 744 | rootObject = 811999181ACF9C8200809293 /* Project object */; 745 | } 746 | -------------------------------------------------------------------------------- /MarkdownTextView.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MarkdownTextView.xcodeproj/xcshareddata/xcschemes/MarkdownTextView.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 57 | 63 | 64 | 65 | 66 | 67 | 73 | 74 | 75 | 76 | 77 | 78 | 88 | 90 | 96 | 97 | 98 | 99 | 100 | 101 | 107 | 109 | 115 | 116 | 117 | 118 | 120 | 121 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /MarkdownTextView.xcodeproj/xcshareddata/xcschemes/MarkdownTextViewKit.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /MarkdownTextView/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // MarkdownTextView 4 | // 5 | // Created by Jesper Christensen on 04/04/15. 6 | // Copyright (c) 2015 Jesper Christensen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool { 17 | return true 18 | } 19 | 20 | func applicationWillResignActive(_ application: UIApplication) { 21 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 22 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 23 | } 24 | 25 | func applicationDidEnterBackground(_ application: UIApplication) { 26 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 27 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 28 | } 29 | 30 | func applicationWillEnterForeground(_ application: UIApplication) { 31 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 32 | } 33 | 34 | func applicationDidBecomeActive(_ application: UIApplication) { 35 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 36 | } 37 | 38 | func applicationWillTerminate(_ application: UIApplication) { 39 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 40 | } 41 | 42 | 43 | } 44 | 45 | -------------------------------------------------------------------------------- /MarkdownTextView/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /MarkdownTextView/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /MarkdownTextView/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /MarkdownTextView/Images.xcassets/big.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "big.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MarkdownTextView/Images.xcassets/big.imageset/big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thabz/MarkdownTextView/3f3e1fe4a086685c0159fbb25b26c29a861f9824/MarkdownTextView/Images.xcassets/big.imageset/big.png -------------------------------------------------------------------------------- /MarkdownTextView/Images.xcassets/small.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "small.jpeg" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /MarkdownTextView/Images.xcassets/small.imageset/small.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thabz/MarkdownTextView/3f3e1fe4a086685c0159fbb25b26c29a861f9824/MarkdownTextView/Images.xcassets/small.imageset/small.jpeg -------------------------------------------------------------------------------- /MarkdownTextView/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSAppTransportSecurity 6 | 7 | NSAllowsArbitraryLoads 8 | 9 | 10 | CFBundleDevelopmentRegion 11 | en 12 | CFBundleExecutable 13 | $(EXECUTABLE_NAME) 14 | CFBundleIdentifier 15 | $(PRODUCT_BUNDLE_IDENTIFIER) 16 | CFBundleInfoDictionaryVersion 17 | 6.0 18 | CFBundleName 19 | $(PRODUCT_NAME) 20 | CFBundlePackageType 21 | APPL 22 | CFBundleShortVersionString 23 | 1.0 24 | CFBundleSignature 25 | ???? 26 | CFBundleVersion 27 | 1 28 | LSRequiresIPhoneOS 29 | 30 | UILaunchStoryboardName 31 | LaunchScreen 32 | UIMainStoryboardFile 33 | Main 34 | UIRequiredDeviceCapabilities 35 | 36 | armv7 37 | 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UISupportedInterfaceOrientations~ipad 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationPortraitUpsideDown 48 | UIInterfaceOrientationLandscapeLeft 49 | UIInterfaceOrientationLandscapeRight 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /MarkdownTextView/TableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewController.swift 3 | // MarkdownTextView 4 | // 5 | // Created by Jesper Christensen on 13/04/15. 6 | // Copyright (c) 2015 Jesper Christensen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import MarkdownTextViewKit 11 | 12 | class TableViewController: UITableViewController { 13 | 14 | let markdownTexts = [[ 15 | "Paragrah", 16 | "### Header between", 17 | "Paragraph" 18 | ],[ 19 | "Go download 💣 [RayGay on GitHub](https://github.com/thabz/RayGay) if you're into raytracing.", 20 | "", 21 | "An `NSAttributedString` spread over multiple lines. ", 22 | "A section spread over multiple lines. ", 23 | ],[ 24 | "A checked list", 25 | "- [x] Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.", 26 | "- [ ] Line two. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.", 27 | "- [X] Line.", 28 | "With a new paragraph after" 29 | ],[ 30 | "# Short headline with [link](http://www.apple.com)", 31 | ],[ 32 | "### Short headline. ### ", 33 | "* Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.", 34 | "- Link to commit deadbeef and issue #1. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.", 35 | "+ Line.", 36 | "There should be nice spacing between this paragrah and the list above.", 37 | "", 38 | "And a slight text after. There should be nice spacing between this paragrah and the list above." 39 | ],[ 40 | "#### Demonstrating quotes", 41 | "> Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.", 42 | "> Link to commit 💣 deadbeef and issue 💣 #1.", 43 | "And this marks the end of the quote section." 44 | ],[ 45 | "Examples from /Apple Mail/ on OS X:", 46 | "", 47 | "![skaermbillede 2015-03-19 kl 10 11 35](https://cloud.githubusercontent.com/assets/157777/6727236/752598ac-ce20-11e4-8f1d-6bd7536caa01.png)", 48 | "![skaermbillede 2015-03-19 kl 10 11 44](https://cloud.githubusercontent.com/assets/157777/6727235/75223612-ce20-11e4-8aac-f5bfb2d4dcd7.png)" 49 | ],[ 50 | "```", 51 | "func square(x) { ", 52 | " // Simple square func!", 53 | " return x * x", 54 | "}", 55 | "```", 56 | ],[ 57 | "![An image](http://www.kalliope.org/gfx/icons/iphone-icon.png) ", 58 | "![An image](http://www.kalliope.org/gfx/icons/iphone-icon.png)", 59 | "", 60 | "With some text directly under it. ", 61 | ],[ 62 | "Normal, **bold**, ~~strikethrough~~, __bold__, _italic_ and *italic*.", 63 | "", 64 | "1. First item", 65 | "2. Second item", 66 | "1. First item", 67 | "2. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.", 68 | "1. First item", 69 | "2. Second item", 70 | "1. First item", 71 | "2. Second item", 72 | "1. First item", 73 | "2. Second item", 74 | "1. First item", 75 | "2. Second item", 76 | "", 77 | "And a small final paragraph, that should span a couple of lines if all goes according to plan. ", 78 | "Also it contains a raw URL http://www.kalliope.org/page/ inline" 79 | ],[ 80 | "![An image](http://www.kalliope.org/gfx/icons/iphone-icon.png) ", 81 | "![An image](http://www.kalliope.org/gfx/icons/iphone-icon.png)", 82 | "", 83 | "With some text directly under it. ", 84 | ],[ 85 | "An a simple single line. ", 86 | "An a simple single line. ", 87 | "An a simple single line. ", 88 | ] 89 | ] 90 | 91 | var defaultStyles: StylesDict = { 92 | let fontSize = CGFloat(13) 93 | let boldFont = UIFont.boldSystemFont(ofSize: fontSize) 94 | return [ 95 | MarkdownStylesName.normal: [NSFontAttributeName: UIFont.systemFont(ofSize: fontSize)], 96 | MarkdownStylesName.bold: [NSFontAttributeName: boldFont], 97 | MarkdownStylesName.italic: [NSFontAttributeName: UIFont.italicSystemFont(ofSize: fontSize)], 98 | MarkdownStylesName.quote: [NSFontAttributeName: UIFont.systemFont(ofSize: fontSize), NSForegroundColorAttributeName: UIColor.gray], 99 | MarkdownStylesName.monospace: [NSFontAttributeName: UIFont(name: "Menlo-Regular", size: fontSize-2)!], 100 | MarkdownStylesName.headline: [NSFontAttributeName: boldFont], 101 | MarkdownStylesName.subheadline: [NSFontAttributeName: boldFont], 102 | MarkdownStylesName.subsubheadline: [NSFontAttributeName: boldFont], 103 | MarkdownStylesName.subsubsubheadline: [NSFontAttributeName: boldFont]] 104 | }() 105 | 106 | var markdownTextStorages = [MarkdownTextStorage]() 107 | 108 | override func viewDidLoad() { 109 | super.viewDidLoad() 110 | for section in markdownTexts { 111 | let markdown = section.joined(separator: "\n") 112 | let markdownTextStorage = MarkdownTextStorage(markdown: markdown, styles: defaultStyles) 113 | markdownTextStorages.append(markdownTextStorage) 114 | } 115 | } 116 | 117 | override func viewWillAppear(_ animated: Bool) { 118 | super.viewWillAppear(animated) 119 | } 120 | 121 | // MARK: - Table view data source 122 | 123 | override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { 124 | return 100 125 | } 126 | 127 | override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 128 | return UITableViewAutomaticDimension 129 | } 130 | 131 | override func numberOfSections(in tableView: UITableView) -> Int { 132 | return 1 133 | } 134 | 135 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 136 | return markdownTexts.count 137 | } 138 | 139 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 140 | let cell = tableView.dequeueReusableCell(withIdentifier: "MarkdownCell", for: indexPath) as! MarkdownCell 141 | cell.markdownTextView.tableView = self.tableView 142 | cell.markdownTextView.markdownTextStorage = markdownTextStorages[indexPath.row] 143 | return cell 144 | } 145 | } 146 | 147 | class MarkdownCell : UITableViewCell { 148 | @IBOutlet weak var markdownTextView: MarkdownTextView! 149 | 150 | override func prepareForReuse() { 151 | markdownTextView.markdownTextStorage = nil 152 | super.prepareForReuse() 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /MarkdownTextView/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // MarkdownTextView 4 | // 5 | // Created by Jesper Christensen on 04/04/15. 6 | // Copyright (c) 2015 Jesper Christensen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import MarkdownTextViewKit 11 | 12 | class ViewController: UIViewController { 13 | 14 | @IBOutlet weak var textView: UITextView! 15 | var textStorage: MarkdownTextStorage? 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | 20 | let markdownExample = [ 21 | "Go download [RayGay on GitHub](https://github.com/thabz/RayGay) if you're into raytracing.", 22 | "", 23 | "An `NSAttributedString` spread over multiple lines. ", 24 | "A section spread over multiple lines. ", 25 | "", 26 | "Short line.", 27 | "* Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", 28 | "* Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.", 29 | "", 30 | "```", 31 | "func square(x) { ", 32 | " // Simple square func!", 33 | " return x * x", 34 | "}", 35 | "```", 36 | "", 37 | "![An image](http://www.kalliope.org/gfx/icons/iphone-icon.png)", 38 | "", 39 | "With some text directly under it. ", 40 | "Normal, *italic*, ~~strikethrough~~, __bold__ and _italic_.", 41 | "", 42 | "1. First item", 43 | "2. Second item", 44 | "" 45 | ] 46 | let joinedMarkdown = markdownExample.joined(separator: "\n") 47 | 48 | textStorage = MarkdownTextStorage(markdown: joinedMarkdown) 49 | 50 | textView.attributedText = NSAttributedString(string: "") 51 | 52 | // setUpReusedLayoutManager() 53 | // setUpAttributedString() 54 | // setUpNewTextView() 55 | setUpWithNotifications() 56 | } 57 | 58 | 59 | func setUpNewTextView() { 60 | 61 | let layoutManager = NSLayoutManager() 62 | textStorage?.addLayoutManager(layoutManager) 63 | 64 | let containerSize = CGSize(width: self.view.bounds.size.width, height: CGFloat.greatestFiniteMagnitude) 65 | let textContainer = NSTextContainer(size: containerSize) 66 | textContainer.widthTracksTextView = true 67 | textContainer.heightTracksTextView = true 68 | layoutManager.addTextContainer(textContainer) 69 | 70 | let newTextView = UITextView(frame: (self.view.bounds).insetBy(dx: 0, dy: 0), textContainer: textContainer) 71 | newTextView.backgroundColor = UIColor.green 72 | newTextView.isEditable = false 73 | view.addSubview(newTextView) 74 | 75 | self.textView.isHidden = true 76 | } 77 | 78 | func setUpAttributedString() { 79 | textView.attributedText = textStorage 80 | } 81 | 82 | func setUpReusedLayoutManager() { 83 | let layoutManager = textView.layoutManager 84 | let existingTextStorage = layoutManager.textStorage 85 | existingTextStorage?.removeLayoutManager(layoutManager) 86 | textStorage?.addLayoutManager(layoutManager) 87 | 88 | //textView.attributedText = textStorage 89 | } 90 | 91 | func setUpWithNotifications() { 92 | let markdownTextView = MarkdownTextView(frame: self.view.bounds) 93 | markdownTextView.autoresizingMask = [.flexibleWidth, .flexibleHeight] 94 | markdownTextView.isEditable = false 95 | markdownTextView.markdownTextStorage = textStorage 96 | view.addSubview(markdownTextView) 97 | self.textView.isHidden = true 98 | } 99 | } 100 | 101 | -------------------------------------------------------------------------------- /MarkdownTextViewKit/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /MarkdownTextViewKit/MarkdownTextViewKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // MarkdownTextViewKit.h 3 | // MarkdownTextViewKit 4 | // 5 | // Created by Jesper Christensen on 28/09/2015. 6 | // Copyright © 2015 Jesper Christensen. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for MarkdownTextViewKit. 12 | FOUNDATION_EXPORT double MarkdownTextViewKitVersionNumber; 13 | 14 | //! Project version string for MarkdownTextViewKit. 15 | FOUNDATION_EXPORT const unsigned char MarkdownTextViewKitVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /MarkdownTextViewKitTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /MarkdownTextViewKitTests/MarkdownTextViewKitTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MarkdownTextViewKitTests.swift 3 | // MarkdownTextViewKitTests 4 | // 5 | // Created by Jesper Christensen on 28/09/2015. 6 | // Copyright © 2015 Jesper Christensen. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import MarkdownTextViewKit 11 | 12 | class MarkdownTextViewKitTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | // func testPerformanceExample() { 30 | // // This is an example of a performance test case. 31 | // self.measureBlock { 32 | // // Put the code you want to measure the time of here. 33 | // } 34 | // } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /MarkdownTextViewTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /MarkdownTextViewTests/MarkdownTextViewTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MarkdownTextViewTests.swift 3 | // MarkdownTextViewTests 4 | // 5 | // Created by Jesper Christensen on 04/04/15. 6 | // Copyright (c) 2015 Jesper Christensen. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import XCTest 11 | import MarkdownTextViewKit 12 | 13 | class MarkdownTextViewTests: XCTestCase { 14 | 15 | override func setUp() { 16 | super.setUp() 17 | // Put setup code here. This method is called before the invocation of each test method in the class. 18 | } 19 | 20 | override func tearDown() { 21 | // Put teardown code here. This method is called after the invocation of each test method in the class. 22 | super.tearDown() 23 | } 24 | 25 | func testBold() { 26 | XCTAssertTrue(MarkdownTextStorage(markdown: "__bold__").isBoldAtIndex(0)) 27 | XCTAssertTrue(MarkdownTextStorage(markdown: "**bold**").isBoldAtIndex(0)) 28 | XCTAssertFalse(MarkdownTextStorage(markdown: "*italic*").isBoldAtIndex(0)) 29 | XCTAssertFalse(MarkdownTextStorage(markdown: "_italic_").isBoldAtIndex(0)) 30 | XCTAssertEqual("bold", MarkdownTextStorage(markdown: "**bold**").string) 31 | XCTAssertEqual("bold", MarkdownTextStorage(markdown: "__bold__").string) 32 | XCTAssertEqual("🐞", MarkdownTextStorage(markdown: "__🐞__").string) 33 | XCTAssertEqual("🐞 bold", MarkdownTextStorage(markdown: "🐞 __bold__").string) 34 | XCTAssertEqual("🐞 bold 🐞", MarkdownTextStorage(markdown: "🐞 __bold__ 🐞").string) 35 | XCTAssertTrue(MarkdownTextStorage(markdown: "Z **bold**").isBoldAtIndex(2)) 36 | XCTAssertTrue(MarkdownTextStorage(markdown: "🐞 **bold**").isBoldAtIndex(3)) 37 | XCTAssertTrue(MarkdownTextStorage(markdown: "__b__ _i_").isBoldAtIndex(0)) 38 | XCTAssertEqual("*bold*", MarkdownTextStorage(markdown: "\\*bold\\*").string) 39 | } 40 | 41 | func testItalic() { 42 | XCTAssertTrue(MarkdownTextStorage(markdown: "_italic_").isItalicAtIndex(0)) 43 | XCTAssertTrue(MarkdownTextStorage(markdown: "*italic*").isItalicAtIndex(0)) 44 | XCTAssertFalse(MarkdownTextStorage(markdown: "__bold__").isItalicAtIndex(0)) 45 | XCTAssertFalse(MarkdownTextStorage(markdown: "**bold**").isItalicAtIndex(0)) 46 | XCTAssertTrue(MarkdownTextStorage(markdown: "__b__ _i_").isItalicAtIndex(2)) 47 | XCTAssertFalse(MarkdownTextStorage(markdown: "__b__ _i_").isItalicAtIndex(0)) 48 | XCTAssertEqual("italic", MarkdownTextStorage(markdown: "_italic_").string) 49 | XCTAssertEqual("🐞", MarkdownTextStorage(markdown: "_🐞_").string) 50 | XCTAssertEqual("🐞 italic", MarkdownTextStorage(markdown: "🐞 _italic_").string) 51 | XCTAssertEqual("🐞 italic 🐞", MarkdownTextStorage(markdown: "🐞 _italic_ 🐞").string) 52 | XCTAssertTrue(MarkdownTextStorage(markdown: "🐞 *italic*").isItalicAtIndex(3)) 53 | XCTAssertFalse(MarkdownTextStorage(markdown: "a_b_c_d").isItalicAtIndex(1)) 54 | XCTAssertFalse(MarkdownTextStorage(markdown: "a_b_c_d").isItalicAtIndex(2)) 55 | XCTAssertEqual("a_b_c_d", MarkdownTextStorage(markdown: "a_b_c_d").string) 56 | XCTAssertFalse(MarkdownTextStorage(markdown: "/italic/").isItalicAtIndex(0), "This is wiki italic. Not supported by either GitHub or Bitbucket.") 57 | } 58 | 59 | func testStrikethrough() { 60 | XCTAssertTrue(MarkdownTextStorage(markdown: "~~strikethrough~~").isStrikethroughAtIndex(0)) 61 | XCTAssertFalse(MarkdownTextStorage(markdown: "~strikethrough~").isStrikethroughAtIndex(0)) 62 | XCTAssertEqual("strikethrough", MarkdownTextStorage(markdown: "~~strikethrough~~").string) 63 | XCTAssertEqual("🐞strikethrough🐞", MarkdownTextStorage(markdown: "🐞~~strikethrough~~🐞").string) 64 | XCTAssertEqual("🐞", MarkdownTextStorage(markdown: "~~🐞~~").string) 65 | XCTAssertTrue(MarkdownTextStorage(markdown: "X ~~XX~~").isStrikethroughAtIndex(2)) 66 | XCTAssertTrue(MarkdownTextStorage(markdown: "🐞 ~~XX~~").isStrikethroughAtIndex(3)) 67 | } 68 | 69 | func testNormalLinks() { 70 | XCTAssertTrue(MarkdownTextStorage(markdown: "[Link](http://www.kalliope.org/suburl/)").string.characters.count == 4) 71 | let boldLink = MarkdownTextStorage(markdown: "[**Bold** link](http://www.kalliope.org/suburl/)") 72 | XCTAssertEqual("Bold link", boldLink.string) 73 | XCTAssertTrue(boldLink.isBoldAtIndex(0)) 74 | XCTAssertTrue(boldLink.isLinkAtIndex(0)) 75 | XCTAssertFalse(boldLink.isBoldAtIndex(6)) 76 | XCTAssertTrue(boldLink.isLinkAtIndex(6)) 77 | XCTAssertTrue(MarkdownTextStorage(markdown: "[XXX](http://www.kalliope.org/suburl/) ").isLinkAtIndex(1)) 78 | // The following test exposes issue #18. The "#3835772" part gets recognized as a commit sha which causes problems. 79 | XCTAssertTrue(MarkdownTextStorage(markdown: "[Static Overflow](http://stackoverflow.com/questions/1637332/static-const-vs-define/3835772#3835772)").string.range(of: ")") == nil) 80 | XCTAssertTrue(MarkdownTextStorage(markdown: "[Static Overflow](http://stackoverflow.com/questions/1637332/static-const-vs-define/3835772#383)").string.range(of: ")") == nil) 81 | XCTAssertEqual("]", MarkdownTextStorage(markdown: "[\\]](http://www.kalliope.org/)").string, "Escapes in links") 82 | XCTAssertEqual("🐞", MarkdownTextStorage(markdown: "[🐞](http://www.kalliope.org/)").string, "Emojis in links") 83 | XCTAssertEqual("🐞🐞", MarkdownTextStorage(markdown: "[🐞](http://www.kalliope.org/)🐞").string, "Emojis in links") 84 | XCTAssertEqual("🐞🐞🐞", MarkdownTextStorage(markdown: "🐞[🐞](http://www.kalliope.org/)🐞").string, "Emojis in links") 85 | XCTAssertEqual("🐞X🐞", MarkdownTextStorage(markdown: "🐞[X](http://www.kalliope.org/)🐞").string, "Emojis in links") 86 | XCTAssertTrue(MarkdownTextStorage(markdown: "[🐞](http://www.kalliope.org/)").isLinkAtIndex(0), "Emojis in links") 87 | XCTAssertTrue(MarkdownTextStorage(markdown: "A [🐞](http://www.kalliope.org/)").isLinkAtIndex(2), "Emojis in links") 88 | XCTAssertFalse(MarkdownTextStorage(markdown: "A [🐞](http://www.kalliope.org/)").isLinkAtIndex(1), "Emojis in links") 89 | XCTAssertTrue(MarkdownTextStorage(markdown: "🐞[A](http://www.kalliope.org/)").isLinkAtIndex(2), "This is the real culprit of a bunch of failing tests below") 90 | XCTAssertTrue(MarkdownTextStorage(markdown: "🐞 [A](http://www.kalliope.org/)").isLinkAtIndex(3), "This is the real culprit of a bunch of failing tests below") 91 | XCTAssertTrue(MarkdownTextStorage(markdown: "[A](http://www.kalliope.org/)🐞").isLinkAtIndex(0)) 92 | XCTAssertEqual("...XXX...", MarkdownTextStorage(markdown: "...[XXX]( https://appsto.re/dk/VxQbN.i)...").string, "Leading whitespace in the URL part") 93 | XCTAssertEqual("...XXX...", MarkdownTextStorage(markdown: "...[XXX](\nhttps://appsto.re/dk/VxQbN.i)...").string, "Leading whitespace (newline) in the URL part") 94 | XCTAssertEqual("...XXX...", MarkdownTextStorage(markdown: "...[XXX]( https://appsto.re/dk/VxQbN.i )...").string, "Leading and trailing whitespace in the URL part") 95 | } 96 | 97 | func testRawLinks() { 98 | XCTAssertTrue(MarkdownTextStorage(markdown: "XXX http://www.kalliope.org/suburl/").isLinkAtIndex(5)) 99 | XCTAssertTrue(MarkdownTextStorage(markdown: "http://www.kalliope.org/suburl/ http://www.kalliope.org/suburl/").isLinkAtIndex(5), "Two raw links side by side") 100 | XCTAssertTrue(MarkdownTextStorage(markdown: "http://www.kalliope.org/suburl/").isLinkAtIndex(5)) 101 | XCTAssertTrue(MarkdownTextStorage(markdown: "https://www.kalliope.org/suburl/#anchor").isLinkAtIndex(5)) 102 | XCTAssertFalse(MarkdownTextStorage(markdown: "(http://www.kalliope.org/suburl/").isLinkAtIndex(5)) 103 | XCTAssertTrue(MarkdownTextStorage(markdown: "X https://www.kalliope.org/suburl/#anchor").isLinkAtIndex(2)) 104 | XCTAssertEqual("🐞 https://www.kalliope.org/", MarkdownTextStorage(markdown: "🐞 https://www.kalliope.org/").string) 105 | XCTAssertEqual("🐞 https://www.kalliope.org/ 🐞", MarkdownTextStorage(markdown: "🐞 https://www.kalliope.org/ 🐞").string) 106 | XCTAssertTrue(MarkdownTextStorage(markdown: "🐞 https://www.kalliope.org/").isLinkAtIndex(3)) 107 | XCTAssertTrue(MarkdownTextStorage(markdown: "https://www.kalliope.org/ 🐞").isLinkAtIndex(0)) 108 | XCTAssertTrue(MarkdownTextStorage(markdown: "🐞 https://www.kalliope.org/suburl/#anchor").isLinkAtIndex(3)) 109 | XCTAssertEqual("http://www.kalliope.org/suburl/", MarkdownTextStorage(markdown: "http://www.kalliope.org/suburl/").linkAtIndex(0) ?? "No link found") 110 | let linkInludingSha = MarkdownTextStorage(markdown: "http://stackoverflow.com/questions/1637332/static-const-vs-define/3835772#3835772") 111 | XCTAssertEqual("http://stackoverflow.com/questions/1637332/static-const-vs-define/3835772#3835772", linkInludingSha.linkAtIndex(0) ?? "No link found") 112 | XCTAssertEqual("http://stackoverflow.com/questions/1637332/static-const-vs-define/3835772#3835772", linkInludingSha.string) 113 | XCTAssertEqual(MarkdownTextStorage(markdown: "(http://www.kalliope.org/suburl)").string, "(http://www.kalliope.org/suburl)") 114 | XCTAssertEqual(MarkdownTextStorage(markdown: "A (http://www.kalliope.org/suburl/) B").string, "A (http://www.kalliope.org/suburl/) B") 115 | XCTAssertEqual(MarkdownTextStorage(markdown: "(See https://api.imgur.com/#authentication)").string, "(See https://api.imgur.com/#authentication)") 116 | XCTAssertEqual("https://www.branchesapp.co/", MarkdownTextStorage(markdown: "https://www.branchesapp.co/").linkAtIndex(0) ?? "No link found") 117 | XCTAssertEqual("https://www.branchesapp.co/test", MarkdownTextStorage(markdown: "https://www.branchesapp.co/test").linkAtIndex(0) ?? "No link found") 118 | XCTAssertEqual("https://www.branchesapp.co/test#test", MarkdownTextStorage(markdown: "https://www.branchesapp.co/test#test").linkAtIndex(0) ?? "No link found") 119 | XCTAssertEqual("https://www.branchesapp.co/", MarkdownTextStorage(markdown: "https://www.branchesapp.co/.").linkAtIndex(0) ?? "No link found") 120 | XCTAssertEqual("https://www.branchesapp.co/test", MarkdownTextStorage(markdown: "https://www.branchesapp.co/test.").linkAtIndex(0) ?? "No link found") 121 | XCTAssertEqual("https://www.branchesapp.co/test#test", MarkdownTextStorage(markdown: "https://www.branchesapp.co/test#test.").linkAtIndex(0) ?? "No link found") 122 | XCTAssertEqual("https://www.branchesapp.co/", MarkdownTextStorage(markdown: "https://www.branchesapp.co/,").linkAtIndex(0) ?? "No link found") 123 | XCTAssertEqual("https://www.branchesapp.co/test", MarkdownTextStorage(markdown: "https://www.branchesapp.co/test,").linkAtIndex(0) ?? "No link found") 124 | XCTAssertEqual("https://www.branchesapp.co/test#test", MarkdownTextStorage(markdown: "https://www.branchesapp.co/test#test,").linkAtIndex(0) ?? "No link found") 125 | XCTAssertEqual("https://www.branchesapp.co/.test", MarkdownTextStorage(markdown: "https://www.branchesapp.co/.test.").linkAtIndex(0) ?? "No link found") 126 | } 127 | 128 | func testUnicodeInRawLinks() { 129 | XCTAssertEqual("🐞 https://www.kalliope.org/", MarkdownTextStorage(markdown: "🐞 [https://www.kalliope.org/](https://www.kalliope.org/)").string) 130 | XCTAssertTrue(MarkdownTextStorage(markdown: "🐞 [XX](https://www.kalliope.org/)").isLinkAtIndex(3)) 131 | XCTAssertTrue(MarkdownTextStorage(markdown: "🐞 [https://www.kalliope.org/](https://www.kalliope.org/)").isLinkAtIndex(3)) 132 | XCTAssertTrue(MarkdownTextStorage(markdown: "🐞 https://www.kalliope.org/").isLinkAtIndex(3)) 133 | } 134 | 135 | func testIssueLinks() { 136 | print(MarkdownTextStorage(markdown: "#123").string) 137 | XCTAssertTrue(MarkdownTextStorage(markdown: "#123").isLinkAtIndex(0)) 138 | XCTAssertFalse(MarkdownTextStorage(markdown: " #123 ").isLinkAtIndex(0)) 139 | XCTAssertTrue(MarkdownTextStorage(markdown: " #123 ").isLinkAtIndex(1)) 140 | XCTAssertTrue(MarkdownTextStorage(markdown: " #123 ").isLinkAtIndex(2)) 141 | XCTAssertTrue(MarkdownTextStorage(markdown: "X #123 ").isLinkAtIndex(2)) 142 | XCTAssertTrue(MarkdownTextStorage(markdown: "🐞 #123 ").isLinkAtIndex(3)) 143 | XCTAssertTrue(MarkdownTextStorage(markdown: "#123 🐞").isLinkAtIndex(0)) 144 | XCTAssertTrue(MarkdownTextStorage(markdown: "🐞 #123 🐞").isLinkAtIndex(3)) 145 | XCTAssertEqual("🐞 #123 🐞", MarkdownTextStorage(markdown: "🐞 #123 🐞").string) 146 | XCTAssertEqual("(#123)", MarkdownTextStorage(markdown: "(#123)").string, "Should keep prefix and postfix") 147 | XCTAssertEqual("#123?", MarkdownTextStorage(markdown: "#123?").string, "Should postfix") 148 | XCTAssertFalse(MarkdownTextStorage(markdown: "#acd").isLinkAtIndex(0)) 149 | XCTAssertFalse(MarkdownTextStorage(markdown: "#123x").isLinkAtIndex(0)) 150 | XCTAssertTrue(MarkdownTextStorage(markdown: "#123?").isLinkAtIndex(0)) 151 | } 152 | 153 | func testCommitLinks() { 154 | XCTAssertTrue(MarkdownTextStorage(markdown: " deadbeef ").isLinkAtIndex(1)) 155 | XCTAssertTrue(MarkdownTextStorage(markdown: "deadbeef").isLinkAtIndex(0)) 156 | XCTAssertTrue(MarkdownTextStorage(markdown: "X deadbeef").isLinkAtIndex(2)) 157 | XCTAssertTrue(MarkdownTextStorage(markdown: "🐞 deadbeef").isLinkAtIndex(3)) 158 | XCTAssertFalse(MarkdownTextStorage(markdown: " cafebabes ").isLinkAtIndex(1)) 159 | XCTAssertTrue(MarkdownTextStorage(markdown: " deadbee ").string.characters.count == 9, "Should keep prefix and postfix") 160 | XCTAssertEqual("(deadbee)", MarkdownTextStorage(markdown: "(deadbeefcafebabe)").string, "Should keep prefix and postfix") 161 | XCTAssertTrue(MarkdownTextStorage(markdown: "cafebabedeadbeef").string.characters.count == 7, "Should truncate link text to 7 hex chars") 162 | XCTAssertFalse(MarkdownTextStorage(markdown: "deadbe").isLinkAtIndex(0), "A recognized SHA is between 7 and 40 hex chars. Not 6.") 163 | XCTAssertTrue(MarkdownTextStorage(markdown: "1dafd76e861262f609db7786b64406101f942f53").isLinkAtIndex(0), "A recognized SHA is between 7 and 40 hex chars. Like 40.") 164 | XCTAssertFalse(MarkdownTextStorage(markdown: "1dafd76e861262f609db7786b64406101f942f530").isLinkAtIndex(0), "A recognized SHA is between 7 and 40 hex chars. Not 41.") 165 | 166 | } 167 | 168 | func testParagraphs() { 169 | XCTAssertEqual("Five Guy Burgers", MarkdownTextStorage(markdown: "Five\nGuy\nBurgers").string, "Should join lines.") 170 | XCTAssertEqual("Five Guy 🐞 Burgers", MarkdownTextStorage(markdown: "Five\nGuy 🐞\nBurgers").string, "Should join lines with emojis") 171 | XCTAssertEqual("🐞 Five Guy Burgers", MarkdownTextStorage(markdown: "🐞 Five\nGuy\nBurgers").string, "Should join lines with emojis") 172 | } 173 | 174 | func testInlineCode() { 175 | XCTAssertEqual("ABC", MarkdownTextStorage(markdown: "`ABC`").string) 176 | XCTAssertTrue(MarkdownTextStorage(markdown: "`ABC`").isMonospaceAtIndex(0)) 177 | XCTAssertEqual("*ABC*", MarkdownTextStorage(markdown: "`*ABC*`").string) 178 | XCTAssertEqual("A *bold* B", MarkdownTextStorage(markdown: "A `*bold*` B").string) 179 | XCTAssertEqual("A *bold* B /italic/", MarkdownTextStorage(markdown: "A `*bold*` B `/italic/`").string) 180 | XCTAssertEqual("A *bold* B", MarkdownTextStorage(markdown: "A `*bold*` B").string) 181 | XCTAssertEqual("[Link](http://apple.com/)", MarkdownTextStorage(markdown: "`[Link](http://apple.com/)`").string) 182 | XCTAssertEqual("A [Link](http://apple.com/) B", MarkdownTextStorage(markdown: "A `[Link](http://apple.com/)` B").string) 183 | XCTAssertEqual("A🐞B🐞C", MarkdownTextStorage(markdown: "`A🐞B🐞C`").string) 184 | XCTAssertEqual("💣A🐞B🐞C💣", MarkdownTextStorage(markdown: "💣`A🐞B🐞C`💣").string) 185 | XCTAssertTrue(MarkdownTextStorage(markdown: "💣`ABC`").isMonospaceAtIndex(2)) 186 | XCTAssertTrue(MarkdownTextStorage(markdown: "`ABC`💣").isMonospaceAtIndex(2)) 187 | XCTAssertFalse(MarkdownTextStorage(markdown: "`ABC`💣").isMonospaceAtIndex(3)) 188 | } 189 | 190 | func testBackslashEscaping() { 191 | XCTAssertEqual("*", MarkdownTextStorage(markdown: "\\*").string, "Basic escape") 192 | XCTAssertEqual("&", MarkdownTextStorage(markdown: "\\&").string, "Basic escape") 193 | XCTAssertEqual("X\\Y", MarkdownTextStorage(markdown: "X\\\\Y").string, "Backslash escapes") 194 | XCTAssertEqual("*", MarkdownTextStorage(markdown: "*\\**").string, "Escaped * inside italic marks") 195 | XCTAssertTrue(MarkdownTextStorage(markdown: "*\\**").isItalicAtIndex(0), "Escaped * inside italic marks") 196 | XCTAssertEqual("[]", MarkdownTextStorage(markdown: "\\[\\]").string, "Two escapes next to each other") 197 | XCTAssertFalse(MarkdownTextStorage(markdown: "\\*A\\*").isItalicAtIndex(0), "Escaped italic is not italic") 198 | XCTAssertEqual("\\*", MarkdownTextStorage(markdown: "`\\*`").string, "Don't escape in code sections") 199 | XCTAssertEqual("💣*🐞", MarkdownTextStorage(markdown: "💣\\*🐞").string, "Basic escape with emojis") 200 | XCTAssertEqual("💣*🐞", MarkdownTextStorage(markdown: "*💣\\*🐞*").string, "Basic escape with emojis in italic") 201 | } 202 | 203 | func testRawLinksInBrackets() { 204 | XCTAssertTrue(MarkdownTextStorage(markdown: "").isLinkAtIndex(1)) 205 | XCTAssertTrue(MarkdownTextStorage(markdown: "").isLinkAtIndex(0)) 206 | XCTAssertEqual("http://www.kalliope.org/", MarkdownTextStorage(markdown: "").string) 207 | XCTAssertTrue("http://www.kalliope.org/" == MarkdownTextStorage(markdown: "").linkAtIndex(0)) 208 | XCTAssertEqual("X http://www.kalliope.org/", MarkdownTextStorage(markdown: "X ").string) 209 | } 210 | 211 | func testNormalLinksWithURLInBrackets() { 212 | XCTAssertTrue(MarkdownTextStorage(markdown: "[XX]()").isLinkAtIndex(0)) 213 | XCTAssertEqual("XX", MarkdownTextStorage(markdown: "[XX]()").string) 214 | XCTAssertTrue("http://www.kalliope.org/" == MarkdownTextStorage(markdown: "[XX]()").linkAtIndex(0)) 215 | XCTAssertEqual("X XX", MarkdownTextStorage(markdown: "X [XX]()").string) 216 | } 217 | 218 | func testRemovingDoubleSpaces() { 219 | XCTAssertEqual("A B C", MarkdownTextStorage(markdown: "A B C").string) 220 | XCTAssertEqual("A B C", MarkdownTextStorage(markdown: "A B \nC").string) 221 | XCTAssertEqual("A B C", MarkdownTextStorage(markdown: "**A B C**").string) 222 | XCTAssertEqual("A B C", MarkdownTextStorage(markdown: "[A B C](http://www.kalliope.org/)").string, "Spaces in links") 223 | XCTAssertEqual("A B C D", MarkdownTextStorage(markdown: "`A B C D`").string) 224 | } 225 | 226 | func testHTMLEntityEscapes() { 227 | XCTAssertEqual("Blåbærgrød", MarkdownTextStorage(markdown: "Blåbærgrød").string) 228 | XCTAssertEqual("← ⇐", MarkdownTextStorage(markdown: "← ⇐").string, "Beyond ASCII") 229 | XCTAssertEqual("∞", MarkdownTextStorage(markdown: "*∞*").string, "Inside italic") 230 | XCTAssertTrue(MarkdownTextStorage(markdown: "*∞*").isItalicAtIndex(0), "Inside italic") 231 | XCTAssertEqual("×", MarkdownTextStorage(markdown: "\\×").string, "Escaping") 232 | XCTAssertEqual("×", MarkdownTextStorage(markdown: "`×`").string, "No entity escaping inside code blocks") 233 | XCTAssertEqual("&xxxxxx;", MarkdownTextStorage(markdown: "&xxxxxx;").string, "Unrecognized entity escape") 234 | XCTAssertEqual("AT&T", MarkdownTextStorage(markdown: "[AT&T](http://www.att.com/)").string, "HTML escapes in links") 235 | } 236 | 237 | func testNumericHTMLEntityEscapes() { 238 | XCTAssertEqual("α&ω", MarkdownTextStorage(markdown: "α&ω").string) 239 | XCTAssertEqual("α&ω", MarkdownTextStorage(markdown: "α&ω").string) 240 | } 241 | 242 | func testEmojiSynonyms() { 243 | XCTAssertEqual("A 💣 B", MarkdownTextStorage(markdown: "A :bomb: B").string) 244 | XCTAssertEqual("💣🐞", MarkdownTextStorage(markdown: ":bomb::beetle:").string) 245 | XCTAssertEqual("\\💣", MarkdownTextStorage(markdown: "\\:bomb:").string, "No backslash escaping") 246 | XCTAssertEqual(":💣:🐞:", MarkdownTextStorage(markdown: "::bomb:::beetle::").string) 247 | XCTAssertEqual("👍👎", MarkdownTextStorage(markdown: ":+1::-1:").string, "Reg exp escaping") 248 | XCTAssertEqual("A💣B", MarkdownTextStorage(markdown: "*A:bomb:B*").string, "Inside italic") 249 | XCTAssertTrue(MarkdownTextStorage(markdown: "*:bomb:*").isItalicAtIndex(0), "Inside italic") 250 | XCTAssertEqual("X:bomb:Y", MarkdownTextStorage(markdown: "`X:bomb:Y`").string, "No emoji synonyms inside code blocks") 251 | XCTAssertEqual(":xxxxxx:", MarkdownTextStorage(markdown: ":xxxxxx:").string, "Unrecognized emoji synonym") 252 | XCTAssertEqual(":bombs:", MarkdownTextStorage(markdown: ":bombs:").string, "Unrecognized emoji synonym") 253 | XCTAssertEqual("A💣B", MarkdownTextStorage(markdown: "[A:bomb:B](http://www.att.com/)").string, "Emojis synonyms in links") 254 | } 255 | 256 | // We should remove HTML comments outside of codeblocks. 257 | func testHTMLComments() { 258 | XCTAssertEqual("AB", MarkdownTextStorage(markdown: "AB").string) 259 | XCTAssertEqual("ABC", MarkdownTextStorage(markdown: "ABC").string) 260 | XCTAssertEqual("ABC", MarkdownTextStorage(markdown: "A``BC").string) 261 | } 262 | 263 | func testHeaderTrimming() { 264 | XCTAssertTrue(MarkdownTextStorage(markdown: "# Header").string.characters.count == 6) 265 | XCTAssertTrue(MarkdownTextStorage(markdown: "# Header").string.characters.count == 6) 266 | XCTAssertTrue(MarkdownTextStorage(markdown: "# Header ").string.characters.count == 6) 267 | } 268 | 269 | // Links and formatting in headlines 270 | func testRichHeadlines() { 271 | XCTAssertTrue(MarkdownTextStorage(markdown: "# Header [Link](http://www.kalliope.org/suburl/)").string.characters.count == 11) 272 | var header = MarkdownTextStorage(markdown: "# Header [Link](http://www.kalliope.org/suburl/)") 273 | XCTAssertEqual("Header Link", header.string) 274 | XCTAssertTrue(header.isLinkAtIndex(8)) 275 | XCTAssertTrue(header.isLinkAtIndex(10)) 276 | header = MarkdownTextStorage(markdown: "# Header http://www.kalliope.org/suburl/") 277 | XCTAssertEqual("Header http://www.kalliope.org/suburl/", header.string) 278 | XCTAssertTrue(header.isLinkAtIndex(8)) 279 | XCTAssertTrue(header.isLinkAtIndex(10)) 280 | header = MarkdownTextStorage(markdown: "# Header #123") 281 | XCTAssertEqual("Header #123", header.string) 282 | XCTAssertTrue(header.isLinkAtIndex(8)) 283 | header = MarkdownTextStorage(markdown: "# _Header_ **bold**") 284 | XCTAssertEqual("Header bold", header.string) 285 | XCTAssertTrue(header.isItalicAtIndex(0)) 286 | XCTAssertTrue(header.isBoldAtIndex(8)) 287 | header = MarkdownTextStorage(markdown: "# Header :bomb:") 288 | XCTAssertEqual("Header 💣", header.string) 289 | } 290 | 291 | } 292 | 293 | extension MarkdownTextStorage { 294 | func isBoldAtIndex(_ index: Int) -> Bool { 295 | let attrs = attributes(at: index, effectiveRange: nil) 296 | if let font = attrs[NSFontAttributeName] as? UIFont { 297 | let traits = font.fontDescriptor.symbolicTraits 298 | return traits.contains(.traitBold) 299 | } else { 300 | return false 301 | } 302 | } 303 | 304 | func isItalicAtIndex(_ index: Int) -> Bool { 305 | let attrs = attributes(at: index, effectiveRange: nil) 306 | if let font = attrs[NSFontAttributeName] as? UIFont { 307 | let traits = font.fontDescriptor.symbolicTraits 308 | return traits.contains(.traitItalic) 309 | } else { 310 | return false 311 | } 312 | } 313 | 314 | func isMonospaceAtIndex(_ index: Int) -> Bool { 315 | let attrs = attributes(at: index, effectiveRange: nil) 316 | if let font = attrs[NSFontAttributeName] as? UIFont { 317 | let traits = font.fontDescriptor.symbolicTraits 318 | return traits.contains(.traitMonoSpace) 319 | } else { 320 | return false 321 | } 322 | } 323 | 324 | func isLinkAtIndex(_ index: Int) -> Bool { 325 | let attrs = attributes(at: index, effectiveRange: nil) 326 | return attrs[NSLinkAttributeName] != nil 327 | } 328 | 329 | func isStrikethroughAtIndex(_ index: Int) -> Bool { 330 | let attrs = attributes(at: index, effectiveRange: nil) 331 | return attrs[NSStrikethroughStyleAttributeName] != nil 332 | } 333 | 334 | 335 | func linkAtIndex(_ index: Int) -> String? { 336 | let attrs = attributes(at: index, effectiveRange: nil) 337 | return attrs[NSLinkAttributeName] as? String 338 | } 339 | 340 | } 341 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/thabz/MarkdownTextView.svg?branch=master)](https://travis-ci.org/thabz/MarkdownTextView) 2 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 3 | 4 | # README 5 | 6 | Display Markdown in a UITextView on iOS 8+ without converting to HTML first. 7 | 8 | ## Features 9 | * Handle asynchronous download of inline images. 10 | * Handle most [GitHub](https://guides.github.com/features/mastering-markdown/) and [Bitbucket](https://confluence.atlassian.com/display/BITBUCKET/Mark+up+comments) extensions for basic Markdown. 11 | 12 | ## Usage 13 | 14 | Drag the `MarkdownTextStorage.swift` into your XCode project. 15 | 16 | ```swift 17 | let markdown = "See [GitHub](https://github.com/)" 18 | let storage = MarkdownTextStorage(markdown) 19 | let textView = MarkdownTextView() 20 | textView.markdownTextStorage = storage 21 | subviews.append(textView) 22 | ``` 23 | 24 | ## Contact 25 | 26 | [@thabz](https://twitter.com/thabz) on Twitter. 27 | 28 | 29 | ## License 30 | 31 | MarkdownTextView is licensed under the MIT License. See `LICENSE` for more information. 32 | --------------------------------------------------------------------------------