├── .gitignore ├── AppKit └── SampleApp │ ├── NSAttributedString+MarkdownTests │ ├── Info.plist │ └── NSAttributedString_MarkdownTests.m │ ├── SampleApp.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcbaselines │ │ └── 445A90692400612800B487B4.xcbaseline │ │ ├── DAAD5161-0D3B-4ADF-81EF-ACE1DEE397B4.plist │ │ └── Info.plist │ └── SampleApp │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ └── Main.storyboard │ ├── Info.plist │ ├── SampleApp.entitlements │ ├── ViewController.h │ ├── ViewController.m │ └── main.m ├── Examples ├── Examples.md ├── Examples.rtf └── Long.md ├── NSAttributedString+Markdown.h ├── NSAttributedString+Markdown.m ├── README.md └── UIKit ├── SampleApp ├── SampleApp.xcodeproj │ └── project.pbxproj └── SampleApp │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── first.imageset │ │ ├── Contents.json │ │ └── first.pdf │ └── second.imageset │ │ ├── Contents.json │ │ └── second.pdf │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── FirstViewController.h │ ├── FirstViewController.m │ ├── Info.plist │ ├── SceneDelegate.h │ ├── SceneDelegate.m │ ├── SecondViewController.h │ ├── SecondViewController.m │ ├── StringData.h │ ├── StringData.m │ └── main.m └── SwiftSampleApp ├── SwiftSampleApp-Bridging-Header.h ├── SwiftSampleApp.xcodeproj └── project.pbxproj └── SwiftSampleApp ├── AppDelegate.swift ├── Assets.xcassets ├── AppIcon.appiconset │ └── Contents.json └── Contents.json ├── Base.lproj ├── LaunchScreen.storyboard └── Main.storyboard ├── Info.plist ├── SceneDelegate.swift └── ViewController.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | # CocoaPods 34 | # 35 | # We recommend against adding the Pods directory to your .gitignore. However 36 | # you should judge for yourself, the pros and cons are mentioned at: 37 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 38 | # 39 | # Pods/ 40 | # 41 | # Add this line if you want to avoid checking in source code from the Xcode workspace 42 | # *.xcworkspace 43 | 44 | # Carthage 45 | # 46 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 47 | # Carthage/Checkouts 48 | 49 | Carthage/Build/ 50 | 51 | # fastlane 52 | # 53 | # It is recommended to not store the screenshots in the git repo. 54 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 55 | # For more information about the recommended setup visit: 56 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 57 | 58 | fastlane/report.xml 59 | fastlane/Preview.html 60 | fastlane/screenshots/**/*.png 61 | fastlane/test_output 62 | 63 | # Code Injection 64 | # 65 | # After new code Injection tools there's a generated folder /iOSInjectionProject 66 | # https://github.com/johnno1962/injectionforxcode 67 | 68 | iOSInjectionProject/ 69 | -------------------------------------------------------------------------------- /AppKit/SampleApp/NSAttributedString+MarkdownTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /AppKit/SampleApp/NSAttributedString+MarkdownTests/NSAttributedString_MarkdownTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributedString_MarkdownTests.m 3 | // NSAttributedString+MarkdownTests 4 | // 5 | // Created by Craig Hockenberry on 2/21/20. 6 | // Copyright © 2020 The Iconfactory. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | #define TESTING 1 // to get -markdownDebug 13 | #import "NSAttributedString+Markdown.h" 14 | 15 | @interface NSAttributedString_MarkdownTests : XCTestCase 16 | 17 | @property (class, nonatomic, readonly, strong) NSString *longMarkdownString; 18 | 19 | @end 20 | 21 | @implementation NSAttributedString_MarkdownTests 22 | 23 | - (void)setUp 24 | { 25 | // Put setup code here. This method is called before the invocation of each test method in the class. 26 | } 27 | 28 | - (void)tearDown 29 | { 30 | // Put teardown code here. This method is called after the invocation of each test method in the class. 31 | } 32 | 33 | + (NSString *)longMarkdownString 34 | { 35 | static NSString *result = nil; 36 | if (! result) { 37 | NSURL *longMarkdownURL = [NSBundle.mainBundle URLForResource:@"Long" withExtension:@"md"]; 38 | NSData *longMarkdownData = [NSData dataWithContentsOfURL:longMarkdownURL]; 39 | result = [[NSString alloc] initWithData:longMarkdownData encoding:NSUTF8StringEncoding]; 40 | } 41 | return result; 42 | } 43 | 44 | static BOOL checkMarkdownToRichText(NSString *testString, NSString *compareString) 45 | { 46 | NSAttributedString *attributedTestString = [[NSAttributedString alloc] initWithMarkdownRepresentation:testString attributes:@{ NSFontAttributeName: [NSFont systemFontOfSize:12.0] }]; 47 | NSString *checkString = [attributedTestString markdownDebug]; 48 | NSLog(@"%s checkString = %@", __PRETTY_FUNCTION__, checkString); 49 | return [checkString isEqual:compareString]; 50 | } 51 | 52 | static BOOL checkRichTextToMarkdown(NSAttributedString *testString, NSString *compareString) 53 | { 54 | NSString *checkString = [testString markdownRepresentation]; 55 | NSLog(@"%s checkString = %@", __PRETTY_FUNCTION__, checkString); 56 | return [checkString isEqual:compareString]; 57 | } 58 | 59 | static BOOL checkMarkdownRoundTrip(NSString *testString) 60 | { 61 | NSAttributedString *attributedTestString = [[NSAttributedString alloc] initWithMarkdownRepresentation:testString attributes:@{ NSFontAttributeName: [NSFont systemFontOfSize:12.0] }]; 62 | NSString *checkString = [attributedTestString markdownRepresentation]; 63 | return [checkString isEqual:testString]; 64 | } 65 | 66 | - (void)testPlainText 67 | { 68 | NSString *testString = @"Test plain text"; 69 | NSString *compareString = @"[Test plain text]( )"; 70 | XCTAssert(checkMarkdownToRichText(testString, compareString), @"Markdown to rich text test failed"); 71 | XCTAssert(checkMarkdownRoundTrip(testString), @"Round-trip test failed"); 72 | } 73 | 74 | - (void)testLiterals 75 | { 76 | NSString *testString = @"Test \\*\\* \\_\\_ and \\* \\_ in string"; 77 | NSString *compareString = @"[Test ** __ and * _ in string]( )"; 78 | XCTAssert(checkMarkdownToRichText(testString, compareString), @"Markdown to rich text test failed"); 79 | // no round-trip test because conversion is lossy (literals aren't strictly needed) 80 | } 81 | 82 | - (void)testBareLiterals 83 | { 84 | NSString *testString = @"Test * and _ in string"; 85 | NSString *compareString = @"[Test * and _ in string]( )"; 86 | XCTAssert(checkMarkdownToRichText(testString, compareString), @"Markdown to rich text test failed"); 87 | XCTAssert(checkMarkdownRoundTrip(testString), @"Round-trip test failed"); 88 | } 89 | 90 | - (void)testSpanWithBareLiterals 91 | { 92 | NSString *testString = @"Test for _span that contains _ and * literals_"; 93 | NSString *compareString = @"[Test for ]( )[span that contains _ and * literals]( I)"; 94 | XCTAssert(checkMarkdownToRichText(testString, compareString), @"Markdown to rich text test failed"); 95 | XCTAssert(checkMarkdownRoundTrip(testString), @"Round-trip test failed"); 96 | } 97 | 98 | - (void)testTabsWithBareLiterals 99 | { 100 | NSString *testString = @"\t*\t*\t*\n\t_\t_\t_"; 101 | NSString *compareString = @"[\\t*\\t*\\t*\\n\\t_\\t_\\t_]( )"; 102 | XCTAssert(checkMarkdownToRichText(testString, compareString), @"Markdown to rich text test failed"); 103 | XCTAssert(checkMarkdownRoundTrip(testString), @"Round-trip test failed"); 104 | } 105 | 106 | - (void)testUnterminatedMarkers 107 | { 108 | NSString *testString = @"Test * unterminated _ markers and \\*\\* _ escapes"; 109 | NSString *compareString = @"[Test * unterminated _ markers and ** _ escapes]( )"; 110 | XCTAssert(checkMarkdownToRichText(testString, compareString), @"Markdown to rich text test failed"); 111 | XCTAssert(checkMarkdownRoundTrip(testString), @"Round-trip test failed"); 112 | } 113 | 114 | - (void)testMiddleMarkers 115 | { 116 | NSString *testString = @"Test un_frigging_believable"; 117 | NSString *compareString = @"[Test un]( )[frigging]( I)[believable]( )"; 118 | XCTAssert(checkMarkdownToRichText(testString, compareString), @"Markdown to rich text test failed"); 119 | XCTAssert(checkMarkdownRoundTrip(testString), @"Round-trip test failed"); 120 | } 121 | 122 | - (void)testLiteralsInSpan 123 | { 124 | NSString *testString = @"Test _literals\\_in\\_text span_"; 125 | NSString *compareString = @"[Test ]( )[literals_in_text span]( I)"; 126 | XCTAssert(checkMarkdownToRichText(testString, compareString), @"Markdown to rich text test failed"); 127 | XCTAssert(checkMarkdownRoundTrip(testString), @"Round-trip test failed"); 128 | } 129 | 130 | - (void)testSymbolsInSpan 131 | { 132 | NSString *testString = @"Test **span with ⌘ and ⚠️ symbols**"; 133 | NSString *compareString = @"[Test ]( )[span with ⌘ and ⚠️ symbols](B )"; 134 | XCTAssert(checkMarkdownToRichText(testString, compareString), @"Markdown to rich text test failed"); 135 | XCTAssert(checkMarkdownRoundTrip(testString), @"Round-trip test failed"); 136 | } 137 | 138 | - (void)testSymbolsAndLiterals 139 | { 140 | NSString *testString = @"Test ¯\\\\\\_(ツ)\\_/¯"; 141 | NSString *compareString = @"[Test ¯\\_(ツ)_/¯]( )"; 142 | XCTAssert(checkMarkdownToRichText(testString, compareString), @"Markdown to rich text test failed"); 143 | XCTAssert(checkMarkdownRoundTrip(testString), @"Round-trip test failed"); 144 | } 145 | 146 | - (void)testStylesEmbedded 147 | { 148 | NSString *testString = @"**Test _emphasis_ embedded** and _Test **strong** embedded_"; 149 | NSString *compareString = @"[Test ](B )[emphasis](BI)[ embedded](B )[ and ]( )[Test ]( I)[strong](BI)[ embedded]( I)"; 150 | XCTAssert(checkMarkdownToRichText(testString, compareString), @"Markdown to rich text test failed"); 151 | XCTAssert(checkMarkdownRoundTrip(testString), @"Round-trip test failed"); 152 | } 153 | 154 | - (void)testStylesEmbeddedWeirdly 155 | { 156 | NSString *testString = @"Test **strong _and** emphasis_ embedded"; 157 | NSString *compareString = @"[Test ]( )[strong ](B )[and](BI)[ emphasis]( I)[ embedded]( )"; 158 | XCTAssert(checkMarkdownToRichText(testString, compareString), @"Markdown to rich text test failed"); 159 | XCTAssert(checkMarkdownRoundTrip(testString), @"Round-trip test failed"); 160 | } 161 | 162 | - (void)testStylesAcrossLines 163 | { 164 | NSString *testString = @"Test the _beginning of a **span\nthat** continues on next line._ Because\n**Markdown** is a visual specfication."; 165 | NSString *compareString = @"[Test the ]( )[beginning of a ]( I)[span\\nthat](BI)[ continues on next line.]( I)[ Because\\n]( )[Markdown](B )[ is a visual specfication.]( )"; 166 | XCTAssert(checkMarkdownToRichText(testString, compareString), @"Markdown to rich text test failed"); 167 | XCTAssert(checkMarkdownRoundTrip(testString), @"Round-trip test failed"); 168 | } 169 | 170 | - (void)testStylesAcrossLineBreaks 171 | { 172 | NSString *testString = @"Test _emphasis\n\nthat will not span_ lines."; 173 | NSString *compareString = @"[Test _emphasis\\n\\nthat will not span_ lines.]( )"; 174 | XCTAssert(checkMarkdownToRichText(testString, compareString), @"Markdown to rich text test failed"); 175 | //XCTAssert(checkMarkdownRoundTrip(testString), @"Round-trip test failed"); 176 | } 177 | 178 | - (void)testBlankSpans 179 | { 180 | NSString *testString = @"Test **this.****\n\n**"; 181 | NSString *compareString = @"[Test ]( )[this.](B )[**\\n\\n**]( )"; 182 | XCTAssert(checkMarkdownToRichText(testString, compareString), @"Markdown to rich text test failed"); 183 | //XCTAssert(checkMarkdownRoundTrip(testString), @"Round-trip test failed"); 184 | } 185 | 186 | - (void)testStylesWithLiterals 187 | { 188 | NSString *testString = @"Test **\\*\\*strong with literals\\*\\*** and _\\_emphasis too\\__"; 189 | NSString *compareString = @"[Test ]( )[**strong with literals**](B )[ and ]( )[_emphasis too_]( I)"; 190 | XCTAssert(checkMarkdownToRichText(testString, compareString), @"Markdown to rich text test failed"); 191 | XCTAssert(checkMarkdownRoundTrip(testString), @"Round-trip test failed"); 192 | } 193 | 194 | - (void)testBasicLinks 195 | { 196 | NSString *testString = @"Test [inline links](https://iconfactory.com) and automatic links like "; 197 | NSString *compareString = @"[Test ]( )[inline links]( )[ and automatic links like ]( )[https://daringfireball.net/projects/markdown/syntax]( )"; 198 | XCTAssert(checkMarkdownToRichText(testString, compareString), @"Markdown to rich text test failed"); 199 | XCTAssert(checkMarkdownRoundTrip(testString), @"Round-trip test failed"); 200 | } 201 | 202 | - (void)testOtherLinks 203 | { 204 | NSString *testString = @"Test "; 205 | NSString *compareString = @"[Test ]( )[zippy@pinhead.com]( )[ ]( )[tel:867-5309]( )[ ]( )[ssh:l33t@daringfireball.net]( )[ ]( )[dict://tot]( )"; 206 | XCTAssert(checkMarkdownToRichText(testString, compareString), @"Markdown to rich text test failed"); 207 | XCTAssert(checkMarkdownRoundTrip(testString), @"Round-trip test failed"); 208 | } 209 | 210 | - (void)testAutomaticLinksWithUnterminatedMarkers 211 | { 212 | NSString *testString = @"Test "; 213 | NSString *compareString = @"[Test ]( )[https://music.apple.com/us/album/egg-man/721276795?i=721277066&uo=4&app=itunes&at=10l4G7&ct=STREAMER_MAC]( )"; 214 | XCTAssert(checkMarkdownToRichText(testString, compareString), @"Markdown to rich text test failed"); 215 | XCTAssert(checkMarkdownRoundTrip(testString), @"Round-trip test failed"); 216 | } 217 | 218 | - (void)testAttributeLinksWithoutEscaping 219 | { 220 | NSURL *URL = [NSURL URLWithString:@"https://music.apple.com/us/album/*/721276795?i=721277066&uo=4&app=itunes&at=10l4G7&ct=STREAMER_MAC"]; 221 | 222 | NSMutableAttributedString *attributedTestString = [[NSMutableAttributedString alloc] initWithString:@""]; 223 | NSAttributedString *bareLinkString = [[NSAttributedString alloc] initWithString:URL.absoluteString attributes:@{NSLinkAttributeName: URL}]; 224 | [attributedTestString appendAttributedString:bareLinkString]; 225 | NSAttributedString *spacerString = [[NSAttributedString alloc] initWithString:@" " attributes:nil]; 226 | [attributedTestString appendAttributedString:spacerString]; 227 | NSAttributedString *namedLinkString = [[NSAttributedString alloc] initWithString:@"inline" attributes:@{NSLinkAttributeName: URL}]; 228 | [attributedTestString appendAttributedString:namedLinkString]; 229 | 230 | NSString *compareString = @" [inline](https://music.apple.com/us/album/*/721276795?i=721277066&uo=4&app=itunes&at=10l4G7&ct=STREAMER_MAC)"; 231 | 232 | XCTAssert(checkRichTextToMarkdown(attributedTestString, compareString), @"Rich text to Markdown test failed"); 233 | } 234 | 235 | - (void)testAutomaticLinksWithMarkers 236 | { 237 | NSString *testString = @"Test "; 238 | NSString *compareString = @"[Test ]( )[https://daringfireball.net/2020/02/my_2019_apple_report_card]( )"; 239 | XCTAssert(checkMarkdownToRichText(testString, compareString), @"Markdown to rich text test failed"); 240 | XCTAssert(checkMarkdownRoundTrip(testString), @"Round-trip test failed"); 241 | } 242 | 243 | - (void)testInlineLinksWithMarkers 244 | { 245 | NSString *testString = @"Test **[w\\_oo\\_t](https://daringfireball.net/2020/02/my_2019_apple_report_card)**"; 246 | NSString *compareString = @"[Test ]( )[w_oo_t](B )"; XCTAssert(checkMarkdownToRichText(testString, compareString), @"Markdown to rich text test failed"); 247 | XCTAssert(checkMarkdownRoundTrip(testString), @"Round-trip test failed"); 248 | } 249 | 250 | - (void)testIgnoreListMarker 251 | { 252 | // https://daringfireball.net/projects/markdown/syntax#list 253 | // "List markers typically start at the left margin, but may be indented by up to three spaces. List markers must be followed by one or more spaces or a tab. 254 | 255 | NSString *testString = @"* One **item**\n * Two\n * _Three_\n * Four\n*\tFive\n * Six\n\t*\tSeven"; 256 | NSString *compareString = @"[* One ]( )[item](B )[\\n * Two\\n * ]( )[Three]( I)[\\n * Four\\n*\\tFive\\n * Six\\n\\t*\\tSeven]( )"; 257 | XCTAssert(checkMarkdownToRichText(testString, compareString), @"Markdown to rich text test failed"); 258 | XCTAssert(checkMarkdownRoundTrip(testString), @"Round-trip test failed"); 259 | } 260 | 261 | - (void)testIgnoreHorizontalRules 262 | { 263 | // https://daringfireball.net/projects/markdown/syntax#hr 264 | // "You can produce a horizontal rule tag by placing three or more hyphens, asterisks, or underscores on a line by themselves. If you wish, you may use spaces between the hyphens or asterisks." 265 | 266 | NSString *testString = @"* * *\n***\n*****\n * * * \n*** ***\n_ _ _\n___\n_____\n _ _ _ \n___ ___\n---\n"; 267 | NSString *compareString = @"[* * *\\n***\\n*****\\n * * * \\n*** ***\\n_ _ _\\n___\\n_____\\n _ _ _ \\n___ ___\\n---\\n]( )"; 268 | XCTAssert(checkMarkdownToRichText(testString, compareString), @"Markdown to rich text test failed"); 269 | XCTAssert(checkMarkdownRoundTrip(testString), @"Round-trip test failed"); 270 | } 271 | 272 | - (void)testForPeopleWhoDoMarkdownWrong 273 | { 274 | NSString *testString = @"Test *single asterisk* and __double underscore__"; 275 | NSString *compareString = @"[Test ]( )[single asterisk]( I)[ and ]( )[double underscore](B )"; 276 | XCTAssert(checkMarkdownToRichText(testString, compareString), @"People who do Markdown wrong test failed"); 277 | // no round-trip test because the wrongs will be righted 278 | } 279 | 280 | - (void)testForPlainTextFromHell 281 | { 282 | // Hell: https://support.code42.com/Administrator/6/Planning_and_installing/Manage_app_installations_in_your_Code42_environment/Deployment_script_and_command_reference 283 | 284 | NSString *testString = @" msiexec /i Code42CrashPlan_n.n.n_Win64.msi\n CP_ARGS=“DEPLOYMENT_URL=https://.host:port\n &DEPLOYMENT_POLICY_TOKEN=0fb12341-246b-448d-b07f-c6573ad5ad02\n &SSL_WHITELIST=7746278a457e64737094c44eeb2bbc32357ece44\n &PROXY_URL=http://.host:port/fname.pac”\n CP_SILENT=true DEVICE_CLOAKED=false /norestart /qn \n\n"; 285 | NSString *compareString = @"[ msiexec /i Code42CrashPlan]( )[n.n.n]( I)[Win64.msi\\n CP]( )[ARGS=“DEPLOYMENT]( I)[URL=https://.host:port\\n &DEPLOYMENT]( )[POLICY]( I)[TOKEN=0fb12341-246b-448d-b07f-c6573ad5ad02\\n &SSL]( )[WHITELIST=7746278a457e64737094c44eeb2bbc32357ece44\\n &PROXY]( I)[URL=http://.host:port/fname.pac”\\n CP]( )[SILENT=true DEVICE]( I)[CLOAKED=false /norestart /qn \\n\\n]( )"; 286 | XCTAssert(checkMarkdownToRichText(testString, compareString), @"Markdown to rich text test failed"); 287 | XCTAssert(checkMarkdownRoundTrip(testString), @"Round-trip test failed"); 288 | } 289 | 290 | - (void)testConversionToRichTextPerformance 291 | { 292 | NSString *longMarkdownString = NSAttributedString_MarkdownTests.longMarkdownString; 293 | 294 | [self measureBlock:^{ 295 | NSAttributedString *convertedMarkdownAttributedString = [[NSAttributedString alloc] initWithMarkdownRepresentation:longMarkdownString attributes:@{ NSFontAttributeName: [NSFont systemFontOfSize:12.0] }]; 296 | NSLog(@"original length = %ld, converted length = %ld", longMarkdownString.length, convertedMarkdownAttributedString.length); 297 | }]; 298 | } 299 | 300 | - (void)testConversionToMarkdownPerformance 301 | { 302 | NSString *longMarkdownString = NSAttributedString_MarkdownTests.longMarkdownString; 303 | NSAttributedString *longAttributedMarkdownString = [[NSAttributedString alloc] initWithMarkdownRepresentation:longMarkdownString attributes:@{ NSFontAttributeName: [NSFont systemFontOfSize:12.0] }]; 304 | 305 | [self measureBlock:^{ 306 | NSString *convertedMarkdownString = [longAttributedMarkdownString markdownRepresentation]; 307 | NSLog(@"original length = %ld, converted length = %ld", longAttributedMarkdownString.length, convertedMarkdownString.length); 308 | }]; 309 | } 310 | 311 | - (void)testInlineLinksWithEscapes 312 | { 313 | NSString *testString = @"[\\(opt\\-shift\\-k\\)](https://apple.com)\n"; 314 | NSString *compareString = @"[(opt-shift-k)]( )[\\n]( )"; 315 | XCTAssert(checkMarkdownToRichText(testString, compareString), @"Markdown to rich text test failed"); 316 | } 317 | 318 | - (void)testInlineLinksWithWithoutEscapes 319 | { 320 | NSString *testString = @"[This (should not break) parsing](https://apple.com)\n"; 321 | NSString *compareString = @"[This (should not break) parsing]( )[\\n]( )"; 322 | XCTAssert(checkMarkdownToRichText(testString, compareString), @"Markdown to rich text test failed"); 323 | } 324 | 325 | - (void)testMarkdownEscapes 326 | { 327 | NSMutableAttributedString *attributedTestString = [[NSMutableAttributedString alloc] initWithString:@"my_variable_name = 1;"]; 328 | NSString *compareString = @"my\\_variable\\_name = 1;"; 329 | XCTAssert(checkRichTextToMarkdown(attributedTestString, compareString), @"Rich text to Markdown test failed"); 330 | } 331 | 332 | - (void)testBackslashEscapes 333 | { 334 | NSString *testString = @"This is two \\\\\\\\ escapes and this is \\\\\\\\\\\\ three and don't break here \\"; 335 | NSString *compareString = @"[This is two \\\\ escapes and this is \\\\\\ three and don't break here \\]( )"; 336 | XCTAssert(checkMarkdownToRichText(testString, compareString), @"Markdown to rich text test failed"); 337 | XCTAssert(checkMarkdownRoundTrip(testString), @"Round-trip test failed"); 338 | } 339 | 340 | // NOTE: The following tests were submitted by Simon Ward in https://github.com/chockenberry/MarkdownAttributedString/issues/4 341 | 342 | - (NSAttributedString *)attributedString:(NSString *)text withTraits:(NSFontDescriptorSymbolicTraits)traits 343 | { 344 | NSFont *font = [NSFont systemFontOfSize: 17.0]; 345 | NSFontDescriptor *fontDescriptor = font.fontDescriptor; 346 | NSFontDescriptorSymbolicTraits symbolicTraits = fontDescriptor.symbolicTraits; 347 | 348 | NSFontDescriptorSymbolicTraits newSymbolicTraits = symbolicTraits | traits; 349 | NSFontDescriptor *newFontDescriptor = [fontDescriptor fontDescriptorWithSymbolicTraits:newSymbolicTraits]; 350 | NSFont *newFont = [NSFont fontWithDescriptor:newFontDescriptor size:font.pointSize]; 351 | 352 | return [[NSAttributedString alloc] initWithString:text attributes:@{NSFontAttributeName: newFont}]; 353 | } 354 | 355 | - (void)applySymbolicTraits:(NSFontDescriptorSymbolicTraits)traits toAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range 356 | { 357 | NSFont *font = [NSFont systemFontOfSize: 17.0]; 358 | NSFontDescriptor *fontDescriptor = font.fontDescriptor; 359 | NSFontDescriptorSymbolicTraits symbolicTraits = fontDescriptor.symbolicTraits; 360 | 361 | NSFontDescriptorSymbolicTraits newSymbolicTraits = symbolicTraits | traits; 362 | NSFontDescriptor *newFontDescriptor = [fontDescriptor fontDescriptorWithSymbolicTraits:newSymbolicTraits]; 363 | NSFont *newFont = [NSFont fontWithDescriptor:newFontDescriptor size:font.pointSize]; 364 | 365 | [attributedString setAttributes:@{NSFontAttributeName: newFont} range:range]; 366 | } 367 | 368 | - (void)testItalic 369 | { 370 | NSAttributedString *attrString = [self attributedString:@"Italic" withTraits:NSFontDescriptorTraitItalic]; 371 | XCTAssertEqualObjects(attrString.markdownRepresentation, @"_Italic_"); 372 | } 373 | 374 | - (void)testBold 375 | { 376 | NSAttributedString *attrString = [self attributedString:@"Bold" withTraits:NSFontDescriptorTraitBold]; 377 | XCTAssertEqualObjects(attrString.markdownRepresentation, @"**Bold**"); 378 | } 379 | 380 | - (void)testBoldItalic 381 | { 382 | NSAttributedString *attrString = [self attributedString:@"Italic Bold" withTraits:NSFontDescriptorTraitItalic | NSFontDescriptorTraitBold]; 383 | XCTAssertEqualObjects(attrString.markdownRepresentation, @"**_Italic Bold_**"); 384 | } 385 | 386 | - (void)testSeparate 387 | { 388 | NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] initWithString:@"This is italic and this is bold."]; 389 | [self applySymbolicTraits:NSFontDescriptorTraitItalic toAttributedString:attrString range:NSMakeRange(8, 6)]; 390 | [self applySymbolicTraits:NSFontDescriptorTraitBold toAttributedString:attrString range:NSMakeRange(27, 4)]; 391 | 392 | XCTAssertEqualObjects(attrString.markdownRepresentation, @"This is _italic_ and this is **bold**."); 393 | } 394 | 395 | #if NO 396 | // NOTE: This test is disabled for now: https://github.com/chockenberry/MarkdownAttributedString/issues/4 397 | // I'm not sure this is a valid test, Markdown (like HTML) has no requirement for the order of styling or its scope. 398 | - (void)testOverlap 399 | { 400 | NSMutableAttributedString *attrString1 = [[NSMutableAttributedString alloc] initWithString:@"Italic Bold"]; 401 | [self applySymbolicTraits:NSFontDescriptorTraitItalic toAttributedString:attrString1 range:NSMakeRange(0, 11)]; 402 | [self applySymbolicTraits:NSFontDescriptorTraitItalic | NSFontDescriptorTraitBold toAttributedString:attrString1 range:NSMakeRange(7, 4)]; 403 | 404 | XCTAssertEqualObjects(attrString1.markdownRepresentation, @"_Italic **Bold**_"); 405 | 406 | NSMutableAttributedString *attrString2 = [[NSMutableAttributedString alloc] initWithString:@"Bold Italic"]; 407 | [self applySymbolicTraits:NSFontDescriptorTraitBold toAttributedString:attrString2 range:NSMakeRange(0, 11)]; 408 | [self applySymbolicTraits:NSFontDescriptorTraitBold | NSFontDescriptorTraitItalic toAttributedString:attrString2 range:NSMakeRange(5, 6)]; 409 | 410 | XCTAssertEqualObjects(attrString2.markdownRepresentation, @"**Bold _Italic_**"); 411 | } 412 | #endif 413 | 414 | #if NO 415 | // NOTE: This test is disabled for now: https://github.com/chockenberry/MarkdownAttributedString/issues/5 416 | // The test will pass if ESCAPE_ALL_LITERALS is turned on, but that has a nasty side effect where the punctuation in regular text gets escaped and becomes hard to read. 417 | - (void)testEscaping 418 | { 419 | for (NSString *character in @[@"\\", @"`", @"*", @"_", @"{", @"}", @"[", @"]", @"(", @")", @"#", @"+", @"-", @".", @"!"]) { 420 | NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:character]; 421 | NSString *expected = [@"\\" stringByAppendingString:character]; 422 | XCTAssertEqualObjects(attrString.markdownRepresentation, expected); 423 | } 424 | } 425 | #endif 426 | 427 | @end 428 | -------------------------------------------------------------------------------- /AppKit/SampleApp/SampleApp.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 445A906D2400612900B487B4 /* NSAttributedString_MarkdownTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 445A906C2400612900B487B4 /* NSAttributedString_MarkdownTests.m */; }; 11 | 447867B42401968F00EBE733 /* Long.md in Resources */ = {isa = PBXBuildFile; fileRef = 447867B2240195C700EBE733 /* Long.md */; }; 12 | 44B1EE8123DB90D5004E2E29 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 44B1EE8023DB90D5004E2E29 /* AppDelegate.m */; }; 13 | 44B1EE8423DB90D5004E2E29 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 44B1EE8323DB90D5004E2E29 /* ViewController.m */; }; 14 | 44B1EE8623DB90D6004E2E29 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 44B1EE8523DB90D6004E2E29 /* Assets.xcassets */; }; 15 | 44B1EE8923DB90D6004E2E29 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 44B1EE8723DB90D6004E2E29 /* Main.storyboard */; }; 16 | 44B1EE8C23DB90D6004E2E29 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 44B1EE8B23DB90D6004E2E29 /* main.m */; }; 17 | 44B1EE9523DB91C9004E2E29 /* NSAttributedString+Markdown.m in Sources */ = {isa = PBXBuildFile; fileRef = 44B1EE9323DB91C9004E2E29 /* NSAttributedString+Markdown.m */; }; 18 | 44B1EE9923DB9254004E2E29 /* Examples.md in Resources */ = {isa = PBXBuildFile; fileRef = 44B1EE9723DB9254004E2E29 /* Examples.md */; }; 19 | 44B1EE9A23DB9254004E2E29 /* Examples.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 44B1EE9823DB9254004E2E29 /* Examples.rtf */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXContainerItemProxy section */ 23 | 445A906F2400612900B487B4 /* PBXContainerItemProxy */ = { 24 | isa = PBXContainerItemProxy; 25 | containerPortal = 44B1EE7423DB90D5004E2E29 /* Project object */; 26 | proxyType = 1; 27 | remoteGlobalIDString = 44B1EE7B23DB90D5004E2E29; 28 | remoteInfo = SampleApp; 29 | }; 30 | /* End PBXContainerItemProxy section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | 445A906A2400612800B487B4 /* NSAttributedString+MarkdownTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "NSAttributedString+MarkdownTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 34 | 445A906C2400612900B487B4 /* NSAttributedString_MarkdownTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NSAttributedString_MarkdownTests.m; sourceTree = ""; }; 35 | 445A906E2400612900B487B4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 36 | 447867B2240195C700EBE733 /* Long.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Long.md; sourceTree = ""; }; 37 | 44962FF523E8BCC400E2A598 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../../README.md; sourceTree = ""; }; 38 | 44B1EE7C23DB90D5004E2E29 /* SampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 39 | 44B1EE7F23DB90D5004E2E29 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 40 | 44B1EE8023DB90D5004E2E29 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 41 | 44B1EE8223DB90D5004E2E29 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 42 | 44B1EE8323DB90D5004E2E29 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 43 | 44B1EE8523DB90D6004E2E29 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 44 | 44B1EE8823DB90D6004E2E29 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 45 | 44B1EE8A23DB90D6004E2E29 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 46 | 44B1EE8B23DB90D6004E2E29 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 47 | 44B1EE8D23DB90D6004E2E29 /* SampleApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SampleApp.entitlements; sourceTree = ""; }; 48 | 44B1EE9323DB91C9004E2E29 /* NSAttributedString+Markdown.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSAttributedString+Markdown.m"; path = "../../NSAttributedString+Markdown.m"; sourceTree = ""; }; 49 | 44B1EE9423DB91C9004E2E29 /* NSAttributedString+Markdown.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSAttributedString+Markdown.h"; path = "../../NSAttributedString+Markdown.h"; sourceTree = ""; }; 50 | 44B1EE9723DB9254004E2E29 /* Examples.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Examples.md; sourceTree = ""; }; 51 | 44B1EE9823DB9254004E2E29 /* Examples.rtf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.rtf; path = Examples.rtf; sourceTree = ""; }; 52 | /* End PBXFileReference section */ 53 | 54 | /* Begin PBXFrameworksBuildPhase section */ 55 | 445A90672400612800B487B4 /* Frameworks */ = { 56 | isa = PBXFrameworksBuildPhase; 57 | buildActionMask = 2147483647; 58 | files = ( 59 | ); 60 | runOnlyForDeploymentPostprocessing = 0; 61 | }; 62 | 44B1EE7923DB90D5004E2E29 /* Frameworks */ = { 63 | isa = PBXFrameworksBuildPhase; 64 | buildActionMask = 2147483647; 65 | files = ( 66 | ); 67 | runOnlyForDeploymentPostprocessing = 0; 68 | }; 69 | /* End PBXFrameworksBuildPhase section */ 70 | 71 | /* Begin PBXGroup section */ 72 | 445A906B2400612900B487B4 /* NSAttributedString+MarkdownTests */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | 445A906C2400612900B487B4 /* NSAttributedString_MarkdownTests.m */, 76 | 445A906E2400612900B487B4 /* Info.plist */, 77 | ); 78 | path = "NSAttributedString+MarkdownTests"; 79 | sourceTree = ""; 80 | }; 81 | 44B1EE7323DB90D5004E2E29 = { 82 | isa = PBXGroup; 83 | children = ( 84 | 44962FF523E8BCC400E2A598 /* README.md */, 85 | 44B1EE9423DB91C9004E2E29 /* NSAttributedString+Markdown.h */, 86 | 44B1EE9323DB91C9004E2E29 /* NSAttributedString+Markdown.m */, 87 | 44B1EE7E23DB90D5004E2E29 /* SampleApp */, 88 | 445A906B2400612900B487B4 /* NSAttributedString+MarkdownTests */, 89 | 44B1EE7D23DB90D5004E2E29 /* Products */, 90 | ); 91 | sourceTree = ""; 92 | }; 93 | 44B1EE7D23DB90D5004E2E29 /* Products */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | 44B1EE7C23DB90D5004E2E29 /* SampleApp.app */, 97 | 445A906A2400612800B487B4 /* NSAttributedString+MarkdownTests.xctest */, 98 | ); 99 | name = Products; 100 | sourceTree = ""; 101 | }; 102 | 44B1EE7E23DB90D5004E2E29 /* SampleApp */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | 44B1EE7F23DB90D5004E2E29 /* AppDelegate.h */, 106 | 44B1EE8023DB90D5004E2E29 /* AppDelegate.m */, 107 | 44B1EE8223DB90D5004E2E29 /* ViewController.h */, 108 | 44B1EE8323DB90D5004E2E29 /* ViewController.m */, 109 | 44B1EE8523DB90D6004E2E29 /* Assets.xcassets */, 110 | 44B1EE8723DB90D6004E2E29 /* Main.storyboard */, 111 | 44B1EE9623DB9254004E2E29 /* Examples */, 112 | 44B1EE8A23DB90D6004E2E29 /* Info.plist */, 113 | 44B1EE8B23DB90D6004E2E29 /* main.m */, 114 | 44B1EE8D23DB90D6004E2E29 /* SampleApp.entitlements */, 115 | ); 116 | path = SampleApp; 117 | sourceTree = ""; 118 | }; 119 | 44B1EE9623DB9254004E2E29 /* Examples */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | 44B1EE9723DB9254004E2E29 /* Examples.md */, 123 | 44B1EE9823DB9254004E2E29 /* Examples.rtf */, 124 | 447867B2240195C700EBE733 /* Long.md */, 125 | ); 126 | name = Examples; 127 | path = ../../../Examples; 128 | sourceTree = ""; 129 | }; 130 | /* End PBXGroup section */ 131 | 132 | /* Begin PBXNativeTarget section */ 133 | 445A90692400612800B487B4 /* NSAttributedString+MarkdownTests */ = { 134 | isa = PBXNativeTarget; 135 | buildConfigurationList = 445A90732400612900B487B4 /* Build configuration list for PBXNativeTarget "NSAttributedString+MarkdownTests" */; 136 | buildPhases = ( 137 | 445A90662400612800B487B4 /* Sources */, 138 | 445A90672400612800B487B4 /* Frameworks */, 139 | 445A90682400612800B487B4 /* Resources */, 140 | ); 141 | buildRules = ( 142 | ); 143 | dependencies = ( 144 | 445A90702400612900B487B4 /* PBXTargetDependency */, 145 | ); 146 | name = "NSAttributedString+MarkdownTests"; 147 | productName = "NSAttributedString+MarkdownTests"; 148 | productReference = 445A906A2400612800B487B4 /* NSAttributedString+MarkdownTests.xctest */; 149 | productType = "com.apple.product-type.bundle.unit-test"; 150 | }; 151 | 44B1EE7B23DB90D5004E2E29 /* SampleApp */ = { 152 | isa = PBXNativeTarget; 153 | buildConfigurationList = 44B1EE9023DB90D6004E2E29 /* Build configuration list for PBXNativeTarget "SampleApp" */; 154 | buildPhases = ( 155 | 44B1EE7823DB90D5004E2E29 /* Sources */, 156 | 44B1EE7923DB90D5004E2E29 /* Frameworks */, 157 | 44B1EE7A23DB90D5004E2E29 /* Resources */, 158 | ); 159 | buildRules = ( 160 | ); 161 | dependencies = ( 162 | ); 163 | name = SampleApp; 164 | productName = SampleApp; 165 | productReference = 44B1EE7C23DB90D5004E2E29 /* SampleApp.app */; 166 | productType = "com.apple.product-type.application"; 167 | }; 168 | /* End PBXNativeTarget section */ 169 | 170 | /* Begin PBXProject section */ 171 | 44B1EE7423DB90D5004E2E29 /* Project object */ = { 172 | isa = PBXProject; 173 | attributes = { 174 | LastUpgradeCheck = 1120; 175 | ORGANIZATIONNAME = "The Iconfactory"; 176 | TargetAttributes = { 177 | 445A90692400612800B487B4 = { 178 | CreatedOnToolsVersion = 11.2.1; 179 | TestTargetID = 44B1EE7B23DB90D5004E2E29; 180 | }; 181 | 44B1EE7B23DB90D5004E2E29 = { 182 | CreatedOnToolsVersion = 11.2.1; 183 | }; 184 | }; 185 | }; 186 | buildConfigurationList = 44B1EE7723DB90D5004E2E29 /* Build configuration list for PBXProject "SampleApp" */; 187 | compatibilityVersion = "Xcode 9.3"; 188 | developmentRegion = en; 189 | hasScannedForEncodings = 0; 190 | knownRegions = ( 191 | en, 192 | Base, 193 | ); 194 | mainGroup = 44B1EE7323DB90D5004E2E29; 195 | productRefGroup = 44B1EE7D23DB90D5004E2E29 /* Products */; 196 | projectDirPath = ""; 197 | projectRoot = ""; 198 | targets = ( 199 | 44B1EE7B23DB90D5004E2E29 /* SampleApp */, 200 | 445A90692400612800B487B4 /* NSAttributedString+MarkdownTests */, 201 | ); 202 | }; 203 | /* End PBXProject section */ 204 | 205 | /* Begin PBXResourcesBuildPhase section */ 206 | 445A90682400612800B487B4 /* Resources */ = { 207 | isa = PBXResourcesBuildPhase; 208 | buildActionMask = 2147483647; 209 | files = ( 210 | ); 211 | runOnlyForDeploymentPostprocessing = 0; 212 | }; 213 | 44B1EE7A23DB90D5004E2E29 /* Resources */ = { 214 | isa = PBXResourcesBuildPhase; 215 | buildActionMask = 2147483647; 216 | files = ( 217 | 44B1EE9923DB9254004E2E29 /* Examples.md in Resources */, 218 | 44B1EE9A23DB9254004E2E29 /* Examples.rtf in Resources */, 219 | 44B1EE8623DB90D6004E2E29 /* Assets.xcassets in Resources */, 220 | 447867B42401968F00EBE733 /* Long.md in Resources */, 221 | 44B1EE8923DB90D6004E2E29 /* Main.storyboard in Resources */, 222 | ); 223 | runOnlyForDeploymentPostprocessing = 0; 224 | }; 225 | /* End PBXResourcesBuildPhase section */ 226 | 227 | /* Begin PBXSourcesBuildPhase section */ 228 | 445A90662400612800B487B4 /* Sources */ = { 229 | isa = PBXSourcesBuildPhase; 230 | buildActionMask = 2147483647; 231 | files = ( 232 | 445A906D2400612900B487B4 /* NSAttributedString_MarkdownTests.m in Sources */, 233 | ); 234 | runOnlyForDeploymentPostprocessing = 0; 235 | }; 236 | 44B1EE7823DB90D5004E2E29 /* Sources */ = { 237 | isa = PBXSourcesBuildPhase; 238 | buildActionMask = 2147483647; 239 | files = ( 240 | 44B1EE8423DB90D5004E2E29 /* ViewController.m in Sources */, 241 | 44B1EE8C23DB90D6004E2E29 /* main.m in Sources */, 242 | 44B1EE9523DB91C9004E2E29 /* NSAttributedString+Markdown.m in Sources */, 243 | 44B1EE8123DB90D5004E2E29 /* AppDelegate.m in Sources */, 244 | ); 245 | runOnlyForDeploymentPostprocessing = 0; 246 | }; 247 | /* End PBXSourcesBuildPhase section */ 248 | 249 | /* Begin PBXTargetDependency section */ 250 | 445A90702400612900B487B4 /* PBXTargetDependency */ = { 251 | isa = PBXTargetDependency; 252 | target = 44B1EE7B23DB90D5004E2E29 /* SampleApp */; 253 | targetProxy = 445A906F2400612900B487B4 /* PBXContainerItemProxy */; 254 | }; 255 | /* End PBXTargetDependency section */ 256 | 257 | /* Begin PBXVariantGroup section */ 258 | 44B1EE8723DB90D6004E2E29 /* Main.storyboard */ = { 259 | isa = PBXVariantGroup; 260 | children = ( 261 | 44B1EE8823DB90D6004E2E29 /* Base */, 262 | ); 263 | name = Main.storyboard; 264 | sourceTree = ""; 265 | }; 266 | /* End PBXVariantGroup section */ 267 | 268 | /* Begin XCBuildConfiguration section */ 269 | 445A90712400612900B487B4 /* Debug */ = { 270 | isa = XCBuildConfiguration; 271 | buildSettings = { 272 | BUNDLE_LOADER = "$(TEST_HOST)"; 273 | CODE_SIGN_STYLE = Automatic; 274 | COMBINE_HIDPI_IMAGES = YES; 275 | DEVELOPMENT_TEAM = RYQWBTQRPT; 276 | HEADER_SEARCH_PATHS = "\"$(SRCROOT)/../..\""; 277 | INFOPLIST_FILE = "NSAttributedString+MarkdownTests/Info.plist"; 278 | LD_RUNPATH_SEARCH_PATHS = ( 279 | "$(inherited)", 280 | "@executable_path/../Frameworks", 281 | "@loader_path/../Frameworks", 282 | ); 283 | PRODUCT_BUNDLE_IDENTIFIER = "com.iconfactory.NSAttributedString-MarkdownTests"; 284 | PRODUCT_NAME = "$(TARGET_NAME)"; 285 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SampleApp.app/Contents/MacOS/SampleApp"; 286 | }; 287 | name = Debug; 288 | }; 289 | 445A90722400612900B487B4 /* Release */ = { 290 | isa = XCBuildConfiguration; 291 | buildSettings = { 292 | BUNDLE_LOADER = "$(TEST_HOST)"; 293 | CODE_SIGN_STYLE = Automatic; 294 | COMBINE_HIDPI_IMAGES = YES; 295 | DEVELOPMENT_TEAM = RYQWBTQRPT; 296 | HEADER_SEARCH_PATHS = "\"$(SRCROOT)/../..\""; 297 | INFOPLIST_FILE = "NSAttributedString+MarkdownTests/Info.plist"; 298 | LD_RUNPATH_SEARCH_PATHS = ( 299 | "$(inherited)", 300 | "@executable_path/../Frameworks", 301 | "@loader_path/../Frameworks", 302 | ); 303 | PRODUCT_BUNDLE_IDENTIFIER = "com.iconfactory.NSAttributedString-MarkdownTests"; 304 | PRODUCT_NAME = "$(TARGET_NAME)"; 305 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SampleApp.app/Contents/MacOS/SampleApp"; 306 | }; 307 | name = Release; 308 | }; 309 | 44B1EE8E23DB90D6004E2E29 /* Debug */ = { 310 | isa = XCBuildConfiguration; 311 | buildSettings = { 312 | ALWAYS_SEARCH_USER_PATHS = NO; 313 | CLANG_ANALYZER_NONNULL = YES; 314 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 315 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 316 | CLANG_CXX_LIBRARY = "libc++"; 317 | CLANG_ENABLE_MODULES = YES; 318 | CLANG_ENABLE_OBJC_ARC = YES; 319 | CLANG_ENABLE_OBJC_WEAK = YES; 320 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 321 | CLANG_WARN_BOOL_CONVERSION = YES; 322 | CLANG_WARN_COMMA = YES; 323 | CLANG_WARN_CONSTANT_CONVERSION = YES; 324 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 325 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 326 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 327 | CLANG_WARN_EMPTY_BODY = YES; 328 | CLANG_WARN_ENUM_CONVERSION = YES; 329 | CLANG_WARN_INFINITE_RECURSION = YES; 330 | CLANG_WARN_INT_CONVERSION = YES; 331 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 332 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 333 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 334 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 335 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 336 | CLANG_WARN_STRICT_PROTOTYPES = YES; 337 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 338 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 339 | CLANG_WARN_UNREACHABLE_CODE = YES; 340 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 341 | COPY_PHASE_STRIP = NO; 342 | DEBUG_INFORMATION_FORMAT = dwarf; 343 | ENABLE_STRICT_OBJC_MSGSEND = YES; 344 | ENABLE_TESTABILITY = YES; 345 | GCC_C_LANGUAGE_STANDARD = gnu11; 346 | GCC_DYNAMIC_NO_PIC = NO; 347 | GCC_NO_COMMON_BLOCKS = YES; 348 | GCC_OPTIMIZATION_LEVEL = 0; 349 | GCC_PREPROCESSOR_DEFINITIONS = ( 350 | "DEBUG=1", 351 | "$(inherited)", 352 | ); 353 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 354 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 355 | GCC_WARN_UNDECLARED_SELECTOR = YES; 356 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 357 | GCC_WARN_UNUSED_FUNCTION = YES; 358 | GCC_WARN_UNUSED_VARIABLE = YES; 359 | MACOSX_DEPLOYMENT_TARGET = 10.14; 360 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 361 | MTL_FAST_MATH = YES; 362 | ONLY_ACTIVE_ARCH = YES; 363 | SDKROOT = macosx; 364 | }; 365 | name = Debug; 366 | }; 367 | 44B1EE8F23DB90D6004E2E29 /* Release */ = { 368 | isa = XCBuildConfiguration; 369 | buildSettings = { 370 | ALWAYS_SEARCH_USER_PATHS = NO; 371 | CLANG_ANALYZER_NONNULL = YES; 372 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 373 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 374 | CLANG_CXX_LIBRARY = "libc++"; 375 | CLANG_ENABLE_MODULES = YES; 376 | CLANG_ENABLE_OBJC_ARC = YES; 377 | CLANG_ENABLE_OBJC_WEAK = YES; 378 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 379 | CLANG_WARN_BOOL_CONVERSION = YES; 380 | CLANG_WARN_COMMA = YES; 381 | CLANG_WARN_CONSTANT_CONVERSION = YES; 382 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 383 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 384 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 385 | CLANG_WARN_EMPTY_BODY = YES; 386 | CLANG_WARN_ENUM_CONVERSION = YES; 387 | CLANG_WARN_INFINITE_RECURSION = YES; 388 | CLANG_WARN_INT_CONVERSION = YES; 389 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 390 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 391 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 392 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 393 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 394 | CLANG_WARN_STRICT_PROTOTYPES = YES; 395 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 396 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 397 | CLANG_WARN_UNREACHABLE_CODE = YES; 398 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 399 | COPY_PHASE_STRIP = NO; 400 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 401 | ENABLE_NS_ASSERTIONS = NO; 402 | ENABLE_STRICT_OBJC_MSGSEND = YES; 403 | GCC_C_LANGUAGE_STANDARD = gnu11; 404 | GCC_NO_COMMON_BLOCKS = YES; 405 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 406 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 407 | GCC_WARN_UNDECLARED_SELECTOR = YES; 408 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 409 | GCC_WARN_UNUSED_FUNCTION = YES; 410 | GCC_WARN_UNUSED_VARIABLE = YES; 411 | MACOSX_DEPLOYMENT_TARGET = 10.14; 412 | MTL_ENABLE_DEBUG_INFO = NO; 413 | MTL_FAST_MATH = YES; 414 | SDKROOT = macosx; 415 | }; 416 | name = Release; 417 | }; 418 | 44B1EE9123DB90D6004E2E29 /* Debug */ = { 419 | isa = XCBuildConfiguration; 420 | buildSettings = { 421 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 422 | CODE_SIGN_ENTITLEMENTS = SampleApp/SampleApp.entitlements; 423 | CODE_SIGN_STYLE = Automatic; 424 | COMBINE_HIDPI_IMAGES = YES; 425 | DEVELOPMENT_TEAM = RYQWBTQRPT; 426 | ENABLE_HARDENED_RUNTIME = YES; 427 | HEADER_SEARCH_PATHS = "\"$(SRCROOT)/../..\""; 428 | INFOPLIST_FILE = SampleApp/Info.plist; 429 | LD_RUNPATH_SEARCH_PATHS = ( 430 | "$(inherited)", 431 | "@executable_path/../Frameworks", 432 | ); 433 | PRODUCT_BUNDLE_IDENTIFIER = com.iconfactory.SampleApp; 434 | PRODUCT_NAME = "$(TARGET_NAME)"; 435 | }; 436 | name = Debug; 437 | }; 438 | 44B1EE9223DB90D6004E2E29 /* Release */ = { 439 | isa = XCBuildConfiguration; 440 | buildSettings = { 441 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 442 | CODE_SIGN_ENTITLEMENTS = SampleApp/SampleApp.entitlements; 443 | CODE_SIGN_STYLE = Automatic; 444 | COMBINE_HIDPI_IMAGES = YES; 445 | DEVELOPMENT_TEAM = RYQWBTQRPT; 446 | ENABLE_HARDENED_RUNTIME = YES; 447 | HEADER_SEARCH_PATHS = "\"$(SRCROOT)/../..\""; 448 | INFOPLIST_FILE = SampleApp/Info.plist; 449 | LD_RUNPATH_SEARCH_PATHS = ( 450 | "$(inherited)", 451 | "@executable_path/../Frameworks", 452 | ); 453 | PRODUCT_BUNDLE_IDENTIFIER = com.iconfactory.SampleApp; 454 | PRODUCT_NAME = "$(TARGET_NAME)"; 455 | }; 456 | name = Release; 457 | }; 458 | /* End XCBuildConfiguration section */ 459 | 460 | /* Begin XCConfigurationList section */ 461 | 445A90732400612900B487B4 /* Build configuration list for PBXNativeTarget "NSAttributedString+MarkdownTests" */ = { 462 | isa = XCConfigurationList; 463 | buildConfigurations = ( 464 | 445A90712400612900B487B4 /* Debug */, 465 | 445A90722400612900B487B4 /* Release */, 466 | ); 467 | defaultConfigurationIsVisible = 0; 468 | defaultConfigurationName = Release; 469 | }; 470 | 44B1EE7723DB90D5004E2E29 /* Build configuration list for PBXProject "SampleApp" */ = { 471 | isa = XCConfigurationList; 472 | buildConfigurations = ( 473 | 44B1EE8E23DB90D6004E2E29 /* Debug */, 474 | 44B1EE8F23DB90D6004E2E29 /* Release */, 475 | ); 476 | defaultConfigurationIsVisible = 0; 477 | defaultConfigurationName = Release; 478 | }; 479 | 44B1EE9023DB90D6004E2E29 /* Build configuration list for PBXNativeTarget "SampleApp" */ = { 480 | isa = XCConfigurationList; 481 | buildConfigurations = ( 482 | 44B1EE9123DB90D6004E2E29 /* Debug */, 483 | 44B1EE9223DB90D6004E2E29 /* Release */, 484 | ); 485 | defaultConfigurationIsVisible = 0; 486 | defaultConfigurationName = Release; 487 | }; 488 | /* End XCConfigurationList section */ 489 | }; 490 | rootObject = 44B1EE7423DB90D5004E2E29 /* Project object */; 491 | } 492 | -------------------------------------------------------------------------------- /AppKit/SampleApp/SampleApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /AppKit/SampleApp/SampleApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /AppKit/SampleApp/SampleApp.xcodeproj/xcshareddata/xcbaselines/445A90692400612800B487B4.xcbaseline/DAAD5161-0D3B-4ADF-81EF-ACE1DEE397B4.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | classNames 6 | 7 | NSAttributedString_MarkdownTests 8 | 9 | testConversionToMarkdownPerformance 10 | 11 | com.apple.XCTPerformanceMetric_WallClockTime 12 | 13 | baselineAverage 14 | 0.012772 15 | baselineIntegrationDisplayName 16 | Local Baseline 17 | 18 | 19 | testConversionToRichTextPerformance 20 | 21 | com.apple.XCTPerformanceMetric_WallClockTime 22 | 23 | baselineAverage 24 | 0.13062 25 | baselineIntegrationDisplayName 26 | Local Baseline 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /AppKit/SampleApp/SampleApp.xcodeproj/xcshareddata/xcbaselines/445A90692400612800B487B4.xcbaseline/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | runDestinationsByUUID 6 | 7 | DAAD5161-0D3B-4ADF-81EF-ACE1DEE397B4 8 | 9 | localComputer 10 | 11 | busSpeedInMHz 12 | 100 13 | cpuCount 14 | 1 15 | cpuKind 16 | Intel Core i7 17 | cpuSpeedInMHz 18 | 4000 19 | logicalCPUCoresPerPackage 20 | 8 21 | modelCode 22 | iMac15,1 23 | physicalCPUCoresPerPackage 24 | 4 25 | platformIdentifier 26 | com.apple.platform.macosx 27 | 28 | targetArchitecture 29 | x86_64 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /AppKit/SampleApp/SampleApp/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // SampleApp 4 | // 5 | // Created by Craig Hockenberry on 1/24/20. 6 | // Copyright © 2020 The Iconfactory. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : NSObject 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /AppKit/SampleApp/SampleApp/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // SampleApp 4 | // 5 | // Created by Craig Hockenberry on 1/24/20. 6 | // Copyright © 2020 The Iconfactory. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { 18 | // Insert code here to initialize your application 19 | } 20 | 21 | 22 | - (void)applicationWillTerminate:(NSNotification *)aNotification { 23 | // Insert code here to tear down your application 24 | } 25 | 26 | - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender 27 | { 28 | return YES; 29 | } 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /AppKit/SampleApp/SampleApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /AppKit/SampleApp/SampleApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /AppKit/SampleApp/SampleApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | Copyright © 2020 The Iconfactory. All rights reserved. 27 | NSMainStoryboardFile 28 | Main 29 | NSPrincipalClass 30 | NSApplication 31 | NSSupportsAutomaticTermination 32 | 33 | NSSupportsSuddenTermination 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /AppKit/SampleApp/SampleApp/SampleApp.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /AppKit/SampleApp/SampleApp/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // SampleApp 4 | // 5 | // Created by Craig Hockenberry on 1/24/20. 6 | // Copyright © 2020 The Iconfactory. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : NSViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /AppKit/SampleApp/SampleApp/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // MarkdownAttributedString 4 | // 5 | // Created by Craig Hockenberry on 12/28/19. 6 | // Copyright © 2019 The Iconfactory. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | 11 | static NSString *const savedStringKey = @"savedString"; 12 | 13 | #define TESTING 1 // to get -markdownDebug 14 | #import "NSAttributedString+Markdown.h" 15 | 16 | #define USE_STYLE_ATTRIBUTES 0 // Enable this to use extended style attributes for the Markdown to attributed string conversions 17 | 18 | @interface ViewController () 19 | 20 | @property (nonatomic, weak) IBOutlet NSTextField *richTextTextField; 21 | @property (nonatomic, weak) IBOutlet NSButton *richTextButton; 22 | @property (nonatomic, weak) IBOutlet NSTextView *richTextTextView; 23 | 24 | @property (nonatomic, weak) IBOutlet NSTextField *markdownTextField; 25 | @property (nonatomic, weak) IBOutlet NSButton *markdownButton; 26 | @property (nonatomic, weak) IBOutlet NSTextView *markdownTextView; 27 | 28 | @property (readonly) NSFont *richTextFont; 29 | @property (readonly) NSFont *markdownFont; 30 | @property (readonly) NSDictionary *baseAttributes; 31 | @property (readonly) NSDictionary *> *styleAttributes; 32 | 33 | @end 34 | 35 | @implementation ViewController 36 | 37 | - (void)viewDidLoad 38 | { 39 | [super viewDidLoad]; 40 | 41 | 42 | NSString *localizedRichTextLabel = NSLocalizedString(@"_NSTextView_ with **Rich Text**", @"Rich Text Label"); 43 | NSString *localizedRichTextButton = NSLocalizedString(@"Show **Rich Text** Example", @"Rich Text Button"); 44 | NSString *localizedMarkdownLabel = NSLocalizedString(@"_NSTextView_ with **Markdown**", @"Markdown Label"); 45 | NSString *localizedMarkdownButton = NSLocalizedString(@"Show **Markdown** Examples", @"Markdown Button"); 46 | 47 | NSDictionary *attributes = @{ NSFontAttributeName: [NSFont systemFontOfSize:13.0] }; 48 | 49 | self.richTextTextField.attributedStringValue = [[NSAttributedString alloc] initWithMarkdownRepresentation:localizedRichTextLabel attributes:attributes]; 50 | self.richTextButton.attributedTitle = [[NSAttributedString alloc] initWithMarkdownRepresentation:localizedRichTextButton attributes:attributes]; 51 | self.markdownTextField.attributedStringValue = [[NSAttributedString alloc] initWithMarkdownRepresentation:localizedMarkdownLabel attributes:attributes]; 52 | self.markdownButton.attributedTitle = [[NSAttributedString alloc] initWithMarkdownRepresentation:localizedMarkdownButton attributes:attributes]; 53 | } 54 | 55 | - (void)viewWillAppear 56 | { 57 | [super viewWillAppear]; 58 | 59 | self.richTextTextView.font = self.richTextFont; 60 | self.richTextTextView.typingAttributes = self.baseAttributes; 61 | self.markdownTextView.font = self.markdownFont; 62 | self.markdownTextView.typingAttributes = @{ NSFontAttributeName: self.markdownFont }; 63 | } 64 | 65 | - (void)viewDidAppear 66 | { 67 | [super viewDidAppear]; 68 | 69 | if ([NSUserDefaults.standardUserDefaults objectForKey:savedStringKey]) { 70 | [self performSelector:@selector(setMarkdownSavedString) withObject:nil afterDelay:0.0]; 71 | } 72 | else { 73 | [self performSelector:@selector(setRichTextExamples:) withObject:nil afterDelay:0.0]; 74 | } 75 | } 76 | 77 | #pragma mark - Accessors 78 | 79 | - (NSFont *)richTextFont 80 | { 81 | return [NSFont userFontOfSize:13.0]; 82 | } 83 | 84 | - (NSFont *)markdownFont 85 | { 86 | return [NSFont userFixedPitchFontOfSize:13.0]; 87 | } 88 | 89 | - (NSDictionary *)baseAttributes 90 | { 91 | #if !USE_STYLE_ATTRIBUTES 92 | return @{ NSFontAttributeName: self.richTextFont }; 93 | #else 94 | return @{ NSFontAttributeName: [NSFont fontWithName:@"AvenirNext-Regular" size:14.0] }; 95 | #endif 96 | } 97 | 98 | - (NSDictionary *> *)styleAttributes 99 | { 100 | return @{ 101 | MarkdownStyleEmphasisSingle: @{ 102 | NSFontAttributeName: [NSFont fontWithName:@"Verdana-Italic" size:14.0], 103 | NSForegroundColorAttributeName: NSColor.systemRedColor 104 | }, 105 | MarkdownStyleEmphasisDouble: @{ 106 | NSFontAttributeName: [NSFont fontWithName:@"LucidaGrande-Bold" size:14.0], 107 | NSForegroundColorAttributeName: NSColor.systemGreenColor 108 | }, 109 | MarkdownStyleEmphasisBoth: @{ 110 | NSFontAttributeName: [NSFont fontWithName:@"Palatino-BoldItalic" size:16.0], 111 | NSForegroundColorAttributeName: NSColor.systemBlueColor 112 | }, 113 | }; 114 | } 115 | 116 | #pragma mark - Actions 117 | 118 | - (IBAction)setRichTextExamples:(id)sender 119 | { 120 | NSLog(@"%s called", __PRETTY_FUNCTION__); 121 | 122 | NSURL *exampleURL = [NSBundle.mainBundle URLForResource:@"Examples" withExtension:@"rtf"]; 123 | NSData *exampleRTF = [NSData dataWithContentsOfURL:exampleURL]; 124 | NSAttributedString *attributedString = [[NSAttributedString alloc] initWithRTF:exampleRTF documentAttributes:nil]; 125 | 126 | [self.richTextTextView.textStorage setAttributedString:attributedString]; 127 | self.markdownTextView.string = [attributedString markdownRepresentation]; 128 | 129 | [self.view.window makeFirstResponder:self.richTextTextView]; 130 | self.richTextTextView.selectedRange = NSMakeRange(0, self.richTextTextView.string.length); 131 | } 132 | 133 | - (IBAction)setMarkdownExamples:(id)sender 134 | { 135 | NSLog(@"%s called", __PRETTY_FUNCTION__); 136 | 137 | NSURL *exampleURL = [NSBundle.mainBundle URLForResource:@"Examples" withExtension:@"md"]; 138 | NSData *exampleMarkdown = [NSData dataWithContentsOfURL:exampleURL]; 139 | NSString *markdownString = [[NSString alloc] initWithData:exampleMarkdown encoding:NSUTF8StringEncoding]; 140 | 141 | self.markdownTextView.string = markdownString; 142 | #if !USE_STYLE_ATTRIBUTES 143 | NSAttributedString *attributedString = [[NSAttributedString alloc] initWithMarkdownRepresentation:markdownString attributes:self.baseAttributes]; 144 | #else 145 | NSAttributedString *attributedString = [[NSAttributedString alloc] initWithMarkdownRepresentation:markdownString baseAttributes:self.baseAttributes styleAttributes:self.styleAttributes]; 146 | #endif 147 | [self.richTextTextView.textStorage setAttributedString:attributedString]; 148 | 149 | [self.view.window makeFirstResponder:self.markdownTextView]; 150 | self.markdownTextView.selectedRange = NSMakeRange(0, self.markdownTextView.string.length); 151 | } 152 | 153 | - (void)setMarkdownSavedString 154 | { 155 | NSLog(@"%s called", __PRETTY_FUNCTION__); 156 | 157 | NSString *markdownString = [NSUserDefaults.standardUserDefaults stringForKey:savedStringKey]; 158 | 159 | self.markdownTextView.string = markdownString; 160 | #if !USE_STYLE_ATTRIBUTES 161 | NSAttributedString *attributedString = [[NSAttributedString alloc] initWithMarkdownRepresentation:markdownString attributes:self.baseAttributes]; 162 | #else 163 | NSAttributedString *attributedString = [[NSAttributedString alloc] initWithMarkdownRepresentation:markdownString baseAttributes:self.baseAttributes styleAttributes:self.styleAttributes]; 164 | #endif 165 | [self.richTextTextView.textStorage setAttributedString:attributedString]; 166 | 167 | [self.view.window makeFirstResponder:self.markdownTextView]; 168 | self.markdownTextView.selectedRange = NSMakeRange(0, self.markdownTextView.string.length); 169 | } 170 | 171 | #pragma mark - NSTextViewDelegate 172 | 173 | - (void)textDidChange:(NSNotification *)notification 174 | { 175 | //NSLog(@"%s notification = %@", __PRETTY_FUNCTION__, notification); 176 | 177 | if (notification.object == self.richTextTextView) { 178 | NSString *markdownString = [self.richTextTextView.attributedString markdownRepresentation]; 179 | self.markdownTextView.string = markdownString; 180 | } 181 | else if (notification.object == self.markdownTextView) { 182 | #if !USE_STYLE_ATTRIBUTES 183 | NSAttributedString *richTextString = [[NSAttributedString alloc] initWithMarkdownRepresentation:self.markdownTextView.string attributes:self.baseAttributes]; 184 | #else 185 | NSAttributedString *richTextString = [[NSAttributedString alloc] initWithMarkdownRepresentation:self.markdownTextView.string baseAttributes:self.baseAttributes styleAttributes:self.styleAttributes]; 186 | #endif 187 | 188 | // NOTE: The logging below is helpful for generating tests in teh NSAttributedString+MarkdownTest target. Use the SampleApp to reproduce a 189 | // bad conversion, then copy the testString and compareString to a new test. 190 | 191 | [self.richTextTextView.textStorage setAttributedString:richTextString]; 192 | NSLog(@"%s Markdown string = %@", __PRETTY_FUNCTION__, self.markdownTextView.string); 193 | 194 | NSMutableString *sourceString = [self.markdownTextView.string mutableCopy]; 195 | [sourceString replaceOccurrencesOfString:@"\\" withString:@"\\\\" options:(0) range:NSMakeRange(0, sourceString.length)]; 196 | [sourceString replaceOccurrencesOfString:@"\n" withString:@"\\n" options:(0) range:NSMakeRange(0, sourceString.length)]; 197 | [sourceString replaceOccurrencesOfString:@"\t" withString:@"\\t" options:(0) range:NSMakeRange(0, sourceString.length)]; 198 | NSLog(@"NSString *testString = @\"%@\";", sourceString); 199 | 200 | NSMutableString *compareString = [[richTextString markdownDebug] mutableCopy]; 201 | [compareString replaceOccurrencesOfString:@"\\" withString:@"\\\\" options:(0) range:NSMakeRange(0, compareString.length)]; 202 | NSLog(@"NSString *compareString = @\"%@\";", compareString); 203 | 204 | [NSUserDefaults.standardUserDefaults setObject:self.markdownTextView.string forKey:savedStringKey]; 205 | [NSUserDefaults.standardUserDefaults synchronize]; 206 | } 207 | } 208 | 209 | @end 210 | -------------------------------------------------------------------------------- /AppKit/SampleApp/SampleApp/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // SampleApp 4 | // 5 | // Created by Craig Hockenberry on 1/24/20. 6 | // Copyright © 2020 The Iconfactory. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, const char * argv[]) { 12 | @autoreleasepool { 13 | // Setup code that might create autoreleased objects goes here. 14 | } 15 | return NSApplicationMain(argc, argv); 16 | } 17 | -------------------------------------------------------------------------------- /Examples/Examples.md: -------------------------------------------------------------------------------- 1 | Strings can contain literals like \*\*, \_\_, \*, and \_. 2 | 3 | Bare symbols like * and _ are supported. 4 | 5 | A **span with symbols and Emoji like ⌘ or ⚠️ work correctly**. It's un*frigging*believable. 6 | 7 | Use _literals\_in\_styled\_text_ or in \_un-styled\_text\_. 8 | 9 | Styles **of _text_ can be embedded**, _like **this**, too_. 10 | 11 | Both [inline links](https://iconfactory.com) and automatic links like are supported. 12 | 13 | Contrarians can even do __bold__ and *italics*. 14 | 15 | Feel free to edit this text and watch for changes in the other view. 16 | -------------------------------------------------------------------------------- /Examples/Examples.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\cocoartf1671\cocoasubrtf600 2 | {\fonttbl\f0\fswiss\fcharset0 Helvetica;\f1\fswiss\fcharset0 Helvetica-Bold;\f2\fswiss\fcharset0 Helvetica-Oblique; 3 | \f3\fswiss\fcharset0 Helvetica-BoldOblique;} 4 | {\colortbl;\red255\green255\blue255;} 5 | {\*\expandedcolortbl;;} 6 | \margl1440\margr1440\vieww14400\viewh10200\viewkind0 7 | \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0 8 | 9 | \f0\fs26 \cf0 Whatever 10 | \f1\b Rich Text 11 | \f0\b0 you type here gets 12 | \f2\i converted 13 | \f0\i0 to 14 | \f1\b Markdown 15 | \f0\b0 in the other view. If you modify the 16 | \f1\b Markdown 17 | \f0\b0 text, this view will 18 | \f2\i update 19 | \f0\i0 accordingly.\ 20 | \ 21 | Currently, span attributes for 22 | \f1\b bold 23 | \f3\i and 24 | \f2\b0 italic 25 | \f0\i0 text and {\field{\*\fldinst{HYPERLINK "https://iconfactory.com"}}{\fldrslt link}} attributes can be represented as Markdown. While limited in scope, this provides a great deal of functionality for developers.\ 26 | \ 27 | For example, the labels and buttons in this example use 28 | \f2\i localized text 29 | \f0\i0 that includes 30 | \f1\b Markdown 31 | \f0\b0 . This allows localization strings 32 | \f2\i and 33 | \f0\i0 text styles to coexist naturally, making the process simpler for everyone involved.\ 34 | \ 35 | Go ahead and edit this text, including updating the styles using the 36 | \f2\i Font 37 | \f0\i0 menu, then watch for changes in the other view...} -------------------------------------------------------------------------------- /NSAttributedString+Markdown.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributedString+Markdown.h 3 | // Tot 4 | // 5 | // Created by Craig Hockenberry on 12/14/19. 6 | // Copyright © 2020 The Iconfactory. All rights reserved. 7 | // 8 | /* 9 | Copyright (c) 2020 The Iconfactory, Inc. 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in 19 | all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 27 | THE SOFTWARE. 28 | */ 29 | 30 | #import 31 | 32 | #if TARGET_OS_OSX 33 | #import 34 | #else 35 | #import 36 | #endif 37 | 38 | #define ALLOW_CODE_MARKERS 0 // EXPERIMENTAL - Currently literals aren't escaped and style attributes are baked in (not using styleAttributes). 39 | 40 | NS_ASSUME_NONNULL_BEGIN 41 | 42 | @interface NSCharacterSet (Markdown) 43 | 44 | @property (readonly, class, copy) NSCharacterSet *markdownLiteralCharacterSet; 45 | 46 | @end 47 | 48 | extern NSString *const UTTypeMarkdown; 49 | // NOTE: The definition above can be used to determine if text on the clipboard contains Markdown: 50 | // 51 | // if ([UIPasteboard.generalPasteboard containsPasteboardTypes:@[ UTTypeMarkdown, (NSString *)kUTTypeText ]]) { ... } 52 | 53 | 54 | typedef NSString * MarkdownStyleKey NS_EXTENSIBLE_STRING_ENUM; 55 | 56 | extern MarkdownStyleKey MarkdownStyleEmphasisSingle; // attribute dictionary for occurances of _ or * (emphasis, typically an italic font) 57 | extern MarkdownStyleKey MarkdownStyleEmphasisDouble; // attribute dictionary for occurances of __ or ** (strong, typically a bold font) 58 | extern MarkdownStyleKey MarkdownStyleEmphasisBoth; // attribute dictionary for occurances of _ or * within __ or ** (emphasis and strong, typically a bold italic font) 59 | 60 | extern MarkdownStyleKey MarkdownStyleLink; // optional attribute dictionary to use instead of NSLinkAttributeName, link will be styled with attributes instead of clickable 61 | 62 | #if ALLOW_CODE_MARKERS 63 | extern MarkdownStyleKey MarkdownStyleCode; // EXPERIMENTAL - attribute dictionary for occuranges of ` 64 | #endif 65 | 66 | @interface NSAttributedString (Markdown) 67 | 68 | - (instancetype)initWithMarkdownRepresentation:(NSString *)markdownRepresentation attributes:(nonnull NSDictionary *)attributes; 69 | 70 | - (instancetype)initWithMarkdownRepresentation:(NSString *)markdownRepresentation baseAttributes:(nonnull NSDictionary *)baseAttributes styleAttributes:(nullable NSDictionary *> *)styleAttributes; 71 | 72 | @property (nonatomic, readonly) NSString *markdownRepresentation; 73 | 74 | #ifdef TESTING 75 | // for tests, to quickly check the placement of attributes 76 | @property (nonatomic, readonly) NSString *markdownDebug; 77 | #endif 78 | 79 | @end 80 | 81 | NS_ASSUME_NONNULL_END 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MarkdownAttributedString 2 | 3 | Adding Markdown support to NSAttributedString. 4 | 5 | ## Why? 6 | 7 | Attributed strings, along with CoreText and UI frameworks for iOS and macOS, are a powerful component used in every app. 8 | 9 | Yet making those attributed strings is a pain in the butt. Your choices are: 10 | 11 | * Write a bunch of code to do it manually. 12 | * Use an external text editor to create a resource which is hard to manage (especially with localization). 13 | 14 | Additionally, with the advent of system fonts that can't be used in TextEdit to make the RTF file, there's no avoiding the need for code when you're dealing with text that needs to match the surrounding user interface. 15 | 16 | ## What? 17 | 18 | This project is an Objective-C category that generates rich text by reading Markdown as the source code. It also allows you to write Markdown using attributed strings. The code only processes link and emphasis span elements in Markdown. There is experimental support for code spans. 19 | 20 | There is no support for Markdown block elements such as headers and lists, although care taken to leave those elements alone. In theory it would be possible to achieve something reasonable using paragraph styles, but I'm not convinced there's a need for these elements and the added complexity they would bring. 21 | 22 | The code is written in Objective-C because it is extending code written in the same language. It was a more natural fit. 23 | 24 | Swift is fully supported. You'll find sample apps in both languages for macOS ([AppKit/Objective-C](AppKit/SampleApp)) and iOS ([UIKit/Objective-C](UIKit/SampleApp) and [UIKit/Swift](UIKit/SwiftSampleApp))). 25 | 26 | The macOS sample app also includes a full suite of tests that check the Markdown parser (for both reading and writing). If you want to add additional Markdown support in this code, familiarize yourself with these tests first. This sample app also makes a good test bed and its [view controller's](AppKit/SampleApp/SampleApp/ViewController.m) `-textDidChange` method logs strings that can be used to create tests. 27 | 28 | ## How? 29 | 30 | [Markdown](https://daringfireball.net/projects/markdown/syntax) is a "visual specification" and I was warned by its author that doing this kind of rich text conversion would be "hairy". It was. 31 | 32 | But the end result is worth it. It's incredibly handy to have Markdown as a tool while you're developing apps that use Apple's frameworks. I have used the code for buttons, labels, and text views; the sample apps included in this repository show you how that's done. Here's a quick taste of how you do it in Swift: 33 | 34 | ```swift 35 | let markdownString = "This is a **_simple_ example** that _shows_ **Markdown** usage." 36 | 37 | myLabel.attributedText = NSAttributedString(markdownRepresentation: markdownString, attributes: [.font : UIFont.systemFont(ofSize: 17.0), .foregroundColor: UIColor.systemPurple ]) 38 | ``` 39 | 40 | If you've used HTML to do this in the past, you'll know that it pulls in WebKit, is not particularly fast, and has [thread-safety issues](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/AttributedStrings/Tasks/CreatingAttributedStrings.html). This new approach using Markdown has no external dependencies and can be used off the main thread. The only restriction is that the conversion code can only run on [a single thread at one time](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html) because it mutates foundation objects. The code is fast and typically used for operations that are not performed repeatedly, so it's unlikely that you'll have any issues. 41 | 42 | Both producing and consuming Markdown involves a lot of text scanning. When going from Markdown to an attributed string, each type of marker (e.g. \*\* or \_ ) is checked and styling is added incrementally to the string. Going the other direction, each attribute range is checked and markers are emitted accordingly. Of course, once you get into the details of the implementation, you'll realize that it gets more complicated than you'd first expect. The only regular expressions used are to detect URLs and email addresses. So yeah, "hairy". 43 | 44 | One of the potential uses I see for this code is with localization. Putting Markdown into your .strings files will be a lot easier than juggling separate RTF files. Be careful about styles that are only available in Latin languages. A good example is the Japanese phrase これはテストです which can be rendered faithfully in a bold font variant (**これはテストです**) but not italic. _これはテストです_ can be synthesized or substituted from another font, but quality will suffer. In these cases, you're better off using the method with `styleAttributes` to add an underline or some other visual emphasis that doesn't rely on the glyph's structure. 45 | 46 | ## Where? 47 | 48 | [Tot](http://tot.rocks). 49 | 50 | This app was the first place I used this code and when you start using it, you'll understand why. Working in both rich text and Markdown is a seamless experience. As folks have been using this app over the past few months, many weird edge cases have emerged: the parsing and generation of Markdown is robust as a result. 51 | 52 | ## Who? 53 | 54 | This code was written by Craig Hockenberry. If you'd like to show your appreciation, there are several ways to do that: 55 | 56 | * [Get Tot!](http://tot.rocks) - if you love Markdown and text in general, you'll love the app that inspired this code. It's free on macOS and a one-time purchase on iOS. 57 | * [Support our Patreon](https://patreon.com/iconfactory) - you'll be supporting a good cause and get tons of cool stuff in return. 58 | * [Buy our apps](https://iconfactoryapps.com) - the Iconfactory has been making software for over twenty years and we're sure to have something that will appeal to you. 59 | 60 | As with any software, there is plenty of room for improvement. Feel free to send pull requests and file issues. It's likely that I will ignore any issues that don't have a failing test case. 61 | 62 | ## License 63 | 64 | This code uses a MIT License: 65 | 66 | Copyright (c) 2020 The Iconfactory, Inc. 67 | 68 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 69 | 70 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 71 | 72 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 73 | -------------------------------------------------------------------------------- /UIKit/SampleApp/SampleApp.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 44B1EE9E23DB9559004E2E29 /* Examples.md in Resources */ = {isa = PBXBuildFile; fileRef = 44B1EE9C23DB9559004E2E29 /* Examples.md */; }; 11 | 44B1EE9F23DB9559004E2E29 /* Examples.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 44B1EE9D23DB9559004E2E29 /* Examples.rtf */; }; 12 | 44B1EEA223DBA0F4004E2E29 /* StringData.m in Sources */ = {isa = PBXBuildFile; fileRef = 44B1EEA123DBA0F4004E2E29 /* StringData.m */; }; 13 | 44E8FA8E23D90632009E1D13 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 44E8FA8D23D90632009E1D13 /* AppDelegate.m */; }; 14 | 44E8FA9123D90632009E1D13 /* SceneDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 44E8FA9023D90632009E1D13 /* SceneDelegate.m */; }; 15 | 44E8FA9423D90632009E1D13 /* FirstViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 44E8FA9323D90632009E1D13 /* FirstViewController.m */; }; 16 | 44E8FA9723D90632009E1D13 /* SecondViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 44E8FA9623D90632009E1D13 /* SecondViewController.m */; }; 17 | 44E8FA9A23D90632009E1D13 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 44E8FA9823D90632009E1D13 /* Main.storyboard */; }; 18 | 44E8FA9C23D90634009E1D13 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 44E8FA9B23D90634009E1D13 /* Assets.xcassets */; }; 19 | 44E8FA9F23D90634009E1D13 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 44E8FA9D23D90634009E1D13 /* LaunchScreen.storyboard */; }; 20 | 44E8FAA223D90634009E1D13 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 44E8FAA123D90634009E1D13 /* main.m */; }; 21 | 44E8FAAA23D906D9009E1D13 /* NSAttributedString+Markdown.m in Sources */ = {isa = PBXBuildFile; fileRef = 44E8FAA823D906D9009E1D13 /* NSAttributedString+Markdown.m */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXFileReference section */ 25 | 44B1EE9C23DB9559004E2E29 /* Examples.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Examples.md; sourceTree = ""; }; 26 | 44B1EE9D23DB9559004E2E29 /* Examples.rtf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.rtf; path = Examples.rtf; sourceTree = ""; }; 27 | 44B1EEA023DBA0F4004E2E29 /* StringData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StringData.h; sourceTree = ""; }; 28 | 44B1EEA123DBA0F4004E2E29 /* StringData.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StringData.m; sourceTree = ""; }; 29 | 44E8FA8923D90632009E1D13 /* SampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 30 | 44E8FA8C23D90632009E1D13 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 31 | 44E8FA8D23D90632009E1D13 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 32 | 44E8FA8F23D90632009E1D13 /* SceneDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SceneDelegate.h; sourceTree = ""; }; 33 | 44E8FA9023D90632009E1D13 /* SceneDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SceneDelegate.m; sourceTree = ""; }; 34 | 44E8FA9223D90632009E1D13 /* FirstViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FirstViewController.h; sourceTree = ""; }; 35 | 44E8FA9323D90632009E1D13 /* FirstViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FirstViewController.m; sourceTree = ""; }; 36 | 44E8FA9523D90632009E1D13 /* SecondViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SecondViewController.h; sourceTree = ""; }; 37 | 44E8FA9623D90632009E1D13 /* SecondViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SecondViewController.m; sourceTree = ""; }; 38 | 44E8FA9923D90632009E1D13 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 39 | 44E8FA9B23D90634009E1D13 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 40 | 44E8FA9E23D90634009E1D13 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 41 | 44E8FAA023D90634009E1D13 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 42 | 44E8FAA123D90634009E1D13 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 43 | 44E8FAA823D906D9009E1D13 /* NSAttributedString+Markdown.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSAttributedString+Markdown.m"; path = "../../NSAttributedString+Markdown.m"; sourceTree = ""; }; 44 | 44E8FAA923D906D9009E1D13 /* NSAttributedString+Markdown.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSAttributedString+Markdown.h"; path = "../../NSAttributedString+Markdown.h"; sourceTree = ""; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | 44E8FA8623D90632009E1D13 /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | /* End PBXFrameworksBuildPhase section */ 56 | 57 | /* Begin PBXGroup section */ 58 | 44B1EE9B23DB9559004E2E29 /* Examples */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 44B1EE9C23DB9559004E2E29 /* Examples.md */, 62 | 44B1EE9D23DB9559004E2E29 /* Examples.rtf */, 63 | ); 64 | name = Examples; 65 | path = ../../../Examples; 66 | sourceTree = ""; 67 | }; 68 | 44E8FA8023D90632009E1D13 = { 69 | isa = PBXGroup; 70 | children = ( 71 | 44E8FAA923D906D9009E1D13 /* NSAttributedString+Markdown.h */, 72 | 44E8FAA823D906D9009E1D13 /* NSAttributedString+Markdown.m */, 73 | 44E8FA8B23D90632009E1D13 /* SampleApp */, 74 | 44E8FA8A23D90632009E1D13 /* Products */, 75 | ); 76 | sourceTree = ""; 77 | }; 78 | 44E8FA8A23D90632009E1D13 /* Products */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | 44E8FA8923D90632009E1D13 /* SampleApp.app */, 82 | ); 83 | name = Products; 84 | sourceTree = ""; 85 | }; 86 | 44E8FA8B23D90632009E1D13 /* SampleApp */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 44E8FA8C23D90632009E1D13 /* AppDelegate.h */, 90 | 44E8FA8D23D90632009E1D13 /* AppDelegate.m */, 91 | 44E8FA8F23D90632009E1D13 /* SceneDelegate.h */, 92 | 44E8FA9023D90632009E1D13 /* SceneDelegate.m */, 93 | 44E8FA9223D90632009E1D13 /* FirstViewController.h */, 94 | 44E8FA9323D90632009E1D13 /* FirstViewController.m */, 95 | 44E8FA9523D90632009E1D13 /* SecondViewController.h */, 96 | 44E8FA9623D90632009E1D13 /* SecondViewController.m */, 97 | 44B1EEA023DBA0F4004E2E29 /* StringData.h */, 98 | 44B1EEA123DBA0F4004E2E29 /* StringData.m */, 99 | 44E8FA9823D90632009E1D13 /* Main.storyboard */, 100 | 44E8FA9B23D90634009E1D13 /* Assets.xcassets */, 101 | 44E8FA9D23D90634009E1D13 /* LaunchScreen.storyboard */, 102 | 44B1EE9B23DB9559004E2E29 /* Examples */, 103 | 44E8FAA023D90634009E1D13 /* Info.plist */, 104 | 44E8FAA123D90634009E1D13 /* main.m */, 105 | ); 106 | path = SampleApp; 107 | sourceTree = ""; 108 | }; 109 | /* End PBXGroup section */ 110 | 111 | /* Begin PBXNativeTarget section */ 112 | 44E8FA8823D90632009E1D13 /* SampleApp */ = { 113 | isa = PBXNativeTarget; 114 | buildConfigurationList = 44E8FAA523D90634009E1D13 /* Build configuration list for PBXNativeTarget "SampleApp" */; 115 | buildPhases = ( 116 | 44E8FA8523D90632009E1D13 /* Sources */, 117 | 44E8FA8623D90632009E1D13 /* Frameworks */, 118 | 44E8FA8723D90632009E1D13 /* Resources */, 119 | ); 120 | buildRules = ( 121 | ); 122 | dependencies = ( 123 | ); 124 | name = SampleApp; 125 | productName = SampleApp; 126 | productReference = 44E8FA8923D90632009E1D13 /* SampleApp.app */; 127 | productType = "com.apple.product-type.application"; 128 | }; 129 | /* End PBXNativeTarget section */ 130 | 131 | /* Begin PBXProject section */ 132 | 44E8FA8123D90632009E1D13 /* Project object */ = { 133 | isa = PBXProject; 134 | attributes = { 135 | LastUpgradeCheck = 1120; 136 | ORGANIZATIONNAME = "The Iconfactory"; 137 | TargetAttributes = { 138 | 44E8FA8823D90632009E1D13 = { 139 | CreatedOnToolsVersion = 11.2.1; 140 | }; 141 | }; 142 | }; 143 | buildConfigurationList = 44E8FA8423D90632009E1D13 /* Build configuration list for PBXProject "SampleApp" */; 144 | compatibilityVersion = "Xcode 9.3"; 145 | developmentRegion = en; 146 | hasScannedForEncodings = 0; 147 | knownRegions = ( 148 | en, 149 | Base, 150 | ); 151 | mainGroup = 44E8FA8023D90632009E1D13; 152 | productRefGroup = 44E8FA8A23D90632009E1D13 /* Products */; 153 | projectDirPath = ""; 154 | projectRoot = ""; 155 | targets = ( 156 | 44E8FA8823D90632009E1D13 /* SampleApp */, 157 | ); 158 | }; 159 | /* End PBXProject section */ 160 | 161 | /* Begin PBXResourcesBuildPhase section */ 162 | 44E8FA8723D90632009E1D13 /* Resources */ = { 163 | isa = PBXResourcesBuildPhase; 164 | buildActionMask = 2147483647; 165 | files = ( 166 | 44E8FA9F23D90634009E1D13 /* LaunchScreen.storyboard in Resources */, 167 | 44B1EE9E23DB9559004E2E29 /* Examples.md in Resources */, 168 | 44E8FA9C23D90634009E1D13 /* Assets.xcassets in Resources */, 169 | 44E8FA9A23D90632009E1D13 /* Main.storyboard in Resources */, 170 | 44B1EE9F23DB9559004E2E29 /* Examples.rtf in Resources */, 171 | ); 172 | runOnlyForDeploymentPostprocessing = 0; 173 | }; 174 | /* End PBXResourcesBuildPhase section */ 175 | 176 | /* Begin PBXSourcesBuildPhase section */ 177 | 44E8FA8523D90632009E1D13 /* Sources */ = { 178 | isa = PBXSourcesBuildPhase; 179 | buildActionMask = 2147483647; 180 | files = ( 181 | 44E8FAA223D90634009E1D13 /* main.m in Sources */, 182 | 44E8FA9723D90632009E1D13 /* SecondViewController.m in Sources */, 183 | 44E8FAAA23D906D9009E1D13 /* NSAttributedString+Markdown.m in Sources */, 184 | 44E8FA8E23D90632009E1D13 /* AppDelegate.m in Sources */, 185 | 44E8FA9423D90632009E1D13 /* FirstViewController.m in Sources */, 186 | 44B1EEA223DBA0F4004E2E29 /* StringData.m in Sources */, 187 | 44E8FA9123D90632009E1D13 /* SceneDelegate.m in Sources */, 188 | ); 189 | runOnlyForDeploymentPostprocessing = 0; 190 | }; 191 | /* End PBXSourcesBuildPhase section */ 192 | 193 | /* Begin PBXVariantGroup section */ 194 | 44E8FA9823D90632009E1D13 /* Main.storyboard */ = { 195 | isa = PBXVariantGroup; 196 | children = ( 197 | 44E8FA9923D90632009E1D13 /* Base */, 198 | ); 199 | name = Main.storyboard; 200 | sourceTree = ""; 201 | }; 202 | 44E8FA9D23D90634009E1D13 /* LaunchScreen.storyboard */ = { 203 | isa = PBXVariantGroup; 204 | children = ( 205 | 44E8FA9E23D90634009E1D13 /* Base */, 206 | ); 207 | name = LaunchScreen.storyboard; 208 | sourceTree = ""; 209 | }; 210 | /* End PBXVariantGroup section */ 211 | 212 | /* Begin XCBuildConfiguration section */ 213 | 44E8FAA323D90634009E1D13 /* Debug */ = { 214 | isa = XCBuildConfiguration; 215 | buildSettings = { 216 | ALWAYS_SEARCH_USER_PATHS = NO; 217 | CLANG_ANALYZER_NONNULL = YES; 218 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 219 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 220 | CLANG_CXX_LIBRARY = "libc++"; 221 | CLANG_ENABLE_MODULES = YES; 222 | CLANG_ENABLE_OBJC_ARC = YES; 223 | CLANG_ENABLE_OBJC_WEAK = YES; 224 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 225 | CLANG_WARN_BOOL_CONVERSION = YES; 226 | CLANG_WARN_COMMA = YES; 227 | CLANG_WARN_CONSTANT_CONVERSION = YES; 228 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 229 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 230 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 231 | CLANG_WARN_EMPTY_BODY = YES; 232 | CLANG_WARN_ENUM_CONVERSION = YES; 233 | CLANG_WARN_INFINITE_RECURSION = YES; 234 | CLANG_WARN_INT_CONVERSION = YES; 235 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 236 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 237 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 238 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 239 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 240 | CLANG_WARN_STRICT_PROTOTYPES = YES; 241 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 242 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 243 | CLANG_WARN_UNREACHABLE_CODE = YES; 244 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 245 | COPY_PHASE_STRIP = NO; 246 | DEBUG_INFORMATION_FORMAT = dwarf; 247 | ENABLE_STRICT_OBJC_MSGSEND = YES; 248 | ENABLE_TESTABILITY = YES; 249 | GCC_C_LANGUAGE_STANDARD = gnu11; 250 | GCC_DYNAMIC_NO_PIC = NO; 251 | GCC_NO_COMMON_BLOCKS = YES; 252 | GCC_OPTIMIZATION_LEVEL = 0; 253 | GCC_PREPROCESSOR_DEFINITIONS = ( 254 | "DEBUG=1", 255 | "$(inherited)", 256 | ); 257 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 258 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 259 | GCC_WARN_UNDECLARED_SELECTOR = YES; 260 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 261 | GCC_WARN_UNUSED_FUNCTION = YES; 262 | GCC_WARN_UNUSED_VARIABLE = YES; 263 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 264 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 265 | MTL_FAST_MATH = YES; 266 | ONLY_ACTIVE_ARCH = YES; 267 | SDKROOT = iphoneos; 268 | }; 269 | name = Debug; 270 | }; 271 | 44E8FAA423D90634009E1D13 /* Release */ = { 272 | isa = XCBuildConfiguration; 273 | buildSettings = { 274 | ALWAYS_SEARCH_USER_PATHS = NO; 275 | CLANG_ANALYZER_NONNULL = YES; 276 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 277 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 278 | CLANG_CXX_LIBRARY = "libc++"; 279 | CLANG_ENABLE_MODULES = YES; 280 | CLANG_ENABLE_OBJC_ARC = YES; 281 | CLANG_ENABLE_OBJC_WEAK = YES; 282 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 283 | CLANG_WARN_BOOL_CONVERSION = YES; 284 | CLANG_WARN_COMMA = YES; 285 | CLANG_WARN_CONSTANT_CONVERSION = YES; 286 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 287 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 288 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 289 | CLANG_WARN_EMPTY_BODY = YES; 290 | CLANG_WARN_ENUM_CONVERSION = YES; 291 | CLANG_WARN_INFINITE_RECURSION = YES; 292 | CLANG_WARN_INT_CONVERSION = YES; 293 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 294 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 295 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 296 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 297 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 298 | CLANG_WARN_STRICT_PROTOTYPES = YES; 299 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 300 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 301 | CLANG_WARN_UNREACHABLE_CODE = YES; 302 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 303 | COPY_PHASE_STRIP = NO; 304 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 305 | ENABLE_NS_ASSERTIONS = NO; 306 | ENABLE_STRICT_OBJC_MSGSEND = YES; 307 | GCC_C_LANGUAGE_STANDARD = gnu11; 308 | GCC_NO_COMMON_BLOCKS = YES; 309 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 310 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 311 | GCC_WARN_UNDECLARED_SELECTOR = YES; 312 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 313 | GCC_WARN_UNUSED_FUNCTION = YES; 314 | GCC_WARN_UNUSED_VARIABLE = YES; 315 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 316 | MTL_ENABLE_DEBUG_INFO = NO; 317 | MTL_FAST_MATH = YES; 318 | SDKROOT = iphoneos; 319 | VALIDATE_PRODUCT = YES; 320 | }; 321 | name = Release; 322 | }; 323 | 44E8FAA623D90634009E1D13 /* Debug */ = { 324 | isa = XCBuildConfiguration; 325 | buildSettings = { 326 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 327 | CODE_SIGN_STYLE = Automatic; 328 | DEVELOPMENT_TEAM = RYQWBTQRPT; 329 | HEADER_SEARCH_PATHS = "\"$(SRCROOT)/../..\""; 330 | INFOPLIST_FILE = SampleApp/Info.plist; 331 | LD_RUNPATH_SEARCH_PATHS = ( 332 | "$(inherited)", 333 | "@executable_path/Frameworks", 334 | ); 335 | PRODUCT_BUNDLE_IDENTIFIER = com.iconfactory.SampleAppMobile; 336 | PRODUCT_NAME = "$(TARGET_NAME)"; 337 | TARGETED_DEVICE_FAMILY = "1,2"; 338 | }; 339 | name = Debug; 340 | }; 341 | 44E8FAA723D90634009E1D13 /* Release */ = { 342 | isa = XCBuildConfiguration; 343 | buildSettings = { 344 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 345 | CODE_SIGN_STYLE = Automatic; 346 | DEVELOPMENT_TEAM = RYQWBTQRPT; 347 | HEADER_SEARCH_PATHS = "\"$(SRCROOT)/../..\""; 348 | INFOPLIST_FILE = SampleApp/Info.plist; 349 | LD_RUNPATH_SEARCH_PATHS = ( 350 | "$(inherited)", 351 | "@executable_path/Frameworks", 352 | ); 353 | PRODUCT_BUNDLE_IDENTIFIER = com.iconfactory.SampleAppMobile; 354 | PRODUCT_NAME = "$(TARGET_NAME)"; 355 | TARGETED_DEVICE_FAMILY = "1,2"; 356 | }; 357 | name = Release; 358 | }; 359 | /* End XCBuildConfiguration section */ 360 | 361 | /* Begin XCConfigurationList section */ 362 | 44E8FA8423D90632009E1D13 /* Build configuration list for PBXProject "SampleApp" */ = { 363 | isa = XCConfigurationList; 364 | buildConfigurations = ( 365 | 44E8FAA323D90634009E1D13 /* Debug */, 366 | 44E8FAA423D90634009E1D13 /* Release */, 367 | ); 368 | defaultConfigurationIsVisible = 0; 369 | defaultConfigurationName = Release; 370 | }; 371 | 44E8FAA523D90634009E1D13 /* Build configuration list for PBXNativeTarget "SampleApp" */ = { 372 | isa = XCConfigurationList; 373 | buildConfigurations = ( 374 | 44E8FAA623D90634009E1D13 /* Debug */, 375 | 44E8FAA723D90634009E1D13 /* Release */, 376 | ); 377 | defaultConfigurationIsVisible = 0; 378 | defaultConfigurationName = Release; 379 | }; 380 | /* End XCConfigurationList section */ 381 | }; 382 | rootObject = 44E8FA8123D90632009E1D13 /* Project object */; 383 | } 384 | -------------------------------------------------------------------------------- /UIKit/SampleApp/SampleApp/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // SampleApp 4 | // 5 | // Created by Craig Hockenberry on 1/22/20. 6 | // Copyright © 2020 The Iconfactory. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /UIKit/SampleApp/SampleApp/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // SampleApp 4 | // 5 | // Created by Craig Hockenberry on 1/22/20. 6 | // Copyright © 2020 The Iconfactory. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | #import "StringData.h" 12 | 13 | @interface AppDelegate () 14 | 15 | @end 16 | 17 | @implementation AppDelegate 18 | 19 | 20 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 21 | // Override point for customization after application launch. 22 | 23 | NSURL *exampleURL = [NSBundle.mainBundle URLForResource:@"Examples" withExtension:@"rtf"]; 24 | NSData *exampleRTF = [NSData dataWithContentsOfURL:exampleURL]; 25 | NSError *error = nil; 26 | NSMutableAttributedString *attributedString = [[[NSAttributedString alloc] initWithData:exampleRTF options:@{ } documentAttributes:nil error:&error] mutableCopy]; 27 | [attributedString addAttribute:NSForegroundColorAttributeName value:UIColor.labelColor range:NSMakeRange(0, attributedString.length)]; 28 | StringData.sharedStringData.attributedString = [attributedString copy]; 29 | 30 | return YES; 31 | } 32 | 33 | 34 | #pragma mark - UISceneSession lifecycle 35 | 36 | 37 | - (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options { 38 | // Called when a new scene session is being created. 39 | // Use this method to select a configuration to create the new scene with. 40 | return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role]; 41 | } 42 | 43 | 44 | - (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet *)sceneSessions { 45 | // Called when the user discards a scene session. 46 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 47 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 48 | } 49 | 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /UIKit/SampleApp/SampleApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /UIKit/SampleApp/SampleApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /UIKit/SampleApp/SampleApp/Assets.xcassets/first.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "first.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /UIKit/SampleApp/SampleApp/Assets.xcassets/first.imageset/first.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chockenberry/MarkdownAttributedString/750e8d5cb455dcc592a9b6d1cacaa19837e7abff/UIKit/SampleApp/SampleApp/Assets.xcassets/first.imageset/first.pdf -------------------------------------------------------------------------------- /UIKit/SampleApp/SampleApp/Assets.xcassets/second.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "second.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /UIKit/SampleApp/SampleApp/Assets.xcassets/second.imageset/second.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chockenberry/MarkdownAttributedString/750e8d5cb455dcc592a9b6d1cacaa19837e7abff/UIKit/SampleApp/SampleApp/Assets.xcassets/second.imageset/second.pdf -------------------------------------------------------------------------------- /UIKit/SampleApp/SampleApp/Base.lproj/LaunchScreen.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 | -------------------------------------------------------------------------------- /UIKit/SampleApp/SampleApp/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 24 | 32 | 33 | 34 | 35 | 36 | 37 | Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 52 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 104 | 113 | 114 | 115 | 116 | 117 | 118 | Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 133 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | -------------------------------------------------------------------------------- /UIKit/SampleApp/SampleApp/FirstViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // FirstViewController.h 3 | // SampleApp 4 | // 5 | // Created by Craig Hockenberry on 1/22/20. 6 | // Copyright © 2020 The Iconfactory. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface FirstViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /UIKit/SampleApp/SampleApp/FirstViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // FirstViewController.m 3 | // SampleApp 4 | // 5 | // Created by Craig Hockenberry on 1/22/20. 6 | // Copyright © 2020 The Iconfactory. All rights reserved. 7 | // 8 | 9 | #import "FirstViewController.h" 10 | 11 | #import "NSAttributedString+Markdown.h" 12 | 13 | #import "StringData.h" 14 | 15 | @interface FirstViewController () 16 | 17 | @property (nonatomic, weak) IBOutlet UILabel *label; 18 | @property (nonatomic, weak) IBOutlet UIButton *button; 19 | @property (nonatomic, weak) IBOutlet UITextView *textView; 20 | @property (nonatomic, weak) IBOutlet UILabel *helpLabel; 21 | 22 | @end 23 | 24 | @implementation FirstViewController 25 | 26 | - (void)dealloc 27 | { 28 | [NSNotificationCenter.defaultCenter removeObserver:self]; 29 | } 30 | 31 | - (void)viewDidLoad 32 | { 33 | [super viewDidLoad]; 34 | 35 | self.textView.delegate = self; 36 | 37 | NSString *localizedLabel = NSLocalizedString(@"_UITextView_ with **Rich Text**", @"Rich Text Label"); 38 | NSString *localizedButton = NSLocalizedString(@"Show **Rich Text** Example", @"Rich Text Button"); 39 | NSString *localizedHelp = NSLocalizedString(@"Any changes made in _this view_ will be reflected in the **Markdown** view", @"Rich Text Help"); 40 | 41 | NSDictionary *attributes = @{ NSFontAttributeName: [UIFont systemFontOfSize:20.0] }; 42 | 43 | self.label.attributedText = [[NSAttributedString alloc] initWithMarkdownRepresentation:localizedLabel attributes:attributes]; 44 | [self.button setAttributedTitle:[[NSAttributedString alloc] initWithMarkdownRepresentation:localizedButton attributes:attributes] forState:UIControlStateNormal]; 45 | 46 | self.helpLabel.attributedText = [[NSAttributedString alloc] initWithMarkdownRepresentation:localizedHelp attributes:@{ NSFontAttributeName: [UIFont systemFontOfSize:16.0], NSForegroundColorAttributeName: UIColor.secondaryLabelColor }]; 47 | 48 | self.textView.attributedText = StringData.sharedStringData.attributedString; 49 | 50 | [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(stringDataDidChange:) name:StringDataDidChangeNotification object:nil]; 51 | [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(keyboardDidShow:) name:UIKeyboardDidShowNotification object:nil]; 52 | [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; 53 | } 54 | 55 | - (void)viewWillDisappear:(BOOL)animated 56 | { 57 | NSLog(@"%s animated = %d", __PRETTY_FUNCTION__, animated); 58 | [super viewWillDisappear:animated]; 59 | 60 | StringData.sharedStringData.attributedString = self.textView.attributedText; 61 | } 62 | 63 | #pragma mark - Actions 64 | 65 | - (IBAction)setExamples:(id)sender 66 | { 67 | NSLog(@"%s called", __PRETTY_FUNCTION__); 68 | 69 | NSURL *exampleURL = [NSBundle.mainBundle URLForResource:@"Examples" withExtension:@"rtf"]; 70 | NSData *exampleRTF = [NSData dataWithContentsOfURL:exampleURL]; 71 | NSError *error = nil; 72 | NSMutableAttributedString *attributedString = [[[NSAttributedString alloc] initWithData:exampleRTF options:@{ } documentAttributes:nil error:&error] mutableCopy]; 73 | [attributedString addAttribute:NSForegroundColorAttributeName value:UIColor.labelColor range:NSMakeRange(0, attributedString.length)]; 74 | StringData.sharedStringData.attributedString = [attributedString copy]; 75 | } 76 | 77 | - (IBAction)dismissKeyboard:(id)sender 78 | { 79 | [self.textView resignFirstResponder]; 80 | } 81 | 82 | #pragma mark - Notifications 83 | 84 | - (void)stringDataDidChange:(NSNotification *)notification 85 | { 86 | NSLog(@"%s called", __PRETTY_FUNCTION__); 87 | self.textView.attributedText = StringData.sharedStringData.attributedString; 88 | } 89 | 90 | - (void)keyboardDidShow:(NSNotification *)notification 91 | { 92 | CGSize keyboardSize = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size; 93 | CGFloat bottomInset = keyboardSize.height - self.view.safeAreaInsets.bottom; 94 | self.additionalSafeAreaInsets = UIEdgeInsetsMake(0, 0, bottomInset, 0); 95 | } 96 | 97 | - (void)keyboardWillHide:(NSNotification *)notification 98 | { 99 | self.additionalSafeAreaInsets = UIEdgeInsetsMake(0, 0, 0, 0); 100 | } 101 | 102 | @end 103 | -------------------------------------------------------------------------------- /UIKit/SampleApp/SampleApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | SceneDelegate 36 | UISceneStoryboardFile 37 | Main 38 | 39 | 40 | 41 | 42 | UILaunchStoryboardName 43 | LaunchScreen 44 | UIMainStoryboardFile 45 | Main 46 | UIRequiredDeviceCapabilities 47 | 48 | armv7 49 | 50 | UIStatusBarTintParameters 51 | 52 | UINavigationBar 53 | 54 | Style 55 | UIBarStyleDefault 56 | Translucent 57 | 58 | 59 | 60 | UISupportedInterfaceOrientations 61 | 62 | UIInterfaceOrientationPortrait 63 | UIInterfaceOrientationLandscapeLeft 64 | UIInterfaceOrientationLandscapeRight 65 | 66 | UISupportedInterfaceOrientations~ipad 67 | 68 | UIInterfaceOrientationPortrait 69 | UIInterfaceOrientationPortraitUpsideDown 70 | UIInterfaceOrientationLandscapeLeft 71 | UIInterfaceOrientationLandscapeRight 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /UIKit/SampleApp/SampleApp/SceneDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.h 3 | // SampleApp 4 | // 5 | // Created by Craig Hockenberry on 1/22/20. 6 | // Copyright © 2020 The Iconfactory. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SceneDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow * window; 14 | 15 | @end 16 | 17 | -------------------------------------------------------------------------------- /UIKit/SampleApp/SampleApp/SceneDelegate.m: -------------------------------------------------------------------------------- 1 | #import "SceneDelegate.h" 2 | 3 | @interface SceneDelegate () 4 | 5 | @end 6 | 7 | @implementation SceneDelegate 8 | 9 | 10 | - (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions { 11 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 12 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 13 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 14 | } 15 | 16 | 17 | - (void)sceneDidDisconnect:(UIScene *)scene { 18 | // Called as the scene is being released by the system. 19 | // This occurs shortly after the scene enters the background, or when its session is discarded. 20 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 21 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 22 | } 23 | 24 | 25 | - (void)sceneDidBecomeActive:(UIScene *)scene { 26 | // Called when the scene has moved from an inactive state to an active state. 27 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 28 | } 29 | 30 | 31 | - (void)sceneWillResignActive:(UIScene *)scene { 32 | // Called when the scene will move from an active state to an inactive state. 33 | // This may occur due to temporary interruptions (ex. an incoming phone call). 34 | } 35 | 36 | 37 | - (void)sceneWillEnterForeground:(UIScene *)scene { 38 | // Called as the scene transitions from the background to the foreground. 39 | // Use this method to undo the changes made on entering the background. 40 | } 41 | 42 | 43 | - (void)sceneDidEnterBackground:(UIScene *)scene { 44 | // Called as the scene transitions from the foreground to the background. 45 | // Use this method to save data, release shared resources, and store enough scene-specific state information 46 | // to restore the scene back to its current state. 47 | } 48 | 49 | 50 | @end 51 | -------------------------------------------------------------------------------- /UIKit/SampleApp/SampleApp/SecondViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SecondViewController.h 3 | // SampleApp 4 | // 5 | // Created by Craig Hockenberry on 1/22/20. 6 | // Copyright © 2020 The Iconfactory. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SecondViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /UIKit/SampleApp/SampleApp/SecondViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SecondViewController.m 3 | // SampleApp 4 | // 5 | // Created by Craig Hockenberry on 1/22/20. 6 | // Copyright © 2020 The Iconfactory. All rights reserved. 7 | // 8 | 9 | #import "SecondViewController.h" 10 | 11 | #import "NSAttributedString+Markdown.h" 12 | 13 | #import "StringData.h" 14 | 15 | #define USE_STYLE_ATTRIBUTES 0 // Enable this to use extended style attributes for the Markdown to attributed string conversions 16 | 17 | @interface SecondViewController () 18 | 19 | @property (nonatomic, weak) IBOutlet UILabel *label; 20 | @property (nonatomic, weak) IBOutlet UIButton *button; 21 | @property (nonatomic, weak) IBOutlet UITextView *textView; 22 | @property (nonatomic, weak) IBOutlet UILabel *helpLabel; 23 | 24 | @property (readonly) NSDictionary *baseAttributes; 25 | @property (readonly) NSDictionary *> *styleAttributes; 26 | 27 | @end 28 | 29 | @implementation SecondViewController 30 | 31 | - (void)dealloc 32 | { 33 | [NSNotificationCenter.defaultCenter removeObserver:self]; 34 | } 35 | 36 | - (void)viewDidLoad 37 | { 38 | [super viewDidLoad]; 39 | 40 | self.textView.delegate = self; 41 | 42 | NSString *localizedLabel = NSLocalizedString(@"_UITextView_ with **Markdown**", @"Markdown Label"); 43 | NSString *localizedButton = NSLocalizedString(@"Show **Markdown** Examples", @"Markdown Button"); 44 | NSString *localizedHelp = NSLocalizedString(@"Any changes made in _this view_ will be reflected in the **Rich Text** view", @"Markdown Help"); 45 | 46 | NSDictionary *attributes = @{ NSFontAttributeName: [UIFont systemFontOfSize:20.0] }; 47 | 48 | self.label.attributedText = [[NSAttributedString alloc] initWithMarkdownRepresentation:localizedLabel attributes:attributes]; 49 | [self.button setAttributedTitle:[[NSAttributedString alloc] initWithMarkdownRepresentation:localizedButton attributes:attributes] forState:UIControlStateNormal]; 50 | 51 | self.helpLabel.attributedText = [[NSAttributedString alloc] initWithMarkdownRepresentation:localizedHelp attributes:@{ NSFontAttributeName: [UIFont systemFontOfSize:16.0], NSForegroundColorAttributeName: UIColor.secondaryLabelColor }]; 52 | 53 | NSAttributedString *attributedString = StringData.sharedStringData.attributedString; 54 | self.textView.attributedText = [self attributedStringConvertedToMarkdown:attributedString]; 55 | 56 | [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(stringDataDidChange:) name:StringDataDidChangeNotification object:nil]; 57 | [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(keyboardDidShow:) name:UIKeyboardDidShowNotification object:nil]; 58 | [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil]; 59 | } 60 | 61 | - (void)viewWillDisappear:(BOOL)animated 62 | { 63 | NSLog(@"%s animated = %d", __PRETTY_FUNCTION__, animated); 64 | [super viewWillDisappear:animated]; 65 | 66 | NSString *string = self.textView.text; 67 | #if !USE_STYLE_ATTRIBUTES 68 | NSAttributedString *attributedString = [[NSAttributedString alloc] initWithMarkdownRepresentation:string attributes:self.baseAttributes]; 69 | #else 70 | NSAttributedString *attributedString = [[NSAttributedString alloc] initWithMarkdownRepresentation:string baseAttributes:self.baseAttributes styleAttributes:self.styleAttributes]; 71 | #endif 72 | StringData.sharedStringData.attributedString = attributedString; 73 | } 74 | 75 | #pragma mark - Accessors 76 | 77 | - (NSDictionary *)baseAttributes 78 | { 79 | #if !USE_STYLE_ATTRIBUTES 80 | return @{ NSFontAttributeName:[UIFont systemFontOfSize:20.0], NSForegroundColorAttributeName: UIColor.labelColor }; 81 | #else 82 | return @{ NSFontAttributeName: [UIFont fontWithName:@"AvenirNext-UltraLight" size:20.0], NSForegroundColorAttributeName: UIColor.labelColor }; 83 | #endif 84 | } 85 | 86 | - (NSDictionary *> *)styleAttributes 87 | { 88 | return @{ 89 | MarkdownStyleEmphasisSingle: @{ 90 | NSFontAttributeName: [UIFont fontWithName:@"AvenirNext-UltraLightItalic" size:20.0], 91 | NSForegroundColorAttributeName: UIColor.systemRedColor 92 | }, 93 | MarkdownStyleEmphasisDouble: @{ 94 | NSFontAttributeName: [UIFont fontWithName:@"AvenirNext-DemiBold" size:20.0], 95 | NSForegroundColorAttributeName: UIColor.systemGreenColor 96 | }, 97 | MarkdownStyleEmphasisBoth: @{ 98 | NSFontAttributeName: [UIFont fontWithName:@"AvenirNext-HeavyItalic" size:20.0], 99 | NSForegroundColorAttributeName: UIColor.systemBlueColor 100 | }, 101 | }; 102 | } 103 | 104 | #pragma mark - Actions 105 | 106 | - (IBAction)setExamples:(id)sender 107 | { 108 | NSLog(@"%s called", __PRETTY_FUNCTION__); 109 | 110 | NSURL *exampleURL = [NSBundle.mainBundle URLForResource:@"Examples" withExtension:@"md"]; 111 | NSData *exampleMarkdown = [NSData dataWithContentsOfURL:exampleURL]; 112 | NSString *string = [[NSString alloc] initWithData:exampleMarkdown encoding:NSUTF8StringEncoding]; 113 | 114 | #if !USE_STYLE_ATTRIBUTES 115 | NSAttributedString *attributedString = [[NSAttributedString alloc] initWithMarkdownRepresentation:string attributes:self.baseAttributes]; 116 | #else 117 | NSAttributedString *attributedString = [[NSAttributedString alloc] initWithMarkdownRepresentation:string baseAttributes:self.baseAttributes styleAttributes:self.styleAttributes]; 118 | #endif 119 | StringData.sharedStringData.attributedString = attributedString; 120 | } 121 | 122 | - (IBAction)dismissKeyboard:(id)sender 123 | { 124 | [self.textView resignFirstResponder]; 125 | } 126 | 127 | #pragma mark - Utility 128 | 129 | - (NSAttributedString *)attributedStringConvertedToMarkdown:(NSAttributedString *)attributedString 130 | { 131 | UIFont *systemFont = [UIFont systemFontOfSize:18.0]; 132 | UIFontDescriptor *fontDescriptor = [systemFont.fontDescriptor fontDescriptorWithDesign:UIFontDescriptorSystemDesignMonospaced]; 133 | UIFont *systemMonospacedFont = [UIFont fontWithDescriptor:fontDescriptor size:systemFont.pointSize]; 134 | 135 | NSString *string = [attributedString markdownRepresentation]; 136 | NSDictionary *attributes = @{ NSFontAttributeName: systemMonospacedFont, NSForegroundColorAttributeName: UIColor.labelColor }; 137 | return [[NSAttributedString alloc] initWithString:string attributes:attributes]; 138 | } 139 | 140 | #pragma mark - Notifications 141 | 142 | - (void)stringDataDidChange:(NSNotification *)notification 143 | { 144 | NSLog(@"%s called", __PRETTY_FUNCTION__); 145 | 146 | NSAttributedString *attributedString = StringData.sharedStringData.attributedString; 147 | self.textView.attributedText = [self attributedStringConvertedToMarkdown:attributedString]; 148 | } 149 | 150 | - (void)keyboardDidShow:(NSNotification *)notification 151 | { 152 | CGSize keyboardSize = [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size; 153 | CGFloat bottomInset = keyboardSize.height - self.view.safeAreaInsets.bottom; 154 | self.additionalSafeAreaInsets = UIEdgeInsetsMake(0, 0, bottomInset, 0); 155 | } 156 | 157 | - (void)keyboardWillHide:(NSNotification *)notification 158 | { 159 | self.additionalSafeAreaInsets = UIEdgeInsetsMake(0, 0, 0, 0); 160 | } 161 | 162 | @end 163 | -------------------------------------------------------------------------------- /UIKit/SampleApp/SampleApp/StringData.h: -------------------------------------------------------------------------------- 1 | // 2 | // StringData.h 3 | // SampleApp 4 | // 5 | // Created by Craig Hockenberry on 1/24/20. 6 | // Copyright © 2020 The Iconfactory. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | extern NSString *const StringDataDidChangeNotification; 14 | 15 | @interface StringData : NSObject 16 | 17 | @property (class, readonly) StringData *sharedStringData; 18 | 19 | @property (nonatomic, strong) NSAttributedString *attributedString; 20 | 21 | @end 22 | 23 | NS_ASSUME_NONNULL_END 24 | -------------------------------------------------------------------------------- /UIKit/SampleApp/SampleApp/StringData.m: -------------------------------------------------------------------------------- 1 | // 2 | // StringData.m 3 | // SampleApp 4 | // 5 | // Created by Craig Hockenberry on 1/24/20. 6 | // Copyright © 2020 The Iconfactory. All rights reserved. 7 | // 8 | 9 | #import "StringData.h" 10 | 11 | NSString *const StringDataDidChangeNotification = @"StringDataDidChangeNotification"; 12 | 13 | @interface StringData () 14 | 15 | @end 16 | 17 | @implementation StringData 18 | 19 | + (StringData *)sharedStringData 20 | { 21 | static StringData *sharedStringDataInstance = nil; 22 | static dispatch_once_t once; 23 | dispatch_once(&once, ^{ 24 | sharedStringDataInstance = [[self alloc] init]; 25 | }); 26 | return sharedStringDataInstance; 27 | } 28 | 29 | - (void)setAttributedString:(NSAttributedString *)attributedString 30 | { 31 | if (! [attributedString isEqual:_attributedString]) { 32 | _attributedString = attributedString; 33 | [NSNotificationCenter.defaultCenter postNotificationName:StringDataDidChangeNotification object:self]; 34 | } 35 | } 36 | @end 37 | -------------------------------------------------------------------------------- /UIKit/SampleApp/SampleApp/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // SampleApp 4 | // 5 | // Created by Craig Hockenberry on 1/22/20. 6 | // Copyright © 2020 The Iconfactory. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | NSString * appDelegateClassName; 14 | @autoreleasepool { 15 | // Setup code that might create autoreleased objects goes here. 16 | appDelegateClassName = NSStringFromClass([AppDelegate class]); 17 | } 18 | return UIApplicationMain(argc, argv, nil, appDelegateClassName); 19 | } 20 | -------------------------------------------------------------------------------- /UIKit/SwiftSampleApp/SwiftSampleApp-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "NSAttributedString+Markdown.h" 6 | 7 | -------------------------------------------------------------------------------- /UIKit/SwiftSampleApp/SwiftSampleApp.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 44962FDE23E7A54A00E2A598 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44962FDD23E7A54A00E2A598 /* AppDelegate.swift */; }; 11 | 44962FE023E7A54A00E2A598 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44962FDF23E7A54A00E2A598 /* SceneDelegate.swift */; }; 12 | 44962FE223E7A54A00E2A598 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44962FE123E7A54A00E2A598 /* ViewController.swift */; }; 13 | 44962FE523E7A54A00E2A598 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 44962FE323E7A54A00E2A598 /* Main.storyboard */; }; 14 | 44962FE723E7A54B00E2A598 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 44962FE623E7A54B00E2A598 /* Assets.xcassets */; }; 15 | 44962FEA23E7A54B00E2A598 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 44962FE823E7A54B00E2A598 /* LaunchScreen.storyboard */; }; 16 | 44962FF423E7A56B00E2A598 /* NSAttributedString+Markdown.m in Sources */ = {isa = PBXBuildFile; fileRef = 44962FF323E7A56B00E2A598 /* NSAttributedString+Markdown.m */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXFileReference section */ 20 | 44962FDA23E7A54A00E2A598 /* SwiftSampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftSampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 21 | 44962FDD23E7A54A00E2A598 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 22 | 44962FDF23E7A54A00E2A598 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 23 | 44962FE123E7A54A00E2A598 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 24 | 44962FE423E7A54A00E2A598 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 25 | 44962FE623E7A54B00E2A598 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 26 | 44962FE923E7A54B00E2A598 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 27 | 44962FEB23E7A54B00E2A598 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 28 | 44962FF123E7A56A00E2A598 /* SwiftSampleApp-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SwiftSampleApp-Bridging-Header.h"; sourceTree = ""; }; 29 | 44962FF223E7A56B00E2A598 /* NSAttributedString+Markdown.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSAttributedString+Markdown.h"; path = "../../NSAttributedString+Markdown.h"; sourceTree = ""; }; 30 | 44962FF323E7A56B00E2A598 /* NSAttributedString+Markdown.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSAttributedString+Markdown.m"; path = "../../NSAttributedString+Markdown.m"; sourceTree = ""; }; 31 | /* End PBXFileReference section */ 32 | 33 | /* Begin PBXFrameworksBuildPhase section */ 34 | 44962FD723E7A54A00E2A598 /* Frameworks */ = { 35 | isa = PBXFrameworksBuildPhase; 36 | buildActionMask = 2147483647; 37 | files = ( 38 | ); 39 | runOnlyForDeploymentPostprocessing = 0; 40 | }; 41 | /* End PBXFrameworksBuildPhase section */ 42 | 43 | /* Begin PBXGroup section */ 44 | 44962FD123E7A54A00E2A598 = { 45 | isa = PBXGroup; 46 | children = ( 47 | 44962FF223E7A56B00E2A598 /* NSAttributedString+Markdown.h */, 48 | 44962FF323E7A56B00E2A598 /* NSAttributedString+Markdown.m */, 49 | 44962FF123E7A56A00E2A598 /* SwiftSampleApp-Bridging-Header.h */, 50 | 44962FDC23E7A54A00E2A598 /* SwiftSampleApp */, 51 | 44962FDB23E7A54A00E2A598 /* Products */, 52 | ); 53 | sourceTree = ""; 54 | }; 55 | 44962FDB23E7A54A00E2A598 /* Products */ = { 56 | isa = PBXGroup; 57 | children = ( 58 | 44962FDA23E7A54A00E2A598 /* SwiftSampleApp.app */, 59 | ); 60 | name = Products; 61 | sourceTree = ""; 62 | }; 63 | 44962FDC23E7A54A00E2A598 /* SwiftSampleApp */ = { 64 | isa = PBXGroup; 65 | children = ( 66 | 44962FDD23E7A54A00E2A598 /* AppDelegate.swift */, 67 | 44962FDF23E7A54A00E2A598 /* SceneDelegate.swift */, 68 | 44962FE123E7A54A00E2A598 /* ViewController.swift */, 69 | 44962FE323E7A54A00E2A598 /* Main.storyboard */, 70 | 44962FE623E7A54B00E2A598 /* Assets.xcassets */, 71 | 44962FE823E7A54B00E2A598 /* LaunchScreen.storyboard */, 72 | 44962FEB23E7A54B00E2A598 /* Info.plist */, 73 | ); 74 | path = SwiftSampleApp; 75 | sourceTree = ""; 76 | }; 77 | /* End PBXGroup section */ 78 | 79 | /* Begin PBXNativeTarget section */ 80 | 44962FD923E7A54A00E2A598 /* SwiftSampleApp */ = { 81 | isa = PBXNativeTarget; 82 | buildConfigurationList = 44962FEE23E7A54B00E2A598 /* Build configuration list for PBXNativeTarget "SwiftSampleApp" */; 83 | buildPhases = ( 84 | 44962FD623E7A54A00E2A598 /* Sources */, 85 | 44962FD723E7A54A00E2A598 /* Frameworks */, 86 | 44962FD823E7A54A00E2A598 /* Resources */, 87 | ); 88 | buildRules = ( 89 | ); 90 | dependencies = ( 91 | ); 92 | name = SwiftSampleApp; 93 | productName = SwiftSampleApp; 94 | productReference = 44962FDA23E7A54A00E2A598 /* SwiftSampleApp.app */; 95 | productType = "com.apple.product-type.application"; 96 | }; 97 | /* End PBXNativeTarget section */ 98 | 99 | /* Begin PBXProject section */ 100 | 44962FD223E7A54A00E2A598 /* Project object */ = { 101 | isa = PBXProject; 102 | attributes = { 103 | LastSwiftUpdateCheck = 1120; 104 | LastUpgradeCheck = 1120; 105 | ORGANIZATIONNAME = "The Iconfactory"; 106 | TargetAttributes = { 107 | 44962FD923E7A54A00E2A598 = { 108 | CreatedOnToolsVersion = 11.2.1; 109 | LastSwiftMigration = 1120; 110 | }; 111 | }; 112 | }; 113 | buildConfigurationList = 44962FD523E7A54A00E2A598 /* Build configuration list for PBXProject "SwiftSampleApp" */; 114 | compatibilityVersion = "Xcode 9.3"; 115 | developmentRegion = en; 116 | hasScannedForEncodings = 0; 117 | knownRegions = ( 118 | en, 119 | Base, 120 | ); 121 | mainGroup = 44962FD123E7A54A00E2A598; 122 | productRefGroup = 44962FDB23E7A54A00E2A598 /* Products */; 123 | projectDirPath = ""; 124 | projectRoot = ""; 125 | targets = ( 126 | 44962FD923E7A54A00E2A598 /* SwiftSampleApp */, 127 | ); 128 | }; 129 | /* End PBXProject section */ 130 | 131 | /* Begin PBXResourcesBuildPhase section */ 132 | 44962FD823E7A54A00E2A598 /* Resources */ = { 133 | isa = PBXResourcesBuildPhase; 134 | buildActionMask = 2147483647; 135 | files = ( 136 | 44962FEA23E7A54B00E2A598 /* LaunchScreen.storyboard in Resources */, 137 | 44962FE723E7A54B00E2A598 /* Assets.xcassets in Resources */, 138 | 44962FE523E7A54A00E2A598 /* Main.storyboard in Resources */, 139 | ); 140 | runOnlyForDeploymentPostprocessing = 0; 141 | }; 142 | /* End PBXResourcesBuildPhase section */ 143 | 144 | /* Begin PBXSourcesBuildPhase section */ 145 | 44962FD623E7A54A00E2A598 /* Sources */ = { 146 | isa = PBXSourcesBuildPhase; 147 | buildActionMask = 2147483647; 148 | files = ( 149 | 44962FE223E7A54A00E2A598 /* ViewController.swift in Sources */, 150 | 44962FDE23E7A54A00E2A598 /* AppDelegate.swift in Sources */, 151 | 44962FF423E7A56B00E2A598 /* NSAttributedString+Markdown.m in Sources */, 152 | 44962FE023E7A54A00E2A598 /* SceneDelegate.swift in Sources */, 153 | ); 154 | runOnlyForDeploymentPostprocessing = 0; 155 | }; 156 | /* End PBXSourcesBuildPhase section */ 157 | 158 | /* Begin PBXVariantGroup section */ 159 | 44962FE323E7A54A00E2A598 /* Main.storyboard */ = { 160 | isa = PBXVariantGroup; 161 | children = ( 162 | 44962FE423E7A54A00E2A598 /* Base */, 163 | ); 164 | name = Main.storyboard; 165 | sourceTree = ""; 166 | }; 167 | 44962FE823E7A54B00E2A598 /* LaunchScreen.storyboard */ = { 168 | isa = PBXVariantGroup; 169 | children = ( 170 | 44962FE923E7A54B00E2A598 /* Base */, 171 | ); 172 | name = LaunchScreen.storyboard; 173 | sourceTree = ""; 174 | }; 175 | /* End PBXVariantGroup section */ 176 | 177 | /* Begin XCBuildConfiguration section */ 178 | 44962FEC23E7A54B00E2A598 /* Debug */ = { 179 | isa = XCBuildConfiguration; 180 | buildSettings = { 181 | ALWAYS_SEARCH_USER_PATHS = NO; 182 | CLANG_ANALYZER_NONNULL = YES; 183 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 184 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 185 | CLANG_CXX_LIBRARY = "libc++"; 186 | CLANG_ENABLE_MODULES = YES; 187 | CLANG_ENABLE_OBJC_ARC = YES; 188 | CLANG_ENABLE_OBJC_WEAK = YES; 189 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 190 | CLANG_WARN_BOOL_CONVERSION = YES; 191 | CLANG_WARN_COMMA = YES; 192 | CLANG_WARN_CONSTANT_CONVERSION = YES; 193 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 194 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 195 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 196 | CLANG_WARN_EMPTY_BODY = YES; 197 | CLANG_WARN_ENUM_CONVERSION = YES; 198 | CLANG_WARN_INFINITE_RECURSION = YES; 199 | CLANG_WARN_INT_CONVERSION = YES; 200 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 201 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 202 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 203 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 204 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 205 | CLANG_WARN_STRICT_PROTOTYPES = YES; 206 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 207 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 208 | CLANG_WARN_UNREACHABLE_CODE = YES; 209 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 210 | COPY_PHASE_STRIP = NO; 211 | DEBUG_INFORMATION_FORMAT = dwarf; 212 | ENABLE_STRICT_OBJC_MSGSEND = YES; 213 | ENABLE_TESTABILITY = YES; 214 | GCC_C_LANGUAGE_STANDARD = gnu11; 215 | GCC_DYNAMIC_NO_PIC = NO; 216 | GCC_NO_COMMON_BLOCKS = YES; 217 | GCC_OPTIMIZATION_LEVEL = 0; 218 | GCC_PREPROCESSOR_DEFINITIONS = ( 219 | "DEBUG=1", 220 | "$(inherited)", 221 | ); 222 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 223 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 224 | GCC_WARN_UNDECLARED_SELECTOR = YES; 225 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 226 | GCC_WARN_UNUSED_FUNCTION = YES; 227 | GCC_WARN_UNUSED_VARIABLE = YES; 228 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 229 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 230 | MTL_FAST_MATH = YES; 231 | ONLY_ACTIVE_ARCH = YES; 232 | SDKROOT = iphoneos; 233 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 234 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 235 | }; 236 | name = Debug; 237 | }; 238 | 44962FED23E7A54B00E2A598 /* Release */ = { 239 | isa = XCBuildConfiguration; 240 | buildSettings = { 241 | ALWAYS_SEARCH_USER_PATHS = NO; 242 | CLANG_ANALYZER_NONNULL = YES; 243 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 244 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 245 | CLANG_CXX_LIBRARY = "libc++"; 246 | CLANG_ENABLE_MODULES = YES; 247 | CLANG_ENABLE_OBJC_ARC = YES; 248 | CLANG_ENABLE_OBJC_WEAK = YES; 249 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 250 | CLANG_WARN_BOOL_CONVERSION = YES; 251 | CLANG_WARN_COMMA = YES; 252 | CLANG_WARN_CONSTANT_CONVERSION = YES; 253 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 254 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 255 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 256 | CLANG_WARN_EMPTY_BODY = YES; 257 | CLANG_WARN_ENUM_CONVERSION = YES; 258 | CLANG_WARN_INFINITE_RECURSION = YES; 259 | CLANG_WARN_INT_CONVERSION = YES; 260 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 261 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 262 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 263 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 264 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 265 | CLANG_WARN_STRICT_PROTOTYPES = YES; 266 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 267 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 268 | CLANG_WARN_UNREACHABLE_CODE = YES; 269 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 270 | COPY_PHASE_STRIP = NO; 271 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 272 | ENABLE_NS_ASSERTIONS = NO; 273 | ENABLE_STRICT_OBJC_MSGSEND = YES; 274 | GCC_C_LANGUAGE_STANDARD = gnu11; 275 | GCC_NO_COMMON_BLOCKS = YES; 276 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 277 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 278 | GCC_WARN_UNDECLARED_SELECTOR = YES; 279 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 280 | GCC_WARN_UNUSED_FUNCTION = YES; 281 | GCC_WARN_UNUSED_VARIABLE = YES; 282 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 283 | MTL_ENABLE_DEBUG_INFO = NO; 284 | MTL_FAST_MATH = YES; 285 | SDKROOT = iphoneos; 286 | SWIFT_COMPILATION_MODE = wholemodule; 287 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 288 | VALIDATE_PRODUCT = YES; 289 | }; 290 | name = Release; 291 | }; 292 | 44962FEF23E7A54B00E2A598 /* Debug */ = { 293 | isa = XCBuildConfiguration; 294 | buildSettings = { 295 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 296 | CLANG_ENABLE_MODULES = YES; 297 | CODE_SIGN_STYLE = Automatic; 298 | DEVELOPMENT_TEAM = RYQWBTQRPT; 299 | INFOPLIST_FILE = SwiftSampleApp/Info.plist; 300 | LD_RUNPATH_SEARCH_PATHS = ( 301 | "$(inherited)", 302 | "@executable_path/Frameworks", 303 | ); 304 | PRODUCT_BUNDLE_IDENTIFIER = com.iconfactory.SwiftSampleApp; 305 | PRODUCT_NAME = "$(TARGET_NAME)"; 306 | SWIFT_OBJC_BRIDGING_HEADER = "SwiftSampleApp-Bridging-Header.h"; 307 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 308 | SWIFT_VERSION = 5.0; 309 | TARGETED_DEVICE_FAMILY = "1,2"; 310 | }; 311 | name = Debug; 312 | }; 313 | 44962FF023E7A54B00E2A598 /* Release */ = { 314 | isa = XCBuildConfiguration; 315 | buildSettings = { 316 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 317 | CLANG_ENABLE_MODULES = YES; 318 | CODE_SIGN_STYLE = Automatic; 319 | DEVELOPMENT_TEAM = RYQWBTQRPT; 320 | INFOPLIST_FILE = SwiftSampleApp/Info.plist; 321 | LD_RUNPATH_SEARCH_PATHS = ( 322 | "$(inherited)", 323 | "@executable_path/Frameworks", 324 | ); 325 | PRODUCT_BUNDLE_IDENTIFIER = com.iconfactory.SwiftSampleApp; 326 | PRODUCT_NAME = "$(TARGET_NAME)"; 327 | SWIFT_OBJC_BRIDGING_HEADER = "SwiftSampleApp-Bridging-Header.h"; 328 | SWIFT_VERSION = 5.0; 329 | TARGETED_DEVICE_FAMILY = "1,2"; 330 | }; 331 | name = Release; 332 | }; 333 | /* End XCBuildConfiguration section */ 334 | 335 | /* Begin XCConfigurationList section */ 336 | 44962FD523E7A54A00E2A598 /* Build configuration list for PBXProject "SwiftSampleApp" */ = { 337 | isa = XCConfigurationList; 338 | buildConfigurations = ( 339 | 44962FEC23E7A54B00E2A598 /* Debug */, 340 | 44962FED23E7A54B00E2A598 /* Release */, 341 | ); 342 | defaultConfigurationIsVisible = 0; 343 | defaultConfigurationName = Release; 344 | }; 345 | 44962FEE23E7A54B00E2A598 /* Build configuration list for PBXNativeTarget "SwiftSampleApp" */ = { 346 | isa = XCConfigurationList; 347 | buildConfigurations = ( 348 | 44962FEF23E7A54B00E2A598 /* Debug */, 349 | 44962FF023E7A54B00E2A598 /* Release */, 350 | ); 351 | defaultConfigurationIsVisible = 0; 352 | defaultConfigurationName = Release; 353 | }; 354 | /* End XCConfigurationList section */ 355 | }; 356 | rootObject = 44962FD223E7A54A00E2A598 /* Project object */; 357 | } 358 | -------------------------------------------------------------------------------- /UIKit/SwiftSampleApp/SwiftSampleApp/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SwiftSampleApp 4 | // 5 | // Created by Craig Hockenberry on 2/2/20. 6 | // Copyright © 2020 The Iconfactory. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | // MARK: UISceneSession Lifecycle 22 | 23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 24 | // Called when a new scene session is being created. 25 | // Use this method to select a configuration to create the new scene with. 26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 27 | } 28 | 29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 30 | // Called when the user discards a scene session. 31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 33 | } 34 | 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /UIKit/SwiftSampleApp/SwiftSampleApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /UIKit/SwiftSampleApp/SwiftSampleApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /UIKit/SwiftSampleApp/SwiftSampleApp/Base.lproj/LaunchScreen.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 | -------------------------------------------------------------------------------- /UIKit/SwiftSampleApp/SwiftSampleApp/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 | 27 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /UIKit/SwiftSampleApp/SwiftSampleApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | UISceneStoryboardFile 37 | Main 38 | 39 | 40 | 41 | 42 | UILaunchStoryboardName 43 | LaunchScreen 44 | UIMainStoryboardFile 45 | Main 46 | UIRequiredDeviceCapabilities 47 | 48 | armv7 49 | 50 | UISupportedInterfaceOrientations 51 | 52 | UIInterfaceOrientationPortrait 53 | UIInterfaceOrientationLandscapeLeft 54 | UIInterfaceOrientationLandscapeRight 55 | 56 | UISupportedInterfaceOrientations~ipad 57 | 58 | UIInterfaceOrientationPortrait 59 | UIInterfaceOrientationPortraitUpsideDown 60 | UIInterfaceOrientationLandscapeLeft 61 | UIInterfaceOrientationLandscapeRight 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /UIKit/SwiftSampleApp/SwiftSampleApp/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // SwiftSampleApp 4 | // 5 | // Created by Craig Hockenberry on 2/2/20. 6 | // Copyright © 2020 The Iconfactory. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 12 | 13 | var window: UIWindow? 14 | 15 | 16 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 17 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 18 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 19 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 20 | guard let _ = (scene as? UIWindowScene) else { return } 21 | } 22 | 23 | func sceneDidDisconnect(_ scene: UIScene) { 24 | // Called as the scene is being released by the system. 25 | // This occurs shortly after the scene enters the background, or when its session is discarded. 26 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 27 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 28 | } 29 | 30 | func sceneDidBecomeActive(_ scene: UIScene) { 31 | // Called when the scene has moved from an inactive state to an active state. 32 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 33 | } 34 | 35 | func sceneWillResignActive(_ scene: UIScene) { 36 | // Called when the scene will move from an active state to an inactive state. 37 | // This may occur due to temporary interruptions (ex. an incoming phone call). 38 | } 39 | 40 | func sceneWillEnterForeground(_ scene: UIScene) { 41 | // Called as the scene transitions from the background to the foreground. 42 | // Use this method to undo the changes made on entering the background. 43 | } 44 | 45 | func sceneDidEnterBackground(_ scene: UIScene) { 46 | // Called as the scene transitions from the foreground to the background. 47 | // Use this method to save data, release shared resources, and store enough scene-specific state information 48 | // to restore the scene back to its current state. 49 | } 50 | 51 | 52 | } 53 | 54 | -------------------------------------------------------------------------------- /UIKit/SwiftSampleApp/SwiftSampleApp/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SwiftSampleApp 4 | // 5 | // Created by Craig Hockenberry on 2/2/20. 6 | // Copyright © 2020 The Iconfactory. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | @IBOutlet var simpleLabel: UILabel! 14 | @IBOutlet var advancedLabel: UILabel! 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | 19 | // simple example 20 | do { 21 | let markdownString = "This is a **_simple_ example** that _shows_ **Markdown** being used for attributed strings." 22 | 23 | let attributedString = NSAttributedString(markdownRepresentation: markdownString, attributes: [.font : UIFont.systemFont(ofSize: 17.0), .foregroundColor: UIColor.systemPurple ]) 24 | 25 | simpleLabel.attributedText = attributedString; 26 | } 27 | 28 | // advanced example 29 | do { 30 | let shadow = NSShadow() 31 | shadow.shadowColor = UIColor.systemOrange.withAlphaComponent(0.75) 32 | shadow.shadowOffset = CGSize(width: 1, height: 1) 33 | shadow.shadowBlurRadius = 2 34 | 35 | let baseAttributes: [NSAttributedString.Key : Any] = [.font : UIFont.preferredFont(forTextStyle: .title2), .foregroundColor: UIColor.systemTeal, .shadow: shadow] 36 | 37 | let styleAttributes: [MarkdownStyleKey: [NSAttributedString.Key : Any]] = [ 38 | .emphasisSingle: [ .font: UIFont.preferredFont(forTextStyle: .title2), .foregroundColor: UIColor.systemTeal, .underlineColor: UIColor.systemIndigo, .underlineStyle: 1 ], 39 | .emphasisDouble: [ .font: UIFont.preferredFont(forTextStyle: .title1), .foregroundColor: UIColor.systemBlue ], 40 | .emphasisBoth: [ .font: UIFont.preferredFont(forTextStyle: .title1), .strokeColor: UIColor.systemBlue, .strokeWidth: 3 ] 41 | ] 42 | 43 | let markdownString = NSLocalizedString("**Advanced _features_** let you adjust _individual_ styles, but it's still _simple_ to do with **Markdown**.", comment: "Use Markdown in localized strings!") 44 | 45 | let attributedString = NSAttributedString(markdownRepresentation: markdownString, baseAttributes: baseAttributes, styleAttributes: styleAttributes) 46 | 47 | advancedLabel.attributedText = attributedString; 48 | } 49 | } 50 | 51 | 52 | } 53 | 54 | --------------------------------------------------------------------------------