)
175 | NSShadow *shadow = [[NSShadow alloc] init];
176 | shadow.shadowOffset = CGSizeMake(0, 0);
177 | shadow.shadowBlurRadius = 2.0;
178 | shadow.shadowColor = [UIColor blackColor];
179 |
180 | if (attributeDictionary[@"offset"]) {
181 | shadow.shadowOffset = CGSizeFromString(attributeDictionary[@"offset"]);
182 | }
183 | if (attributeDictionary[@"blurradius"]) {
184 | shadow.shadowBlurRadius = [attributeDictionary[@"blurradius"] doubleValue];
185 | }
186 | if (attributeDictionary[@"color"]) {
187 | shadow.shadowColor = [self colorFromHexString:attributeDictionary[@"color"]];
188 | }
189 |
190 | [nodeAttributedString addAttribute:NSShadowAttributeName value:shadow range:nodeAttributedStringRange];
191 | #endif
192 | }
193 |
194 | // Font Tag
195 | else if (strncmp("font", (const char *)xmlNode->name, strlen((const char *)xmlNode->name)) == 0) {
196 | NSString *fontName = nil;
197 | NSNumber *fontSize = nil;
198 | UIColor *foregroundColor = nil;
199 | UIColor *backgroundColor = nil;
200 |
201 | if (attributeDictionary[@"face"]) {
202 | fontName = attributeDictionary[@"face"];
203 | }
204 | if (attributeDictionary[@"size"]) {
205 | fontSize = @([attributeDictionary[@"size"] doubleValue]);
206 | }
207 | if (attributeDictionary[@"color"]) {
208 | foregroundColor = [self colorFromHexString:attributeDictionary[@"color"]];
209 | }
210 | if (attributeDictionary[@"backgroundcolor"]) {
211 | backgroundColor = [self colorFromHexString:attributeDictionary[@"backgroundcolor"]];
212 | }
213 |
214 | if (fontName == nil && fontSize != nil) {
215 | [nodeAttributedString addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:[fontSize doubleValue]] range:nodeAttributedStringRange];
216 | }
217 | else if (fontName != nil && fontSize == nil) {
218 | [nodeAttributedString addAttribute:NSFontAttributeName value:[self fontOrSystemFontForName:fontName size:12.0] range:nodeAttributedStringRange];
219 | }
220 | else if (fontName != nil && fontSize != nil) {
221 | [nodeAttributedString addAttribute:NSFontAttributeName value:[self fontOrSystemFontForName:fontName size:fontSize.floatValue] range:nodeAttributedStringRange];
222 | }
223 |
224 | if (foregroundColor) {
225 | [nodeAttributedString addAttribute:NSForegroundColorAttributeName value:foregroundColor range:nodeAttributedStringRange];
226 | }
227 | if (backgroundColor) {
228 | [nodeAttributedString addAttribute:NSBackgroundColorAttributeName value:backgroundColor range:nodeAttributedStringRange];
229 | }
230 | }
231 |
232 | // Paragraph Tag
233 | else if (strncmp("p", (const char *)xmlNode->name, strlen((const char *)xmlNode->name)) == 0) {
234 | NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
235 |
236 | if ([attributeDictionary objectForKey:@"align"]) {
237 | NSString *alignString = [attributeDictionary[@"align"] lowercaseString];
238 |
239 | if ([alignString isEqualToString:@"left"]) {
240 | paragraphStyle.alignment = NSTextAlignmentLeft;
241 | }
242 | else if ([alignString isEqualToString:@"center"]) {
243 | paragraphStyle.alignment = NSTextAlignmentCenter;
244 | }
245 | else if ([alignString isEqualToString:@"right"]) {
246 | paragraphStyle.alignment = NSTextAlignmentRight;
247 | }
248 | else if ([alignString isEqualToString:@"justify"]) {
249 | paragraphStyle.alignment = NSTextAlignmentJustified;
250 | }
251 | }
252 | if ([attributeDictionary objectForKey:@"linebreakmode"]) {
253 | NSString *lineBreakModeString = [attributeDictionary[@"linebreakmode"] lowercaseString];
254 |
255 | if ([lineBreakModeString isEqualToString:@"wordwrapping"]) {
256 | paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
257 | }
258 | else if ([lineBreakModeString isEqualToString:@"charwrapping"]) {
259 | paragraphStyle.lineBreakMode = NSLineBreakByCharWrapping;
260 | }
261 | else if ([lineBreakModeString isEqualToString:@"clipping"]) {
262 | paragraphStyle.lineBreakMode = NSLineBreakByClipping;
263 | }
264 | else if ([lineBreakModeString isEqualToString:@"truncatinghead"]) {
265 | paragraphStyle.lineBreakMode = NSLineBreakByTruncatingHead;
266 | }
267 | else if ([lineBreakModeString isEqualToString:@"truncatingtail"]) {
268 | paragraphStyle.lineBreakMode = NSLineBreakByTruncatingTail;
269 | }
270 | else if ([lineBreakModeString isEqualToString:@"truncatingmiddle"]) {
271 | paragraphStyle.lineBreakMode = NSLineBreakByTruncatingMiddle;
272 | }
273 | }
274 |
275 | if ([attributeDictionary objectForKey:@"firstlineheadindent"]) {
276 | paragraphStyle.firstLineHeadIndent = [attributeDictionary[@"firstlineheadindent"] doubleValue];
277 | }
278 | if ([attributeDictionary objectForKey:@"headindent"]) {
279 | paragraphStyle.headIndent = [attributeDictionary[@"headindent"] doubleValue];
280 | }
281 | if ([attributeDictionary objectForKey:@"hyphenationfactor"]) {
282 | paragraphStyle.hyphenationFactor = [attributeDictionary[@"hyphenationfactor"] doubleValue];
283 | }
284 | if ([attributeDictionary objectForKey:@"lineheightmultiple"]) {
285 | paragraphStyle.lineHeightMultiple = [attributeDictionary[@"lineheightmultiple"] doubleValue];
286 | }
287 | if ([attributeDictionary objectForKey:@"linespacing"]) {
288 | paragraphStyle.lineSpacing = [attributeDictionary[@"linespacing"] doubleValue];
289 | }
290 | if ([attributeDictionary objectForKey:@"maximumlineheight"]) {
291 | paragraphStyle.maximumLineHeight = [attributeDictionary[@"maximumlineheight"] doubleValue];
292 | }
293 | if ([attributeDictionary objectForKey:@"minimumlineheight"]) {
294 | paragraphStyle.minimumLineHeight = [attributeDictionary[@"minimumlineheight"] doubleValue];
295 | }
296 | if ([attributeDictionary objectForKey:@"paragraphspacing"]) {
297 | paragraphStyle.paragraphSpacing = [attributeDictionary[@"paragraphspacing"] doubleValue];
298 | }
299 | if ([attributeDictionary objectForKey:@"paragraphspacingbefore"]) {
300 | paragraphStyle.paragraphSpacingBefore = [attributeDictionary[@"paragraphspacingbefore"] doubleValue];
301 | }
302 | if ([attributeDictionary objectForKey:@"tailindent"]) {
303 | paragraphStyle.tailIndent = [attributeDictionary[@"tailindent"] doubleValue];
304 | }
305 |
306 | [nodeAttributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:nodeAttributedStringRange];
307 |
308 | // MR - For some reason they are not adding the paragraph space when parsing the tag
309 | [nodeAttributedString appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n"]];
310 | }
311 |
312 |
313 | // Links
314 | else if (strncmp("a href", (const char *)xmlNode->name, strlen((const char *)xmlNode->name)) == 0) {
315 |
316 | xmlChar *value = xmlNodeListGetString(xmlNode->doc, xmlNode->xmlChildrenNode, 1);
317 | if (value)
318 | {
319 | NSString *title = [NSString stringWithCString:(const char *)value encoding:NSUTF8StringEncoding];
320 | NSString *link = attributeDictionary[@"href"];
321 | // Sometimes, an a tag may not have a corresponding href attribute.
322 | // This should not be added as an attribute.
323 | if (link)
324 | {
325 | [nodeAttributedString addAttribute:NSLinkAttributeName value:link range:NSMakeRange(0, title.length)];
326 | }
327 | }
328 | }
329 |
330 | // New Lines
331 | else if (strncmp("br", (const char *)xmlNode->name, strlen((const char *)xmlNode->name)) == 0) {
332 | [nodeAttributedString appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n"]];
333 | }
334 |
335 | // Images
336 | else if (strncmp("img", (const char *)xmlNode->name, strlen((const char *)xmlNode->name)) == 0) {
337 | #if __has_include()
338 | NSString *src = attributeDictionary[@"src"];
339 | NSString *width = attributeDictionary[@"width"];
340 | NSString *height = attributeDictionary[@"height"];
341 |
342 | if (src != nil) {
343 | UIImage *image = imageMap[src];
344 | if (image == nil) {
345 | image = [UIImage imageNamed:src];
346 | }
347 |
348 | if (image != nil) {
349 | NSTextAttachment *imageAttachment = [[NSTextAttachment alloc] init];
350 | imageAttachment.image = image;
351 | if (width != nil && height != nil) {
352 | imageAttachment.bounds = CGRectMake(0, 0, [width integerValue] / 2, [height integerValue] / 2);
353 | }
354 | NSAttributedString *imageAttributeString = [NSAttributedString attributedStringWithAttachment:imageAttachment];
355 | [nodeAttributedString appendAttributedString:imageAttributeString];
356 | }
357 | }
358 | #endif
359 | }
360 | }
361 |
362 | return nodeAttributedString;
363 | }
364 |
365 | + (UIFont *)fontOrSystemFontForName:(NSString *)fontName size:(CGFloat)fontSize {
366 | UIFont * font = [UIFont fontWithName:fontName size:fontSize];
367 | if(font) {
368 | return font;
369 | }
370 | return [UIFont systemFontOfSize:fontSize];
371 | }
372 |
373 | + (UIColor *)colorFromHexString:(NSString *)hexString
374 | {
375 | if (hexString == nil)
376 | return nil;
377 |
378 | hexString = [hexString stringByReplacingOccurrencesOfString:@"#" withString:@""];
379 | char *p;
380 | NSUInteger hexValue = strtoul([hexString cStringUsingEncoding:NSUTF8StringEncoding], &p, 16);
381 |
382 | return [UIColor colorWithRed:((hexValue & 0xff0000) >> 16) / 255.0 green:((hexValue & 0xff00) >> 8) / 255.0 blue:(hexValue & 0xff) / 255.0 alpha:1.0];
383 | }
384 |
385 | @end
386 |
--------------------------------------------------------------------------------
/NSAttributedString-DDHTML.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'NSAttributedString-DDHTML'
3 | s.version = '1.2.0'
4 | s.license = {
5 | :type => 'BSD',
6 | :text => <<-LICENSE
7 | Redistribution and use in source and binary forms, with or without
8 | modification, are permitted provided that the following conditions are met:
9 | * Redistributions of source code must retain the above copyright
10 | notice, this list of conditions and the following disclaimer.
11 | * Redistributions in binary form must reproduce the above copyright
12 | notice, this list of conditions and the following disclaimer in the
13 | documentation and/or other materials provided with the distribution.
14 | * Neither the name of the nor the
15 | names of its contributors may be used to endorse or promote products
16 | derived from this software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY
22 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 | LICENSE
29 | }
30 | s.summary = 'Easily create NSAttributedStrings using HTML.'
31 | s.homepage = 'https://github.com/dbowen/NSAttributedString-DDHTML'
32 | s.author = { 'Derek Bowen' => 'dbowen@demiurgic.co' }
33 | s.source = { :git => 'https://github.com/dbowen/NSAttributedString-DDHTML.git', :tag => "v#{s.version}" }
34 | s.description = 'Simplifies working with NSAttributedString by allowing you to use HTML to describe formatting behaviors.'
35 | s.requires_arc = true
36 | s.ios.deployment_target = '7.0'
37 | s.tvos.deployment_target = '9.0'
38 | s.watchos.deployment_target = '2.0'
39 |
40 | s.source_files = 'NSAttributedString+DDHTML'
41 | s.libraries = 'xml2'
42 | s.xcconfig = { 'HEADER_SEARCH_PATHS' => '$(SDKROOT)/usr/include/libxml2' }
43 | end
44 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | NSAttributedString+DDHTML
2 | =========================
3 |
4 | Simplifies working with attributed strings by allowing you to use HTML to describe formatting behaviors.
5 |
6 | NSAttributedString+DDHTML isn't intended to support full HTML rendering. Instead it provides a quick, effient and light-weight implementation for leveraging attributed strings when utilizing native UIKit interface elements.
7 |
8 | ## License
9 | -----
10 |
11 | It is open source and covered by a standard BSD license. That means you have to mention *Derek Bowen @ Deloite Digital* as the original author of this code.
12 |
13 | ## Requirements
14 | -----
15 | NSAttributedString+DDHTML requires a minimum iOS deployment target of iOS 7.0 because of:
16 |
17 | * NSTextAttachment
18 |
19 | ## Setup
20 | -----
21 |
22 | ### Using CocoaPods
23 |
24 | One of the easiest ways to integrate NSAttributedString+DDHTML in your project is to use [CocoaPods](http://cocoapods.org/):
25 |
26 | 1. Add the following line to your `Podfile`:
27 |
28 | ````ruby
29 | pod "NSAttributedString-DDHTML"
30 | ````
31 |
32 | 2. In your project directory, run `pod update`
33 | 3. You should now be able to add `#import ` to any of your target's source files to use the library!
34 |
35 | ### Manual
36 |
37 | 1. Add NSAttributedString+DDHTML.m/h to your project.
38 | 2. Add *libxml2.dylib* to the *"Link Binary With Libraries"* section of your target's build phase.
39 | 3. Add *${SDKROOT}/usr/include/libxml2* to your project's *Header Search Paths* under *Build Settings*.
40 | 4. Start using it!
41 |
42 | ## Usage
43 | -----
44 | #import "NSAttributedString+DDHTML.h"
45 |
46 | ...
47 |
48 | NSAttributedString *attributedString = [NSAttributedString attributedStringFromHTML:@"My formatted string."];
49 |
50 | ...
51 |
52 |
53 | ## Supported Tags
54 | -----
55 |
56 | ### b, strong - Bold (iOS, watchOS, tvOS)
57 |
58 | ### i - Italics (iOS, watchOS, tvOS)
59 |
60 | ### u - Underline (iOS, watchOS, tvOS)
61 |
62 | ### strike - Strikethrough (iOS, watchOS, tvOS)
63 |
64 | ### stroke - Stroke (iOS, watchOS, tvOS)
65 | * **color**: Color of stroke, e.g. stroke="#ff0000"
66 | * **width**: Width of stroke, e.g. stroke="2.0"
67 | * **nofill**: If present text color will be transparent
68 |
69 | ### shadow - Shadow (iOS, tvOS)
70 | * **offset**: Amount to offset shadow from center of text, e.g. offset="{1.0, 1.0}"
71 | * **blurRadius**: Radius/thickness of the shadow
72 | * **color**: Color of the shadow
73 |
74 | ### font - Font (iOS, watchOS, tvOS)
75 | * **face**: Name of font to use, e.g. face="Avenir-Heavy"
76 | * **size**: Size of the text, e.g. size="12.0"
77 | * **color**: Color of the text, e.g. color="#fafafa"
78 | * **backgroundColor**: Color of the text background, e.g. backgroundColor="#333333"
79 |
80 | ### br - Line Break (iOS, watchOS, tvOS)
81 |
82 | ### p - Paragraph (iOS, watchOS, tvOS)
83 | * **align**: Alignment of text, e.g. align="center"
84 | * Available values: left, center, right, justify
85 | * **lineBreakMode**: How to handle text which doesn't fit horizontally in the view
86 | * Available values: WordWrapping, CharWrapping, Clipping, TruncatingHead, TruncatingTail, TruncatingMiddle
87 | * **firstLineHeadIndent**
88 | * **headIndent**
89 | * **hyphenationFactor**
90 | * **lineHeightMultiple**
91 | * **lineSpacing**
92 | * **maximumLineHeight**
93 | * **minimumLineHeight**
94 | * **paragraphSpacing**
95 | * **paragraphSpacingBefore**
96 | * **tailIndent**
97 |
98 | ### img - Image (iOS, tvOS)
99 | * src : key in imagerMapper parameter
100 | * width : px
101 | * height : px
102 |
103 |
--------------------------------------------------------------------------------