├── .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 |
--------------------------------------------------------------------------------