├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── NimbusKit-AttributedLabel.podspec
├── PATENTS
├── README.md
├── attributedlabel.xcworkspace
└── contents.xcworkspacedata
├── catalog
└── BasicInstantiation
│ ├── BasicInstantiation.xcodeproj
│ └── project.pbxproj
│ └── BasicInstantiation
│ ├── AppDelegate.h
│ ├── AppDelegate.m
│ ├── BasicInstantiation-Info.plist
│ ├── BasicInstantiation-Prefix.pch
│ ├── Images.xcassets
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── Icon-iOS7@2x.png
│ │ ├── Icon.png
│ │ └── Icon@2x.png
│ └── LaunchImage.launchimage
│ │ ├── Contents.json
│ │ ├── Default-568h-iOS7-flat@2x-1.png
│ │ ├── Default-568h-iOS7-flat@2x.png
│ │ ├── Default-flat.png
│ │ └── Default-iOS7-flat@2x.png
│ ├── controllers
│ ├── BasicInstantiationViewController.h
│ └── BasicInstantiationViewController.m
│ ├── en.lproj
│ └── InfoPlist.strings
│ └── main.m
├── docs
└── gfx
│ ├── NIAttributedLabelExample1.png
│ ├── NIAttributedLabelExample2.png
│ ├── NIAttributedLabelExample3.png
│ ├── NIAttributedLabelExample4.png
│ ├── NIAttributedLabelExample5.png
│ ├── NIAttributedLabelExample6.png
│ ├── NIAttributedLabelIB.png
│ ├── NIAttributedLabelLinkAttributes.png
│ ├── NIAttributedLabel_autoDetectLinksOff.png
│ ├── NIAttributedLabel_autoDetectLinksOn.png
│ ├── NIAttributedLabel_inlineimages.png
│ └── banner.gif
└── src
├── NIAttributedLabel.h
├── NIAttributedLabel.m
├── NSMutableAttributedString+NimbusKitAttributedLabel.h
├── NSMutableAttributedString+NimbusKitAttributedLabel.m
├── NimbusKitAttributedLabel.h
└── NimbusKitBasics.h
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS X
2 | .DS_Store
3 |
4 | # Xcode
5 | build/
6 | *.pbxuser
7 | !default.pbxuser
8 | *.mode1v3
9 | !default.mode1v3
10 | *.mode2v3
11 | !default.mode2v3
12 | *.perspectivev3
13 | !default.perspectivev3
14 | xcuserdata
15 | *.xccheckout
16 | profile
17 | *.moved-aside
18 | DerivedData
19 | *.hmap
20 | *.ipa
21 |
22 | # CocoaPods
23 | Pods
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Doing the following will ensure speedy merging of pull requests:
2 |
3 | - Document any new functionality.
4 | - Add your name to the README.md file's contributors section, if it's not already there.
5 |
6 | Thanks for contributing!
7 | <3 Jeff
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD License
2 |
3 | For NimbusKit Attributed Label.
4 |
5 | Copyright (c) 2014, NimbusKit. All rights reserved.
6 |
7 | Redistribution and use in source and binary forms, with or without modification, are permitted
8 | provided that the following conditions are met:
9 |
10 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions
11 | and the following disclaimer.
12 |
13 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions
14 | and the following disclaimer in the documentation and/or other materials provided with the
15 | distribution.
16 |
17 | 3. Neither the name NimbusKit nor the names of its contributors may be used to endorse or promote
18 | products derived from this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
21 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
22 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
23 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
26 | IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
27 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/NimbusKit-AttributedLabel.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "NimbusKit-AttributedLabel"
3 | s.version = "1.0.0"
4 | s.license = { :type => 'BSD' }
5 | s.summary = "UILabel subsitute with Core Text rendering, link detection, and inline images."
6 | s.description = <<-DESC
7 | A UILabel substitute with data detectors, links, inline images, and Core Text attributes available right out of the box.
8 | DESC
9 | s.homepage = "https://github.com/nimbuskit/attributedlabel"
10 | s.author = { "Jeff Verkoeyen" => "jverkoey@gmail.com" }
11 | s.social_media_url = "http://twitter.com/featherless"
12 | s.requires_arc = true
13 | s.platform = :ios, '6.0'
14 | s.source = { :git => "https://github.com/nimbuskit/attributedlabel.git", :tag => "1.0.0" }
15 | s.source_files = 'src'
16 | s.public_header_files = 'src/{NimbusKitAttributedLabel,NIAttributedLabel}.h'
17 | s.frameworks = 'CoreText', 'CoreGraphics', 'QuartzCore'
18 | s.screenshots = [ "https://raw.githubusercontent.com/NimbusKit/attributedlabel/master/docs/gfx/NIAttributedLabelExample1.png",
19 | "https://raw.githubusercontent.com/NimbusKit/attributedlabel/master/docs/gfx/NIAttributedLabel_inlineimages.png" ]
20 | end
21 |
--------------------------------------------------------------------------------
/PATENTS:
--------------------------------------------------------------------------------
1 | Additional Grant of Patent Rights
2 |
3 | "Software" means the Attributed Label software distributed by NimbusKit.
4 |
5 | NimbusKit hereby grants you a perpetual, worldwide, royalty-free, non-exclusive,
6 | irrevocable (subject to the termination provision below) license under any
7 | rights in any patent claims owned by NimbusKit, to make, have made, use, sell,
8 | offer to sell, import, and otherwise transfer the Software. For avoidance of
9 | doubt, no license is granted under NimbusKit's rights in any patent claims that
10 | are infringed by (i) modifications to the Software made by you or a third party,
11 | or (ii) the Software in combination with any software or other technology
12 | provided by you or a third party.
13 |
14 | The license granted hereunder will terminate, automatically and without notice,
15 | for anyone that makes any claim (including by filing any lawsuit, assertion or
16 | other action) alleging (a) direct, indirect, or contributory infringement or
17 | inducement to infringe any patent: (i) by NimbusKit or any of its subsidiaries or
18 | affiliates, whether or not such claim is related to the Software, (ii) by any
19 | party if such claim arises in whole or in part from any software, product or
20 | service of NimbusKit or any of its subsidiaries or affiliates, whether or not
21 | such claim is related to the Software, or (iii) by any party relating to the
22 | Software; or (b) that any right in any patent claim of NimbusKit is invalid or
23 | unenforceable.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | 
4 |
5 |
6 |
7 | A UILabel substitute with data detectors, links, inline images, and Core Text attributes available right out of the box.
8 |
9 | 
10 |
11 | 
12 |
13 | iOS 6 introduced supported for attributed text via `attributedText` but it still lacks several significant features that NimbusKit provides:
14 |
15 | - Links, both explicit and implicitly via data detection.
16 | - Inline images.
17 | - Adopting any existing UILabel styles when the text is changed.
18 | - Convenience methods for modifying substrings of the label.
19 |
20 | If you do not need any of these features then you should consider simply using UILabel.
21 |
22 | Adding it to your Project
23 | =========================
24 |
25 | Drag all of the files from the `src` directory into your project and then import the library header.
26 |
27 | ```objc
28 | #import "NimbusKitAttributedLabel.h"
29 | ```
30 |
31 | If you would like to use the internal helper methods on `NSMutableAttributedString`, import:
32 |
33 | ```objc
34 | #import "NSMutableAttributedString+NimbusKitAttributedLabel.h"
35 | ```
36 |
37 | This category header is not included in the library header.
38 |
39 | Using NIAttributedLabel
40 | =======================
41 |
42 | In general using an NIAttributedLabel is similar to using a UILabel. This does not mean, however,that you should start using NIAttributedLabel everywhere that you can. Notably, it takes significantly more time to create and render an NIAttributedLabel than to create and render a corresponding UILabel, and especially compared to rendering the text manually. NIAttributedLabel is designed as a convenience, so take that into account when designing your apps - convenience comes at a cost!
43 |
44 | Creating a Label in Code
45 | ------------------------
46 |
47 | NIAttributedLabel is a subclass of UILabel. When text is assigned to the label, all of the label's style properties are applied to the string in its entirety.
48 |
49 | ```objc
50 | NIAttributedLabel* label = [[NIAttributedLabel alloc] initWithFrame:CGRectZero];
51 |
52 | // The internal NSAttributedString will apply all of UILabel's style attributes when
53 | // we assign text.
54 | label.text = @"Nimbus";
55 |
56 | [label sizeToFit];
57 |
58 | [view addSubview:label];
59 | ```
60 |
61 | Creating a Label in Interface Builder
62 | -------------------------------------
63 |
64 | You can use an attributed label within Interface Builder by creating a \c UILabel and changing its class to NIAttributedLabel. This will allow you to set standard UILabel styles that apply to the entire string. If you need to style specific parts of the string then this must be done in code.
65 |
66 | 
67 |
68 | Features Overview
69 | =================
70 |
71 | - Automatic link detection using data detectors
72 | - Link attributes
73 | - Explicit links
74 | - Inline images
75 | - Underlining
76 | - Justifying paragraphs
77 | - Stroking
78 | - Kerning
79 | - Setting rich text styles at specific ranges
80 |
81 | Links
82 | -----
83 |
84 | ### Automatic Link Detection
85 |
86 | Automatic link detection is provided via NSDataDetector.
87 |
88 | Data detection is off by default and can be enabled by setting NIAttributedLabel::autoDetectLinks to YES. You may configure the types of data that are detected by modifying the NIAttributedLabel::dataDetectorTypes property. By default only urls are detected.
89 |
90 | @attention NIAttributedLabel is not designed to detect html anchor tags (i.e. <a>). If you would like to attach a URL to a given range of text you must use NIAttributedLabel::addLink:range:. You may add links to the attributed string using the attribute NIAttributedLabelLinkAttributeName. The NIAttributedLabelLinkAttributeName value must be an instance of NSTextCheckingResult.
91 |
92 | 
93 |
94 | ```objc
95 | // Enable link detection on the label.
96 | myLabel.autoDetectLinks = YES;
97 | ```
98 |
99 | 
100 |
101 | Enabling automatic link detection will automatically enable user interation with the label view
102 | so that the user can tap the detected links.
103 |
104 | ### Link Attributes
105 |
106 | Detected links will use NIAttributedLabel::linkColor and NIAttributedLabel::highlightedLinkBackgroundColor to differentiate themselves from standard text. `linkColor` is the text color of any link, while `highlightedLinkBackgroundColor` is the color of the background frame drawn around the link when it is tapped. You can easily add underlines to links by enabling NIAttributedLabel::linksHaveUnderlines. You can customize link attributes in more detail by directly modifying the NIAttributedLabel::attributesForLinks property.
107 |
108 | 
109 |
110 | #### A Note on Performance
111 |
112 | Automatic link detection is expensive. You can choose to defer automatic link detection by enabling NIAttributedLabel::deferLinkDetection. This will move the link detection to a separate background thread. Once the links have been detected the label will be redrawn.
113 |
114 | ### Handling Taps on Links
115 |
116 | The NIAttributedLabelDelegate protocol allows you to process events fired by the the user tapping a link. The protocol methods provide the tap point as well as the data pertaining to the tapped link.
117 |
118 | ```objc
119 | - (void)attributedLabel:(NIAttributedLabel)attributedLabel didSelectTextCheckingResult:(NSTextCheckingResult)result atPoint:(CGPoint)point {
120 | [[UIApplication sharedApplication] openURL:result.URL];
121 | }
122 | ```
123 |
124 | ### Explicit Links
125 |
126 | Links can be added explicitly using NIAttributedLabel::addLink:range:.
127 |
128 | ```objc
129 | // Add a link to the string 'nimbus' in myLabel.
130 | [myLabel addLink:[NSURL URLWithString:@"nimbus://custom/url"]
131 | range:[myLabel.text rangeOfString:@"nimbus"]];
132 | ```
133 |
134 | Inline Images
135 | -------------
136 |
137 | Inline images may be inserted using the `-insertImage:atIndex:` family of methods.
138 |
139 | ```objc
140 | NIAttributedLabel* label = [NIAttributedLabel new];
141 | label.text = @"NimbusKit 2.0";
142 | label.font = [UIFont systemFontOfSize:24];
143 |
144 | [label insertImage:[UIImage imageNamed:@"AppIcon60x60"] atIndex:@"NimbusKit".length
145 | margins:UIEdgeInsetsZero verticalTextAlignment:NIVerticalTextAlignmentMiddle];
146 |
147 | label.frame = (CGRect){CGPointMake(20, 80), CGSizeZero};
148 |
149 | [label sizeToFit];
150 |
151 | [self.view addSubview:label];
152 | ```
153 |
154 | Generates the following output:
155 |
156 | 
157 |
158 | Modifying Style Attributes
159 | --------------------------
160 |
161 | ### Underlining Text
162 |
163 | To underline an entire label:
164 |
165 | ```objc
166 | // Underline the whole label with a single line.
167 | myLabel.underlineStyle = kCTUnderlineStyleSingle;
168 | ```
169 |
170 | Underline modifiers can also be added:
171 |
172 | ```objc
173 | // Underline the whole label with a dash dot single line.
174 | myLabel.underlineStyle = kCTUnderlineStyleSingle;
175 | myLabel.underlineStyleModifier = kCTUnderlinePatternDashDot;
176 | ```
177 |
178 | Underline styles and modifiers can be mixed to create the desired effect, which is shown in the following screenshot:
179 |
180 | 
181 |
182 | @remarks Underline style kCTUnderlineStyleThick does not draw a thicker line.
183 |
184 | ### Justifying Paragraphs
185 |
186 | NIAttributedLabel supports justified text using UITextAlignmentJustify.
187 |
188 | ```objc
189 | myLabel.textAlignment = UITextAlignmentJustify;
190 | ```
191 |
192 | ### Stroking Text
193 |
194 | ```objc
195 | myLabel.strokeWidth = 3.0;
196 | myLabel.strokeColor = [UIColor blackColor];
197 | ```
198 |
199 | A positive stroke width will render only the stroke.
200 |
201 | 
202 |
203 | A negative number will fill the stroke with textColor:
204 |
205 | ```objc
206 | myLabel.strokeWidth = -3.0;
207 | myLabel.strokeColor = [UIColor blackColor];
208 | ```
209 |
210 | 
211 |
212 |
213 | ### Kerning Text
214 |
215 | Kerning is the space between characters in points. A positive kern will increase the space
216 | between letters. Correspondingly a negative number will decrease the space.
217 |
218 | ```objc
219 | myLabel.textKern = -6.0;
220 | ```
221 |
222 | 
223 |
224 |
225 | ### Modifying Style at Specific Ranges
226 |
227 | All styles that can be added to the whole label (as well as default UILabel styles like font and
228 | text color) can be added to just a range of text.
229 |
230 | ```objc
231 | [myLabel setTextColor:[UIColor orangeColor] range:[myLabel.text rangeOfString:@"Nimbus"]];
232 | [myLabel setFont:[UIFont boldSystemFontOfSize:22] range:[myLabel.text rangeOfString:@"iOS"]];
233 | ```
234 |
235 | Requirements
236 | ============
237 |
238 | NIAttributedLabel must be compiled with the iOS 6 SDK or above. You must link to the CoreText and Core Graphics frameworks.
239 |
240 | Version History
241 | ===============
242 |
243 | 1.0.0 on Apr 30, 2014
244 | -----
245 |
246 | Initial release. Includes:
247 |
248 | - Zero dependencies!
249 | - Link detection.
250 | - Inline images.
251 | - Helper category on NSMutableAttributedString.
252 |
253 | Credits
254 | =======
255 |
256 | NIAttributedLabel was extracted from Nimbus 1.2.0 by [Jeff Verkoeyen](http://jeffverkoeyen.com/) ([featherless](http://twitter.com/)).
257 |
258 | Contributors
259 | ------------
260 |
261 | You can be the first! [Open a pull request now](https://github.com/NimbusKit/Basics/compare/).
262 |
263 | License
264 | =======
265 |
266 | NimbusKit's Attributed Label is licensed under the BSD three-clause license. For a more permissive license (no redistribution of copyright notice, etc.), please contact Jeff at jverkoey@gmail.com for pricing.
267 |
--------------------------------------------------------------------------------
/attributedlabel.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/catalog/BasicInstantiation/BasicInstantiation.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 66A3E0C8191002AD0094A9B6 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 66A3E0C7191002AD0094A9B6 /* Foundation.framework */; };
11 | 66A3E0CA191002AD0094A9B6 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 66A3E0C9191002AD0094A9B6 /* CoreGraphics.framework */; };
12 | 66A3E0CC191002AD0094A9B6 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 66A3E0CB191002AD0094A9B6 /* UIKit.framework */; };
13 | 66A3E0D2191002AD0094A9B6 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 66A3E0D0191002AD0094A9B6 /* InfoPlist.strings */; };
14 | 66A3E0D4191002AD0094A9B6 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 66A3E0D3191002AD0094A9B6 /* main.m */; };
15 | 66A3E0D8191002AD0094A9B6 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 66A3E0D7191002AD0094A9B6 /* AppDelegate.m */; };
16 | 66A3E0DA191002AD0094A9B6 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 66A3E0D9191002AD0094A9B6 /* Images.xcassets */; };
17 | 66A3E0FC191002E50094A9B6 /* NIAttributedLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = 66A3E0F8191002E50094A9B6 /* NIAttributedLabel.m */; };
18 | 66A3E0FD191002E50094A9B6 /* NSMutableAttributedString+NimbusKitAttributedLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = 66A3E0FB191002E50094A9B6 /* NSMutableAttributedString+NimbusKitAttributedLabel.m */; };
19 | 66A3E10A191008260094A9B6 /* BasicInstantiationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 66A3E109191008260094A9B6 /* BasicInstantiationViewController.m */; };
20 | /* End PBXBuildFile section */
21 |
22 | /* Begin PBXFileReference section */
23 | 66A3E0C4191002AD0094A9B6 /* BasicInstantiation.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BasicInstantiation.app; sourceTree = BUILT_PRODUCTS_DIR; };
24 | 66A3E0C7191002AD0094A9B6 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
25 | 66A3E0C9191002AD0094A9B6 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
26 | 66A3E0CB191002AD0094A9B6 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
27 | 66A3E0CF191002AD0094A9B6 /* BasicInstantiation-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "BasicInstantiation-Info.plist"; sourceTree = ""; };
28 | 66A3E0D1191002AD0094A9B6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; };
29 | 66A3E0D3191002AD0094A9B6 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
30 | 66A3E0D5191002AD0094A9B6 /* BasicInstantiation-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BasicInstantiation-Prefix.pch"; sourceTree = ""; };
31 | 66A3E0D6191002AD0094A9B6 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
32 | 66A3E0D7191002AD0094A9B6 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
33 | 66A3E0D9191002AD0094A9B6 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; };
34 | 66A3E0E0191002AD0094A9B6 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
35 | 66A3E0F7191002E50094A9B6 /* NIAttributedLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NIAttributedLabel.h; sourceTree = ""; };
36 | 66A3E0F8191002E50094A9B6 /* NIAttributedLabel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NIAttributedLabel.m; sourceTree = ""; };
37 | 66A3E0F9191002E50094A9B6 /* NimbusKitAttributedLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NimbusKitAttributedLabel.h; sourceTree = ""; };
38 | 66A3E0FA191002E50094A9B6 /* NSMutableAttributedString+NimbusKitAttributedLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSMutableAttributedString+NimbusKitAttributedLabel.h"; sourceTree = ""; };
39 | 66A3E0FB191002E50094A9B6 /* NSMutableAttributedString+NimbusKitAttributedLabel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSMutableAttributedString+NimbusKitAttributedLabel.m"; sourceTree = ""; };
40 | 66A3E0FE191002F70094A9B6 /* NimbusKitBasics.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NimbusKitBasics.h; sourceTree = ""; };
41 | 66A3E108191008260094A9B6 /* BasicInstantiationViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BasicInstantiationViewController.h; sourceTree = ""; };
42 | 66A3E109191008260094A9B6 /* BasicInstantiationViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BasicInstantiationViewController.m; sourceTree = ""; };
43 | 66AB557E191756000010FDCC /* NimbusKit-AttributedLabel.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; name = "NimbusKit-AttributedLabel.podspec"; path = "../../NimbusKit-AttributedLabel.podspec"; sourceTree = ""; };
44 | /* End PBXFileReference section */
45 |
46 | /* Begin PBXFrameworksBuildPhase section */
47 | 66A3E0C1191002AD0094A9B6 /* Frameworks */ = {
48 | isa = PBXFrameworksBuildPhase;
49 | buildActionMask = 2147483647;
50 | files = (
51 | 66A3E0CA191002AD0094A9B6 /* CoreGraphics.framework in Frameworks */,
52 | 66A3E0CC191002AD0094A9B6 /* UIKit.framework in Frameworks */,
53 | 66A3E0C8191002AD0094A9B6 /* Foundation.framework in Frameworks */,
54 | );
55 | runOnlyForDeploymentPostprocessing = 0;
56 | };
57 | /* End PBXFrameworksBuildPhase section */
58 |
59 | /* Begin PBXGroup section */
60 | 66A3E0BB191002AD0094A9B6 = {
61 | isa = PBXGroup;
62 | children = (
63 | 66AB557E191756000010FDCC /* NimbusKit-AttributedLabel.podspec */,
64 | 66A3E0F6191002CD0094A9B6 /* attributedlabel */,
65 | 66A3E0CD191002AD0094A9B6 /* BasicInstantiation */,
66 | 66A3E0C6191002AD0094A9B6 /* Frameworks */,
67 | 66A3E0C5191002AD0094A9B6 /* Products */,
68 | );
69 | sourceTree = "";
70 | };
71 | 66A3E0C5191002AD0094A9B6 /* Products */ = {
72 | isa = PBXGroup;
73 | children = (
74 | 66A3E0C4191002AD0094A9B6 /* BasicInstantiation.app */,
75 | );
76 | name = Products;
77 | sourceTree = "";
78 | };
79 | 66A3E0C6191002AD0094A9B6 /* Frameworks */ = {
80 | isa = PBXGroup;
81 | children = (
82 | 66A3E0C7191002AD0094A9B6 /* Foundation.framework */,
83 | 66A3E0C9191002AD0094A9B6 /* CoreGraphics.framework */,
84 | 66A3E0CB191002AD0094A9B6 /* UIKit.framework */,
85 | 66A3E0E0191002AD0094A9B6 /* XCTest.framework */,
86 | );
87 | name = Frameworks;
88 | sourceTree = "";
89 | };
90 | 66A3E0CD191002AD0094A9B6 /* BasicInstantiation */ = {
91 | isa = PBXGroup;
92 | children = (
93 | 66A3E107191008070094A9B6 /* controllers */,
94 | 66A3E0D6191002AD0094A9B6 /* AppDelegate.h */,
95 | 66A3E0D7191002AD0094A9B6 /* AppDelegate.m */,
96 | 66A3E0D9191002AD0094A9B6 /* Images.xcassets */,
97 | 66A3E0CE191002AD0094A9B6 /* Supporting Files */,
98 | );
99 | path = BasicInstantiation;
100 | sourceTree = "";
101 | };
102 | 66A3E0CE191002AD0094A9B6 /* Supporting Files */ = {
103 | isa = PBXGroup;
104 | children = (
105 | 66A3E0CF191002AD0094A9B6 /* BasicInstantiation-Info.plist */,
106 | 66A3E0D0191002AD0094A9B6 /* InfoPlist.strings */,
107 | 66A3E0D3191002AD0094A9B6 /* main.m */,
108 | 66A3E0D5191002AD0094A9B6 /* BasicInstantiation-Prefix.pch */,
109 | );
110 | name = "Supporting Files";
111 | sourceTree = "";
112 | };
113 | 66A3E0F6191002CD0094A9B6 /* attributedlabel */ = {
114 | isa = PBXGroup;
115 | children = (
116 | 66A3E0F7191002E50094A9B6 /* NIAttributedLabel.h */,
117 | 66A3E0F8191002E50094A9B6 /* NIAttributedLabel.m */,
118 | 66A3E0F9191002E50094A9B6 /* NimbusKitAttributedLabel.h */,
119 | 66A3E0FA191002E50094A9B6 /* NSMutableAttributedString+NimbusKitAttributedLabel.h */,
120 | 66A3E0FB191002E50094A9B6 /* NSMutableAttributedString+NimbusKitAttributedLabel.m */,
121 | 66A3E0FE191002F70094A9B6 /* NimbusKitBasics.h */,
122 | );
123 | name = attributedlabel;
124 | path = ../../src;
125 | sourceTree = "";
126 | };
127 | 66A3E107191008070094A9B6 /* controllers */ = {
128 | isa = PBXGroup;
129 | children = (
130 | 66A3E108191008260094A9B6 /* BasicInstantiationViewController.h */,
131 | 66A3E109191008260094A9B6 /* BasicInstantiationViewController.m */,
132 | );
133 | path = controllers;
134 | sourceTree = "";
135 | };
136 | /* End PBXGroup section */
137 |
138 | /* Begin PBXNativeTarget section */
139 | 66A3E0C3191002AD0094A9B6 /* BasicInstantiation */ = {
140 | isa = PBXNativeTarget;
141 | buildConfigurationList = 66A3E0F0191002AD0094A9B6 /* Build configuration list for PBXNativeTarget "BasicInstantiation" */;
142 | buildPhases = (
143 | 66A3E0C0191002AD0094A9B6 /* Sources */,
144 | 66A3E0C1191002AD0094A9B6 /* Frameworks */,
145 | 66A3E0C2191002AD0094A9B6 /* Resources */,
146 | );
147 | buildRules = (
148 | );
149 | dependencies = (
150 | );
151 | name = BasicInstantiation;
152 | productName = BasicInstantiation;
153 | productReference = 66A3E0C4191002AD0094A9B6 /* BasicInstantiation.app */;
154 | productType = "com.apple.product-type.application";
155 | };
156 | /* End PBXNativeTarget section */
157 |
158 | /* Begin PBXProject section */
159 | 66A3E0BC191002AD0094A9B6 /* Project object */ = {
160 | isa = PBXProject;
161 | attributes = {
162 | LastUpgradeCheck = 0510;
163 | ORGANIZATIONNAME = NimbusKit;
164 | };
165 | buildConfigurationList = 66A3E0BF191002AD0094A9B6 /* Build configuration list for PBXProject "BasicInstantiation" */;
166 | compatibilityVersion = "Xcode 3.2";
167 | developmentRegion = English;
168 | hasScannedForEncodings = 0;
169 | knownRegions = (
170 | en,
171 | );
172 | mainGroup = 66A3E0BB191002AD0094A9B6;
173 | productRefGroup = 66A3E0C5191002AD0094A9B6 /* Products */;
174 | projectDirPath = "";
175 | projectRoot = "";
176 | targets = (
177 | 66A3E0C3191002AD0094A9B6 /* BasicInstantiation */,
178 | );
179 | };
180 | /* End PBXProject section */
181 |
182 | /* Begin PBXResourcesBuildPhase section */
183 | 66A3E0C2191002AD0094A9B6 /* Resources */ = {
184 | isa = PBXResourcesBuildPhase;
185 | buildActionMask = 2147483647;
186 | files = (
187 | 66A3E0D2191002AD0094A9B6 /* InfoPlist.strings in Resources */,
188 | 66A3E0DA191002AD0094A9B6 /* Images.xcassets in Resources */,
189 | );
190 | runOnlyForDeploymentPostprocessing = 0;
191 | };
192 | /* End PBXResourcesBuildPhase section */
193 |
194 | /* Begin PBXSourcesBuildPhase section */
195 | 66A3E0C0191002AD0094A9B6 /* Sources */ = {
196 | isa = PBXSourcesBuildPhase;
197 | buildActionMask = 2147483647;
198 | files = (
199 | 66A3E0D8191002AD0094A9B6 /* AppDelegate.m in Sources */,
200 | 66A3E0D4191002AD0094A9B6 /* main.m in Sources */,
201 | 66A3E10A191008260094A9B6 /* BasicInstantiationViewController.m in Sources */,
202 | 66A3E0FC191002E50094A9B6 /* NIAttributedLabel.m in Sources */,
203 | 66A3E0FD191002E50094A9B6 /* NSMutableAttributedString+NimbusKitAttributedLabel.m in Sources */,
204 | );
205 | runOnlyForDeploymentPostprocessing = 0;
206 | };
207 | /* End PBXSourcesBuildPhase section */
208 |
209 | /* Begin PBXVariantGroup section */
210 | 66A3E0D0191002AD0094A9B6 /* InfoPlist.strings */ = {
211 | isa = PBXVariantGroup;
212 | children = (
213 | 66A3E0D1191002AD0094A9B6 /* en */,
214 | );
215 | name = InfoPlist.strings;
216 | sourceTree = "";
217 | };
218 | /* End PBXVariantGroup section */
219 |
220 | /* Begin XCBuildConfiguration section */
221 | 66A3E0EE191002AD0094A9B6 /* Debug */ = {
222 | isa = XCBuildConfiguration;
223 | buildSettings = {
224 | ALWAYS_SEARCH_USER_PATHS = NO;
225 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
226 | CLANG_CXX_LIBRARY = "libc++";
227 | CLANG_ENABLE_MODULES = YES;
228 | CLANG_ENABLE_OBJC_ARC = YES;
229 | CLANG_WARN_BOOL_CONVERSION = YES;
230 | CLANG_WARN_CONSTANT_CONVERSION = YES;
231 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
232 | CLANG_WARN_EMPTY_BODY = YES;
233 | CLANG_WARN_ENUM_CONVERSION = YES;
234 | CLANG_WARN_INT_CONVERSION = YES;
235 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
236 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
237 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
238 | COPY_PHASE_STRIP = NO;
239 | GCC_C_LANGUAGE_STANDARD = gnu99;
240 | GCC_DYNAMIC_NO_PIC = NO;
241 | GCC_OPTIMIZATION_LEVEL = 0;
242 | GCC_PREPROCESSOR_DEFINITIONS = (
243 | "DEBUG=1",
244 | "$(inherited)",
245 | );
246 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
247 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
248 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
249 | GCC_WARN_UNDECLARED_SELECTOR = YES;
250 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
251 | GCC_WARN_UNUSED_FUNCTION = YES;
252 | GCC_WARN_UNUSED_VARIABLE = YES;
253 | IPHONEOS_DEPLOYMENT_TARGET = 7.1;
254 | ONLY_ACTIVE_ARCH = YES;
255 | SDKROOT = iphoneos;
256 | };
257 | name = Debug;
258 | };
259 | 66A3E0EF191002AD0094A9B6 /* Release */ = {
260 | isa = XCBuildConfiguration;
261 | buildSettings = {
262 | ALWAYS_SEARCH_USER_PATHS = NO;
263 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
264 | CLANG_CXX_LIBRARY = "libc++";
265 | CLANG_ENABLE_MODULES = YES;
266 | CLANG_ENABLE_OBJC_ARC = YES;
267 | CLANG_WARN_BOOL_CONVERSION = YES;
268 | CLANG_WARN_CONSTANT_CONVERSION = YES;
269 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
270 | CLANG_WARN_EMPTY_BODY = YES;
271 | CLANG_WARN_ENUM_CONVERSION = YES;
272 | CLANG_WARN_INT_CONVERSION = YES;
273 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
274 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
275 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
276 | COPY_PHASE_STRIP = YES;
277 | ENABLE_NS_ASSERTIONS = NO;
278 | GCC_C_LANGUAGE_STANDARD = gnu99;
279 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
280 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
281 | GCC_WARN_UNDECLARED_SELECTOR = YES;
282 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
283 | GCC_WARN_UNUSED_FUNCTION = YES;
284 | GCC_WARN_UNUSED_VARIABLE = YES;
285 | IPHONEOS_DEPLOYMENT_TARGET = 7.1;
286 | SDKROOT = iphoneos;
287 | VALIDATE_PRODUCT = YES;
288 | };
289 | name = Release;
290 | };
291 | 66A3E0F1191002AD0094A9B6 /* Debug */ = {
292 | isa = XCBuildConfiguration;
293 | buildSettings = {
294 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
295 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
296 | GCC_PRECOMPILE_PREFIX_HEADER = YES;
297 | GCC_PREFIX_HEADER = "BasicInstantiation/BasicInstantiation-Prefix.pch";
298 | INFOPLIST_FILE = "BasicInstantiation/BasicInstantiation-Info.plist";
299 | PRODUCT_NAME = "$(TARGET_NAME)";
300 | WRAPPER_EXTENSION = app;
301 | };
302 | name = Debug;
303 | };
304 | 66A3E0F2191002AD0094A9B6 /* Release */ = {
305 | isa = XCBuildConfiguration;
306 | buildSettings = {
307 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
308 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
309 | GCC_PRECOMPILE_PREFIX_HEADER = YES;
310 | GCC_PREFIX_HEADER = "BasicInstantiation/BasicInstantiation-Prefix.pch";
311 | INFOPLIST_FILE = "BasicInstantiation/BasicInstantiation-Info.plist";
312 | PRODUCT_NAME = "$(TARGET_NAME)";
313 | WRAPPER_EXTENSION = app;
314 | };
315 | name = Release;
316 | };
317 | /* End XCBuildConfiguration section */
318 |
319 | /* Begin XCConfigurationList section */
320 | 66A3E0BF191002AD0094A9B6 /* Build configuration list for PBXProject "BasicInstantiation" */ = {
321 | isa = XCConfigurationList;
322 | buildConfigurations = (
323 | 66A3E0EE191002AD0094A9B6 /* Debug */,
324 | 66A3E0EF191002AD0094A9B6 /* Release */,
325 | );
326 | defaultConfigurationIsVisible = 0;
327 | defaultConfigurationName = Release;
328 | };
329 | 66A3E0F0191002AD0094A9B6 /* Build configuration list for PBXNativeTarget "BasicInstantiation" */ = {
330 | isa = XCConfigurationList;
331 | buildConfigurations = (
332 | 66A3E0F1191002AD0094A9B6 /* Debug */,
333 | 66A3E0F2191002AD0094A9B6 /* Release */,
334 | );
335 | defaultConfigurationIsVisible = 0;
336 | defaultConfigurationName = Release;
337 | };
338 | /* End XCConfigurationList section */
339 | };
340 | rootObject = 66A3E0BC191002AD0094A9B6 /* Project object */;
341 | }
342 |
--------------------------------------------------------------------------------
/catalog/BasicInstantiation/BasicInstantiation/AppDelegate.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2011-present, NimbusKit. All rights reserved.
3 |
4 | This source code is licensed under the BSD-style license found in the LICENSE file in the root
5 | directory of this source tree and at the http://nimbuskit.info/license url. An additional grant of
6 | patent rights can be found in the PATENTS file in the same directory and url.
7 | */
8 |
9 | #import
10 |
11 | @interface AppDelegate : UIResponder
12 |
13 | @property (strong, nonatomic) UIWindow *window;
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/catalog/BasicInstantiation/BasicInstantiation/AppDelegate.m:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2011-present, NimbusKit. All rights reserved.
3 |
4 | This source code is licensed under the BSD-style license found in the LICENSE file in the root
5 | directory of this source tree and at the http://nimbuskit.info/license url. An additional grant of
6 | patent rights can be found in the PATENTS file in the same directory and url.
7 | */
8 |
9 | #import "AppDelegate.h"
10 |
11 | #import "BasicInstantiationViewController.h"
12 |
13 | @implementation AppDelegate
14 |
15 | #pragma mark - Standard Scaffolding
16 |
17 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
18 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
19 | self.window.backgroundColor = [UIColor whiteColor];
20 |
21 | self.window.rootViewController = [BasicInstantiationViewController new];
22 |
23 | [self.window makeKeyAndVisible];
24 | return YES;
25 | }
26 |
27 | @end
28 |
--------------------------------------------------------------------------------
/catalog/BasicInstantiation/BasicInstantiation/BasicInstantiation-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | ${PRODUCT_NAME}
9 | CFBundleExecutable
10 | ${EXECUTABLE_NAME}
11 | CFBundleIdentifier
12 | com.nimbuskit.${PRODUCT_NAME:rfc1034identifier}
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | ${PRODUCT_NAME}
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1.0
25 | LSRequiresIPhoneOS
26 |
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/catalog/BasicInstantiation/BasicInstantiation/BasicInstantiation-Prefix.pch:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2011-present, NimbusKit. All rights reserved.
3 |
4 | This source code is licensed under the BSD-style license found in the LICENSE file in the root
5 | directory of this source tree and at the http://nimbuskit.info/license url. An additional grant of
6 | patent rights can be found in the PATENTS file in the same directory and url.
7 | */
8 |
9 | #import
10 |
11 | #ifndef __IPHONE_3_0
12 | #warning "This project uses features only available in iOS SDK 3.0 and later."
13 | #endif
14 |
15 | #ifdef __OBJC__
16 | #import
17 | #import
18 | #endif
19 |
--------------------------------------------------------------------------------
/catalog/BasicInstantiation/BasicInstantiation/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "40x40",
11 | "scale" : "2x"
12 | },
13 | {
14 | "size" : "57x57",
15 | "idiom" : "iphone",
16 | "filename" : "Icon.png",
17 | "scale" : "1x"
18 | },
19 | {
20 | "size" : "57x57",
21 | "idiom" : "iphone",
22 | "filename" : "Icon@2x.png",
23 | "scale" : "2x"
24 | },
25 | {
26 | "size" : "60x60",
27 | "idiom" : "iphone",
28 | "filename" : "Icon-iOS7@2x.png",
29 | "scale" : "2x"
30 | }
31 | ],
32 | "info" : {
33 | "version" : 1,
34 | "author" : "xcode"
35 | },
36 | "properties" : {
37 | "pre-rendered" : true
38 | }
39 | }
--------------------------------------------------------------------------------
/catalog/BasicInstantiation/BasicInstantiation/Images.xcassets/AppIcon.appiconset/Icon-iOS7@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NimbusKit/attributedlabel/400da853812734d3aa986d57c6e7305d0bf946de/catalog/BasicInstantiation/BasicInstantiation/Images.xcassets/AppIcon.appiconset/Icon-iOS7@2x.png
--------------------------------------------------------------------------------
/catalog/BasicInstantiation/BasicInstantiation/Images.xcassets/AppIcon.appiconset/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NimbusKit/attributedlabel/400da853812734d3aa986d57c6e7305d0bf946de/catalog/BasicInstantiation/BasicInstantiation/Images.xcassets/AppIcon.appiconset/Icon.png
--------------------------------------------------------------------------------
/catalog/BasicInstantiation/BasicInstantiation/Images.xcassets/AppIcon.appiconset/Icon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NimbusKit/attributedlabel/400da853812734d3aa986d57c6e7305d0bf946de/catalog/BasicInstantiation/BasicInstantiation/Images.xcassets/AppIcon.appiconset/Icon@2x.png
--------------------------------------------------------------------------------
/catalog/BasicInstantiation/BasicInstantiation/Images.xcassets/LaunchImage.launchimage/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "orientation" : "portrait",
5 | "idiom" : "iphone",
6 | "extent" : "full-screen",
7 | "minimum-system-version" : "7.0",
8 | "filename" : "Default-iOS7-flat@2x.png",
9 | "scale" : "2x"
10 | },
11 | {
12 | "extent" : "full-screen",
13 | "idiom" : "iphone",
14 | "subtype" : "retina4",
15 | "filename" : "Default-568h-iOS7-flat@2x-1.png",
16 | "minimum-system-version" : "7.0",
17 | "orientation" : "portrait",
18 | "scale" : "2x"
19 | },
20 | {
21 | "orientation" : "portrait",
22 | "idiom" : "iphone",
23 | "extent" : "full-screen",
24 | "filename" : "Default-flat.png",
25 | "scale" : "1x"
26 | },
27 | {
28 | "orientation" : "portrait",
29 | "idiom" : "iphone",
30 | "extent" : "full-screen",
31 | "filename" : "Default-568h-iOS7-flat@2x.png",
32 | "subtype" : "retina4",
33 | "scale" : "2x"
34 | }
35 | ],
36 | "info" : {
37 | "version" : 1,
38 | "author" : "xcode"
39 | }
40 | }
--------------------------------------------------------------------------------
/catalog/BasicInstantiation/BasicInstantiation/Images.xcassets/LaunchImage.launchimage/Default-568h-iOS7-flat@2x-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NimbusKit/attributedlabel/400da853812734d3aa986d57c6e7305d0bf946de/catalog/BasicInstantiation/BasicInstantiation/Images.xcassets/LaunchImage.launchimage/Default-568h-iOS7-flat@2x-1.png
--------------------------------------------------------------------------------
/catalog/BasicInstantiation/BasicInstantiation/Images.xcassets/LaunchImage.launchimage/Default-568h-iOS7-flat@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NimbusKit/attributedlabel/400da853812734d3aa986d57c6e7305d0bf946de/catalog/BasicInstantiation/BasicInstantiation/Images.xcassets/LaunchImage.launchimage/Default-568h-iOS7-flat@2x.png
--------------------------------------------------------------------------------
/catalog/BasicInstantiation/BasicInstantiation/Images.xcassets/LaunchImage.launchimage/Default-flat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NimbusKit/attributedlabel/400da853812734d3aa986d57c6e7305d0bf946de/catalog/BasicInstantiation/BasicInstantiation/Images.xcassets/LaunchImage.launchimage/Default-flat.png
--------------------------------------------------------------------------------
/catalog/BasicInstantiation/BasicInstantiation/Images.xcassets/LaunchImage.launchimage/Default-iOS7-flat@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NimbusKit/attributedlabel/400da853812734d3aa986d57c6e7305d0bf946de/catalog/BasicInstantiation/BasicInstantiation/Images.xcassets/LaunchImage.launchimage/Default-iOS7-flat@2x.png
--------------------------------------------------------------------------------
/catalog/BasicInstantiation/BasicInstantiation/controllers/BasicInstantiationViewController.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2011-present, NimbusKit. All rights reserved.
3 |
4 | This source code is licensed under the BSD-style license found in the LICENSE file in the root
5 | directory of this source tree and at the http://nimbuskit.info/license url. An additional grant of
6 | patent rights can be found in the PATENTS file in the same directory and url.
7 | */
8 |
9 | #import
10 |
11 | @interface BasicInstantiationViewController : UIViewController
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/catalog/BasicInstantiation/BasicInstantiation/controllers/BasicInstantiationViewController.m:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2011-present, NimbusKit. All rights reserved.
3 |
4 | This source code is licensed under the BSD-style license found in the LICENSE file in the root
5 | directory of this source tree and at the http://nimbuskit.info/license url. An additional grant of
6 | patent rights can be found in the PATENTS file in the same directory and url.
7 | */
8 |
9 | #import "BasicInstantiationViewController.h"
10 |
11 | // Using the library import, rather than directly importing, has some nice advantages:
12 | //
13 | // - We don't have to import any dependent framework headers.
14 | // - Any file movement within the library happens transparently to us.
15 | //
16 | #import "NimbusKitAttributedLabel.h"
17 |
18 | @interface BasicInstantiationViewController ()
19 | @end
20 |
21 | @implementation BasicInstantiationViewController
22 |
23 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
24 | if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) {
25 | self.title = @"Basic Instantiation";
26 | }
27 | return self;
28 | }
29 |
30 | - (void)viewDidLoad {
31 | [super viewDidLoad];
32 |
33 | // Standard instantiation
34 | {
35 | NIAttributedLabel* label = [NIAttributedLabel new];
36 | label.text = @"NimbusKit 2.0";
37 |
38 | // Standard style properties immediately apply to the label as a whole.
39 | label.font = [UIFont systemFontOfSize:24];
40 |
41 | // NIAttributedLabel provides a number of convenience methods to modify attributes for ranges of
42 | // the label's text.
43 | [label setFont:[UIFont boldSystemFontOfSize:24] range:NSMakeRange(0, @"Nimbus".length)];
44 | [label setTextColor:[UIColor orangeColor] range:NSMakeRange(0, @"NimbusKit".length)];
45 |
46 | label.frame = (CGRect){CGPointMake(20, 50), CGSizeZero};
47 |
48 | [label sizeToFit];
49 |
50 | [self.view addSubview:label];
51 | }
52 |
53 | // Inline images
54 | {
55 | NIAttributedLabel* label = [NIAttributedLabel new];
56 | label.text = @"NimbusKit 2.0";
57 | label.font = [UIFont systemFontOfSize:24];
58 |
59 | [label insertImage:[UIImage imageNamed:@"AppIcon60x60"] atIndex:@"NimbusKit".length
60 | margins:UIEdgeInsetsZero verticalTextAlignment:NIVerticalTextAlignmentMiddle];
61 |
62 | label.frame = (CGRect){CGPointMake(20, 80), CGSizeZero};
63 |
64 | [label sizeToFit];
65 |
66 | [self.view addSubview:label];
67 | }
68 |
69 | // Links
70 | {
71 | NIAttributedLabel* label = [NIAttributedLabel new];
72 | label.text = @"NimbusKit 2.0";
73 | label.font = [UIFont systemFontOfSize:24];
74 |
75 | [label addLink:[NSURL URLWithString:@"http://nimbuskit.info"] range:NSMakeRange(0, label.text.length)];
76 | label.delegate = self;
77 |
78 | label.frame = (CGRect){CGPointMake(20, 150), CGSizeZero};
79 |
80 | [label sizeToFit];
81 |
82 | [self.view addSubview:label];
83 | }
84 | }
85 |
86 | #pragma mark - NIAttributedLabelDelegate
87 |
88 | - (void)attributedLabel:(NIAttributedLabel *)attributedLabel didSelectTextCheckingResult:(NSTextCheckingResult *)result atPoint:(CGPoint)point {
89 | if (result.resultType == NSTextCheckingTypeLink) {
90 | [[UIApplication sharedApplication] openURL:result.URL];
91 | }
92 | }
93 |
94 | @end
95 |
--------------------------------------------------------------------------------
/catalog/BasicInstantiation/BasicInstantiation/en.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /* Localized versions of Info.plist keys */
2 |
3 |
--------------------------------------------------------------------------------
/catalog/BasicInstantiation/BasicInstantiation/main.m:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2011-present, NimbusKit. All rights reserved.
3 |
4 | This source code is licensed under the BSD-style license found in the LICENSE file in the root
5 | directory of this source tree and at the http://nimbuskit.info/license url. An additional grant of
6 | patent rights can be found in the PATENTS file in the same directory and url.
7 | */
8 |
9 | #import
10 |
11 | #import "AppDelegate.h"
12 |
13 | int main(int argc, char * argv[]) {
14 | @autoreleasepool {
15 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/docs/gfx/NIAttributedLabelExample1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NimbusKit/attributedlabel/400da853812734d3aa986d57c6e7305d0bf946de/docs/gfx/NIAttributedLabelExample1.png
--------------------------------------------------------------------------------
/docs/gfx/NIAttributedLabelExample2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NimbusKit/attributedlabel/400da853812734d3aa986d57c6e7305d0bf946de/docs/gfx/NIAttributedLabelExample2.png
--------------------------------------------------------------------------------
/docs/gfx/NIAttributedLabelExample3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NimbusKit/attributedlabel/400da853812734d3aa986d57c6e7305d0bf946de/docs/gfx/NIAttributedLabelExample3.png
--------------------------------------------------------------------------------
/docs/gfx/NIAttributedLabelExample4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NimbusKit/attributedlabel/400da853812734d3aa986d57c6e7305d0bf946de/docs/gfx/NIAttributedLabelExample4.png
--------------------------------------------------------------------------------
/docs/gfx/NIAttributedLabelExample5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NimbusKit/attributedlabel/400da853812734d3aa986d57c6e7305d0bf946de/docs/gfx/NIAttributedLabelExample5.png
--------------------------------------------------------------------------------
/docs/gfx/NIAttributedLabelExample6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NimbusKit/attributedlabel/400da853812734d3aa986d57c6e7305d0bf946de/docs/gfx/NIAttributedLabelExample6.png
--------------------------------------------------------------------------------
/docs/gfx/NIAttributedLabelIB.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NimbusKit/attributedlabel/400da853812734d3aa986d57c6e7305d0bf946de/docs/gfx/NIAttributedLabelIB.png
--------------------------------------------------------------------------------
/docs/gfx/NIAttributedLabelLinkAttributes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NimbusKit/attributedlabel/400da853812734d3aa986d57c6e7305d0bf946de/docs/gfx/NIAttributedLabelLinkAttributes.png
--------------------------------------------------------------------------------
/docs/gfx/NIAttributedLabel_autoDetectLinksOff.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NimbusKit/attributedlabel/400da853812734d3aa986d57c6e7305d0bf946de/docs/gfx/NIAttributedLabel_autoDetectLinksOff.png
--------------------------------------------------------------------------------
/docs/gfx/NIAttributedLabel_autoDetectLinksOn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NimbusKit/attributedlabel/400da853812734d3aa986d57c6e7305d0bf946de/docs/gfx/NIAttributedLabel_autoDetectLinksOn.png
--------------------------------------------------------------------------------
/docs/gfx/NIAttributedLabel_inlineimages.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NimbusKit/attributedlabel/400da853812734d3aa986d57c6e7305d0bf946de/docs/gfx/NIAttributedLabel_inlineimages.png
--------------------------------------------------------------------------------
/docs/gfx/banner.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NimbusKit/attributedlabel/400da853812734d3aa986d57c6e7305d0bf946de/docs/gfx/banner.gif
--------------------------------------------------------------------------------
/src/NIAttributedLabel.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2011-present, NimbusKit. All rights reserved.
3 | Originally created by Roger Chapman
4 |
5 | This source code is licensed under the BSD-style license found in the LICENSE file in the root
6 | directory of this source tree and at the http://nimbuskit.info/license url. An additional grant of
7 | patent rights can be found in the PATENTS file in the same directory and url.
8 | */
9 |
10 | #import
11 | #import
12 | #import
13 |
14 | #if defined __cplusplus
15 | extern "C" {
16 | #endif
17 |
18 | /**
19 | * Calculates the ideal dimensions of an attributed string fitting a given size.
20 | *
21 | * This calculation is performed off the raw attributed string so this calculation may differ
22 | * slightly from NIAttributedLabel's use of it due to lack of link and image attributes.
23 | *
24 | * This method is used in NIAttributedLabel to calculate its size after all additional
25 | * styling attributes have been set.
26 | */
27 | CGSize NISizeOfAttributedStringConstrainedToSize(NSAttributedString* attributedString, CGSize size, NSInteger numberOfLines);
28 |
29 | #if defined __cplusplus
30 | };
31 | #endif
32 |
33 | // Vertical alignments for NIAttributedLabel.
34 | typedef enum {
35 | NIVerticalTextAlignmentTop = 0,
36 | NIVerticalTextAlignmentMiddle,
37 | NIVerticalTextAlignmentBottom,
38 | } NIVerticalTextAlignment;
39 |
40 | extern NSString* const NIAttributedLabelLinkAttributeName; // Value is an NSTextCheckingResult.
41 |
42 | @protocol NIAttributedLabelDelegate;
43 |
44 | /**
45 | * The NIAttributedLabel class provides support for displaying rich text with selectable links and
46 | * embedded images.
47 | *
48 | * Differences between UILabel and NIAttributedLabel:
49 | *
50 | * - @c NSLineBreakByTruncatingHead and @c NSLineBreakByTruncatingMiddle only apply to single
51 | * lines and will not wrap the label regardless of the @c numberOfLines property. To wrap lines
52 | * with any of these line break modes you must explicitly add newline characters to the string.
53 | * - When you assign an NSString to the text property the attributed label will create an
54 | * attributed string that inherits all of the label's current styles.
55 | * - Text is aligned vertically to the top of the bounds by default rather than centered. You can
56 | * change this behavior using @link NIAttributedLabel::verticalTextAlignment verticalTextAlignment@endlink.
57 | * - CoreText fills the frame with glyphs until they no longer fit. This is an important difference
58 | * from UILabel because it means that CoreText will not add any glyphs that won't fit in the
59 | * frame, while UILabel does. This can result in empty NIAttributedLabels if your frame is too
60 | * small where UILabel would draw clipped text. It is recommended that you use sizeToFit to get
61 | * the correct dimensions of the attributed label before setting the frame.
62 | *
63 | * NIAttributedLabel implements the UIAccessibilityContainer methods to expose each link as an
64 | * accessibility item.
65 | *
66 | * @ingroup NimbusKitAttributedLabel
67 | */
68 | @interface NIAttributedLabel : UILabel
69 |
70 | @property (nonatomic) BOOL autoDetectLinks; // Default: NO
71 | @property (nonatomic) NSTextCheckingType dataDetectorTypes; // Default: NSTextCheckingTypeLink
72 | @property (nonatomic) BOOL deferLinkDetection; // Default: NO
73 |
74 | - (void)addLink:(NSURL *)urlLink range:(NSRange)range;
75 | - (void)removeAllExplicitLinks; // Removes all links that were added by addLink:range:. Does not remove autodetected links.
76 |
77 | @property (nonatomic, strong) UIColor* linkColor; // Default: self.tintColor (iOS 7) or [UIColor blueColor] (iOS 6)
78 | @property (nonatomic, strong) UIColor* highlightedLinkBackgroundColor; // Default: [UIColor colorWithWhite:0.5 alpha:0.5
79 | @property (nonatomic) BOOL linksHaveUnderlines; // Default: NO
80 | @property (nonatomic, copy) NSDictionary* attributesForLinks; // Default: nil
81 | @property (nonatomic, copy) NSDictionary* attributesForHighlightedLink; // Default: nil
82 | @property (nonatomic) CGFloat lineHeight;
83 |
84 | @property (nonatomic) NIVerticalTextAlignment verticalTextAlignment; // Default: NIVerticalTextAlignmentTop
85 | @property (nonatomic) CTUnderlineStyle underlineStyle;
86 | @property (nonatomic) CTUnderlineStyleModifiers underlineStyleModifier;
87 | @property (nonatomic) CGFloat shadowBlur; // Default: 0
88 | @property (nonatomic) CGFloat strokeWidth;
89 | @property (nonatomic, strong) UIColor* strokeColor;
90 | @property (nonatomic) CGFloat textKern;
91 |
92 | @property (nonatomic, copy) NSString* tailTruncationString;
93 |
94 | @property (nonatomic) BOOL shouldSortLinksLast; // Sort the links in the text as the last elements in accessible elements. Default: NO
95 |
96 | - (void)setFont:(UIFont *)font range:(NSRange)range;
97 | - (void)setStrokeColor:(UIColor *)color range:(NSRange)range;
98 | - (void)setStrokeWidth:(CGFloat)width range:(NSRange)range;
99 | - (void)setTextColor:(UIColor *)textColor range:(NSRange)range;
100 | - (void)setTextKern:(CGFloat)kern range:(NSRange)range;
101 | - (void)setUnderlineStyle:(CTUnderlineStyle)style modifier:(CTUnderlineStyleModifiers)modifier range:(NSRange)range;
102 |
103 | - (void)insertImage:(UIImage *)image atIndex:(NSInteger)index;
104 | - (void)insertImage:(UIImage *)image atIndex:(NSInteger)index margins:(UIEdgeInsets)margins;
105 | - (void)insertImage:(UIImage *)image atIndex:(NSInteger)index margins:(UIEdgeInsets)margins verticalTextAlignment:(NIVerticalTextAlignment)verticalTextAlignment;
106 |
107 | - (void)invalidateAccessibleElements;
108 |
109 | @property (nonatomic, weak) IBOutlet id delegate;
110 | @end
111 |
112 | /**
113 | * The methods declared by the NIAttributedLabelDelegate protocol allow the adopting delegate to
114 | * respond to messages from the NIAttributedLabel class and thus respond to selections.
115 | *
116 | * @ingroup NimbusKitAttributedLabel
117 | */
118 | @protocol NIAttributedLabelDelegate
119 | @optional
120 |
121 | /** @name Managing Selections */
122 |
123 | /**
124 | * Informs the receiver that a data detector result has been selected.
125 | *
126 | * @param attributedLabel An attributed label informing the receiver of the selection.
127 | * @param result The data detector result that was selected.
128 | * @param point The point within @c attributedLabel where the result was tapped.
129 | */
130 | - (void)attributedLabel:(NIAttributedLabel *)attributedLabel didSelectTextCheckingResult:(NSTextCheckingResult *)result atPoint:(CGPoint)point;
131 |
132 | /**
133 | * Asks the receiver whether an action sheet should be displayed at the given point.
134 | *
135 | * If this method is not implemented by the receiver then @c actionSheet will always be displayed.
136 | *
137 | * @c actionSheet will be populated with actions that match the data type that was selected. For
138 | * example, a link will have the actions "Open in Safari" and "Copy URL". A phone number will have
139 | * @"Call" and "Copy Phone Number".
140 | *
141 | * @param attributedLabel An attributed label asking the delegate whether to display the action
142 | * sheet.
143 | * @param actionSheet The action sheet that will be displayed if YES is returned.
144 | * @param result The data detector result that was selected.
145 | * @param point The point within @c attributedLabel where the result was tapped.
146 | * @returns YES if @c actionSheet should be displayed. NO if @c actionSheet should not be
147 | * displayed.
148 | */
149 | - (BOOL)attributedLabel:(NIAttributedLabel *)attributedLabel shouldPresentActionSheet:(UIActionSheet *)actionSheet withTextCheckingResult:(NSTextCheckingResult *)result atPoint:(CGPoint)point;
150 |
151 | @end
152 |
153 | /** @name Accessing and Detecting Links */
154 |
155 | /**
156 | * A Booelan value indicating whether to automatically detect links in the string.
157 | *
158 | * By default this is disabled.
159 | *
160 | * Link detection is deferred until the label is displayed for the first time. If the text changes
161 | * then all of the links will be cleared and re-detected when the label displays again.
162 | *
163 | * Note that link detection is an expensive operation. If you are planning to use attributed labels
164 | * in table views or similar high-performance situations then you should consider enabling defered
165 | * link detection by setting @link NIAttributedLabel::deferLinkDetection deferLinkDetection@endlink
166 | * to YES.
167 | *
168 | * @sa NIAttributedLabel::dataDetectorTypes
169 | * @sa NIAttributedLabel::deferLinkDetection
170 | * @fn NIAttributedLabel::autoDetectLinks
171 | */
172 |
173 | /**
174 | * A Boolean value indicating whether to defer link detection to a separate thread.
175 | *
176 | * By default this is disabled.
177 | *
178 | * When defering is enabled, link detection will be performed on a separate thread. This will cause
179 | * your label to appear without any links briefly before being redrawn with the detected links.
180 | * This offloads the data detection to a separate thread so that your labels can be displayed
181 | * faster.
182 | *
183 | * @fn NIAttributedLabel::deferLinkDetection
184 | */
185 |
186 | /**
187 | * The types of data that will be detected when
188 | * @link NIAttributedLabel::autoDetectLinks autoDetectLinks@endlink is enabled.
189 | *
190 | * By default this is NSTextCheckingTypeLink. All available data detector types.
191 | *
192 | * @fn NIAttributedLabel::dataDetectorTypes
193 | */
194 |
195 | /**
196 | * Adds a link to a URL at a given range.
197 | *
198 | * Adding any links will immediately enable user interaction on this label. Explicitly added
199 | * links are removed whenever the text changes.
200 | *
201 | * @fn NIAttributedLabel::addLink:range:
202 | */
203 |
204 | /**
205 | * Removes all explicit links from the label.
206 | *
207 | * If you wish to remove automatically-detected links, set autoDetectLinks to NO.
208 | *
209 | * @fn NIAttributedLabel::removeAllExplicitLinks
210 | */
211 |
212 | /** @name Accessing Link Display Styles */
213 |
214 | /**
215 | * The text color of detected links.
216 | *
217 | * The default color is [UIColor blueColor] on pre-iOS 7 devices or self.tintColor on iOS 7 devices.
218 | * If linkColor is assigned nil then links will not be given any special color. Use
219 | * attributesForLinks to specify alternative styling.
220 | *
221 | * @image html NIAttributedLabelLinkAttributes.png "Link attributes"
222 | *
223 | * @fn NIAttributedLabel::linkColor
224 | */
225 |
226 | /**
227 | * The background color of the link's selection frame when the user is touching the link.
228 | *
229 | * The default is [UIColor colorWithWhite:0.5 alpha:0.5].
230 | *
231 | * If you do not want links to be highlighted when touched, set this to nil.
232 | *
233 | * @image html NIAttributedLabelLinkAttributes.png "Link attributes"
234 | *
235 | * @fn NIAttributedLabel::highlightedLinkBackgroundColor
236 | */
237 |
238 | /**
239 | * A Boolean value indicating whether links should have underlines.
240 | *
241 | * By default this is disabled.
242 | *
243 | * This affects all links in the label.
244 | *
245 | * @fn NIAttributedLabel::linksHaveUnderlines
246 | */
247 |
248 | /**
249 | * A dictionary of CoreText attributes to apply to links.
250 | *
251 | * This dictionary must contain CoreText attributes. These attributes are applied after the color
252 | * and underline styles have been applied to the link.
253 | *
254 | * @fn NIAttributedLabel::attributesForLinks
255 | */
256 |
257 | /**
258 | * A dictionary of CoreText attributes to apply to the highlighted link.
259 | *
260 | * This dictionary must contain CoreText attributes. These attributes are applied after
261 | * attributesForLinks have been applied to the highlighted link.
262 | *
263 | * @fn NIAttributedLabel::attributesForHighlightedLink
264 | */
265 |
266 | /** @name Modifying Rich Text Styles for All Text */
267 |
268 | /**
269 | * The vertical alignment of the text within the label's bounds.
270 | *
271 | * The default is @c NIVerticalTextAlignmentTop. This is for performance reasons because the other
272 | * modes require more computation. Aligning to the top is generally what you want anyway.
273 | *
274 | * @c NIVerticalTextAlignmentBottom will align the text to the bottom of the bounds, while
275 | * @c NIVerticalTextAlignmentMiddle will center the text vertically.
276 | *
277 | * @fn NIAttributedLabel::verticalTextAlignment
278 | */
279 |
280 | /**
281 | * The underline style for the entire label.
282 | *
283 | * By default this is @c kCTUnderlineStyleNone.
284 | *
285 | * View all available styles.
286 | *
287 | * @fn NIAttributedLabel::underlineStyle
288 | */
289 |
290 | /**
291 | * The underline style modifier for the entire label.
292 | *
293 | * By default this is @c kCTUnderlinePatternSolid.
294 | *
295 | * View all available style
296 | * modifiers.
297 | *
298 | * @fn NIAttributedLabel::underlineStyleModifier
299 | */
300 |
301 | /**
302 | * A non-negative number specifying the amount of blur to apply to the label's text shadow.
303 | *
304 | * By default this is zero.
305 | *
306 | * @fn NIAttributedLabel::shadowBlur
307 | */
308 |
309 | /**
310 | * Sets the stroke width for the text.
311 | *
312 | * By default this is zero.
313 | *
314 | * Positive numbers will draw the stroke. Negative numbers will draw the stroke and fill.
315 | *
316 | * @fn NIAttributedLabel::strokeWidth
317 | */
318 |
319 | /**
320 | * Sets the stroke color for the text.
321 | *
322 | * By default this is nil.
323 | *
324 | * @fn NIAttributedLabel::strokeColor
325 | */
326 |
327 | /**
328 | * Sets the line height for the text.
329 | *
330 | * By default this is zero.
331 | *
332 | * Setting this value to zero will make the label use the default line height for the text's font.
333 | *
334 | * @fn NIAttributedLabel::lineHeight
335 | */
336 |
337 | /**
338 | * Sets the kern for the text.
339 | *
340 | * By default this is zero.
341 | *
342 | * The text kern indicates how many points each character should be shifted from its default offset.
343 | * A positive kern indicates a shift farther away. A negative kern indicates a shift closer.
344 | *
345 | * @fn NIAttributedLabel::textKern
346 | */
347 |
348 | /** @name Modifying Tail Truncation Properties */
349 |
350 | /**
351 | * The string to display for tail truncation.
352 | *
353 | * By default this is nil and the default ellipses character, \u2026, is used.
354 | *
355 | * @fn NIAttributedLabel::tailTruncationString
356 | */
357 |
358 | /** @name Modifying Rich Text Styles in Ranges */
359 |
360 | /**
361 | * Sets the text color for text in a given range.
362 | *
363 | * @fn NIAttributedLabel::setTextColor:range:
364 | */
365 |
366 | /**
367 | * Sets the font for text in a given range.
368 | *
369 | * @fn NIAttributedLabel::setFont:range:
370 | */
371 |
372 | /**
373 | * Sets the underline style and modifier for text in a given range.
374 | *
375 | * View all available styles.
376 | *
377 | * View all available style
378 | * modifiers.
379 | *
380 | * @fn NIAttributedLabel::setUnderlineStyle:modifier:range:
381 | */
382 |
383 | /**
384 | * Sets the stroke width for text in a given range.
385 | *
386 | * Positive numbers will draw the stroke. Negative numbers will draw the stroke and fill.
387 | *
388 | * @fn NIAttributedLabel::setStrokeWidth:range:
389 | */
390 |
391 | /**
392 | * Sets the stroke color for text in a given range.
393 | *
394 | * @fn NIAttributedLabel::setStrokeColor:range:
395 | */
396 |
397 | /**
398 | * Sets the kern for text in a given range.
399 | *
400 | * The text kern indicates how many points each character should be shifted from its default offset.
401 | * A positive kern indicates a shift farther away. A negative kern indicates a shift closer.
402 | *
403 | * @fn NIAttributedLabel::setTextKern:range:
404 | */
405 |
406 | /** @name Adding Inline Images */
407 |
408 | /**
409 | * Inserts the given image inline at the given index in the receiver's text.
410 | *
411 | * The image will have no margins.
412 | * The image's vertical text alignment will be NIVerticalTextAlignmentBottom.
413 | *
414 | * @param image The image to add to the receiver.
415 | * @param index The index into the receiver's text at which to insert the image.
416 | * @fn NIAttributedLabel::insertImage:atIndex:
417 | */
418 |
419 | /**
420 | * Inserts the given image inline at the given index in the receiver's text.
421 | *
422 | * The image's vertical text alignment will be NIVerticalTextAlignmentBottom.
423 | *
424 | * @param image The image to add to the receiver.
425 | * @param index The index into the receiver's text at which to insert the image.
426 | * @param margins The space around the image on all sides in points.
427 | * @fn NIAttributedLabel::insertImage:atIndex:margins:
428 | */
429 |
430 | /**
431 | * Inserts the given image inline at the given index in the receiver's text.
432 | *
433 | * @attention
434 | * Images do not currently support NIVerticalTextAlignmentTop and the receiver will fire
435 | * multiple debug assertions if you attempt to use it.
436 | *
437 | * @param image The image to add to the receiver.
438 | * @param index The index into the receiver's text at which to insert the image.
439 | * @param margins The space around the image on all sides in points.
440 | * @param verticalTextAlignment The position of the text relative to the image.
441 | * @fn NIAttributedLabel::insertImage:atIndex:margins:verticalTextAlignment:
442 | */
443 |
444 | /** @name Accessibility */
445 |
446 | /**
447 | * Invalidates this label's accessible elements.
448 | *
449 | * When a label is contained within another view and that parent view moves, the label will not be
450 | * informed of this change and any existing accessibility elements will still point to the old
451 | * screen location. If this happens you must call -invalidateAccessibleElements in order to force
452 | * the label to refresh its accessibile elements.
453 | *
454 | * @fn NIAttributedLabel::invalidateAccessibleElements
455 | */
456 |
457 | /** @name Accessing the Delegate */
458 |
459 | /**
460 | * The delegate of the attributed-label object.
461 | *
462 | * The delegate must adopt the NIAttributedLabelDelegate protocol. The NIAttributedLabel class,
463 | * which does not strong the delegate, invokes each protocol method the delegate implements.
464 | *
465 | * @fn NIAttributedLabel::delegate
466 | */
467 |
--------------------------------------------------------------------------------
/src/NIAttributedLabel.m:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2011-present, NimbusKit. All rights reserved.
3 | Originally created by Roger Chapman
4 |
5 | This source code is licensed under the BSD-style license found in the LICENSE file in the root
6 | directory of this source tree and at the http://nimbuskit.info/license url. An additional grant of
7 | patent rights can be found in the PATENTS file in the same directory and url.
8 | */
9 |
10 | #import "NIAttributedLabel.h"
11 |
12 | #import "NSMutableAttributedString+NimbusKitAttributedLabel.h"
13 |
14 | #import "NimbusKitBasics.h"
15 | #import
16 |
17 | #if !defined(__has_feature) || !__has_feature(objc_arc)
18 | #error "NimbusKit requires ARC support."
19 | #endif
20 |
21 | #if __IPHONE_OS_VERSION_MIN_REQUIRED < NI_IOS_6_0
22 | #error "NIAttributedLabel requires iOS 6 or higher."
23 | #endif
24 |
25 | // The number of seconds to wait before executing a long press action on the tapped link.
26 | static const NSTimeInterval kLongPressTimeInterval = 0.5;
27 |
28 | // The number of pixels the user's finger must move before cancelling the long press timer.
29 | static const CGFloat kLongPressGutter = 22;
30 |
31 | // The touch gutter is the amount of space around a link that will still register as tapping
32 | // "within" the link.
33 | static const CGFloat kTouchGutter = 22;
34 |
35 | static const CGFloat kVMargin = 5.0f;
36 |
37 | // \u2026 is the Unicode horizontal ellipsis character code
38 | static NSString* const kEllipsesCharacter = @"\u2026";
39 |
40 | NSString* const NIAttributedLabelLinkAttributeName = @"NIAttributedLabel:Link";
41 |
42 | // For supporting images.
43 | CGFloat NIImageDelegateGetAscentCallback(void* refCon);
44 | CGFloat NIImageDelegateGetDescentCallback(void* refCon);
45 | CGFloat NIImageDelegateGetWidthCallback(void* refCon);
46 |
47 | CGSize NISizeOfAttributedStringConstrainedToSize(NSAttributedString* attributedString, CGSize constraintSize, NSInteger numberOfLines) {
48 | if (nil == attributedString) {
49 | return CGSizeZero;
50 | }
51 |
52 | CFAttributedStringRef attributedStringRef = (__bridge CFAttributedStringRef)attributedString;
53 | CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attributedStringRef);
54 | CFRange range = CFRangeMake(0, 0);
55 |
56 | // This logic adapted from @mattt's TTTAttributedLabel
57 | // https://github.com/mattt/TTTAttributedLabel
58 |
59 | if (numberOfLines == 1) {
60 | constraintSize = CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX);
61 |
62 | } else if (numberOfLines > 0 && nil != framesetter) {
63 | CGMutablePathRef path = CGPathCreateMutable();
64 | CGPathAddRect(path, NULL, CGRectMake(0, 0, constraintSize.width, constraintSize.height));
65 | CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL);
66 | CFArrayRef lines = CTFrameGetLines(frame);
67 |
68 | if (nil != lines && CFArrayGetCount(lines) > 0) {
69 | NSInteger lastVisibleLineIndex = MIN(numberOfLines, CFArrayGetCount(lines)) - 1;
70 | CTLineRef lastVisibleLine = CFArrayGetValueAtIndex(lines, lastVisibleLineIndex);
71 |
72 | CFRange rangeToLayout = CTLineGetStringRange(lastVisibleLine);
73 | range = CFRangeMake(0, rangeToLayout.location + rangeToLayout.length);
74 | }
75 |
76 | CFRelease(frame);
77 | CFRelease(path);
78 | }
79 |
80 | CGSize newSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, range, NULL, constraintSize, NULL);
81 |
82 | if (nil != framesetter) {
83 | CFRelease(framesetter);
84 | framesetter = nil;
85 | }
86 |
87 | return CGSizeMake(ceil(newSize.width), ceil(newSize.height));
88 | }
89 |
90 | @interface NIAttributedLabelImage : NSObject
91 |
92 | - (CGSize)boxSize; // imageSize + margins
93 |
94 | @property (nonatomic) NSInteger index;
95 | @property (nonatomic, strong) UIImage* image;
96 | @property (nonatomic) UIEdgeInsets margins;
97 |
98 | @property (nonatomic) NIVerticalTextAlignment verticalTextAlignment;
99 |
100 | @property (nonatomic) CGFloat fontAscent;
101 | @property (nonatomic) CGFloat fontDescent;
102 |
103 | @end
104 |
105 | @implementation NIAttributedLabelImage
106 |
107 | - (CGSize)boxSize {
108 | return CGSizeMake(self.image.size.width + self.margins.left + self.margins.right,
109 | self.image.size.height + self.margins.top + self.margins.bottom);
110 | }
111 |
112 | @end
113 |
114 | @interface NIAttributedLabel()
115 |
116 | @property (nonatomic, strong) NSMutableAttributedString* mutableAttributedString;
117 |
118 | @property (nonatomic) CTFrameRef textFrame; // CFType, manually managed lifetime, see setter.
119 |
120 | @property (assign) BOOL detectingLinks; // Atomic.
121 | @property (nonatomic) BOOL linksHaveBeenDetected;
122 | @property (nonatomic, copy) NSArray* detectedlinkLocations;
123 | @property (nonatomic, strong) NSMutableArray* explicitLinkLocations;
124 |
125 | @property (nonatomic, strong) NSTextCheckingResult* originalLink;
126 | @property (nonatomic, strong) NSTextCheckingResult* touchedLink;
127 |
128 | @property (nonatomic, strong) NSTimer* longPressTimer;
129 | @property (nonatomic) CGPoint touchPoint;
130 |
131 | @property (nonatomic, strong) NSTextCheckingResult* actionSheetLink;
132 |
133 | @property (nonatomic, copy) NSArray* accessibleElements;
134 |
135 | @property (nonatomic, strong) NSMutableArray *images;
136 |
137 | @end
138 |
139 | @interface NIAttributedLabel (ConversionUtilities)
140 |
141 | + (CTTextAlignment)alignmentFromUITextAlignment:(NSTextAlignment)alignment;
142 | + (CTLineBreakMode)lineBreakModeFromUILineBreakMode:(NSLineBreakMode)lineBreakMode;
143 | + (NSMutableAttributedString *)mutableAttributedStringFromLabel:(UILabel *)label;
144 |
145 | @end
146 |
147 | @implementation NIAttributedLabel
148 |
149 | @synthesize textFrame = _textFrame;
150 |
151 | - (void)dealloc {
152 | [_longPressTimer invalidate];
153 |
154 | // The property is marked 'assign', but retain count for this CFType is managed here and via
155 | // the setter.
156 | if (NULL != _textFrame) {
157 | CFRelease(_textFrame);
158 | }
159 | }
160 |
161 | - (CTFrameRef)textFrame {
162 | if (NULL == _textFrame) {
163 | NSMutableAttributedString* attributedStringWithLinks = [self mutableAttributedStringWithAdditions];
164 | CFAttributedStringRef attributedString = (__bridge CFAttributedStringRef)attributedStringWithLinks;
165 | CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attributedString);
166 |
167 | CGMutablePathRef path = CGPathCreateMutable();
168 | CGPathAddRect(path, NULL, self.bounds);
169 | CTFrameRef textFrame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL);
170 | self.textFrame = textFrame;
171 | if (textFrame) {
172 | CFRelease(textFrame);
173 | }
174 | CGPathRelease(path);
175 | CFRelease(framesetter);
176 | }
177 |
178 | return _textFrame;
179 | }
180 |
181 | - (void)setTextFrame:(CTFrameRef)textFrame {
182 | // The property is marked 'assign', but retain count for this CFType is managed via this setter
183 | // and -dealloc.
184 | if (textFrame != _textFrame) {
185 | if (NULL != _textFrame) {
186 | CFRelease(_textFrame);
187 | }
188 | if (NULL != textFrame) {
189 | CFRetain(textFrame);
190 | }
191 | _textFrame = textFrame;
192 | }
193 | }
194 |
195 | - (void)_configureDefaults {
196 | self.verticalTextAlignment = NIVerticalTextAlignmentTop;
197 | self.linkColor = NITintColorForViewWithFallback(self, [UIColor blueColor]);
198 | self.dataDetectorTypes = NSTextCheckingTypeLink;
199 | self.highlightedLinkBackgroundColor = [UIColor colorWithWhite:0.5f alpha:0.5f];
200 | }
201 |
202 | - (id)initWithFrame:(CGRect)frame {
203 | if ((self = [super initWithFrame:frame])) {
204 | [self _configureDefaults];
205 | }
206 | return self;
207 | }
208 |
209 | - (void)awakeFromNib {
210 | [super awakeFromNib];
211 |
212 | [self _configureDefaults];
213 |
214 | self.attributedText = [[self class] mutableAttributedStringFromLabel:self];
215 | }
216 |
217 | - (void)resetTextFrame {
218 | self.textFrame = NULL;
219 | self.accessibleElements = nil;
220 | }
221 |
222 | - (void)attributedTextDidChange {
223 | [self resetTextFrame];
224 |
225 | [self invalidateIntrinsicContentSize];
226 | [self setNeedsDisplay];
227 | }
228 |
229 | - (void)setFrame:(CGRect)frame {
230 | BOOL frameDidChange = !CGRectEqualToRect(self.frame, frame);
231 |
232 | [super setFrame:frame];
233 |
234 | if (frameDidChange) {
235 | [self attributedTextDidChange];
236 | }
237 | }
238 |
239 | - (CGSize)sizeThatFits:(CGSize)size {
240 | if (nil == self.mutableAttributedString) {
241 | return CGSizeZero;
242 | }
243 |
244 | return NISizeOfAttributedStringConstrainedToSize([self mutableAttributedStringWithAdditions], size, self.numberOfLines);
245 | }
246 |
247 | - (CGSize)intrinsicContentSize {
248 | return [self sizeThatFits:[super intrinsicContentSize]];
249 | }
250 |
251 | #pragma mark - Public
252 |
253 | - (void)setText:(NSString *)text {
254 | [super setText:text];
255 |
256 | self.attributedText = [[self class] mutableAttributedStringFromLabel:self];
257 |
258 | // Apply NIAttributedLabel-specific styles.
259 | [self.mutableAttributedString nimbuskit_setUnderlineStyle:_underlineStyle modifier:_underlineStyleModifier];
260 | [self.mutableAttributedString nimbuskit_setStrokeWidth:_strokeWidth];
261 | [self.mutableAttributedString nimbuskit_setStrokeColor:_strokeColor];
262 | [self.mutableAttributedString nimbuskit_setKern:_textKern];
263 | }
264 |
265 | - (NSAttributedString *)attributedText {
266 | return [self.mutableAttributedString copy];
267 | }
268 |
269 | - (void)setAttributedText:(NSAttributedString *)attributedText {
270 | if (self.mutableAttributedString != attributedText) {
271 | self.mutableAttributedString = [attributedText mutableCopy];
272 |
273 | // Clear the link caches.
274 | self.detectedlinkLocations = nil;
275 | self.linksHaveBeenDetected = NO;
276 | [self removeAllExplicitLinks];
277 |
278 | // Remove all images.
279 | self.images = nil;
280 |
281 | // Pull any explicit links from the attributed string itself
282 | [self _processLinksInAttributedString:self.mutableAttributedString];
283 |
284 | [self attributedTextDidChange];
285 | }
286 | }
287 |
288 | - (void)setAutoDetectLinks:(BOOL)autoDetectLinks {
289 | _autoDetectLinks = autoDetectLinks;
290 |
291 | [self attributedTextDidChange];
292 | }
293 |
294 | - (void)addLink:(NSURL *)urlLink range:(NSRange)range {
295 | if (nil == self.explicitLinkLocations) {
296 | self.explicitLinkLocations = [[NSMutableArray alloc] init];
297 | }
298 |
299 | NSTextCheckingResult* result = [NSTextCheckingResult linkCheckingResultWithRange:range URL:urlLink];
300 | [self.explicitLinkLocations addObject:result];
301 |
302 | [self attributedTextDidChange];
303 | }
304 |
305 | - (void)removeAllExplicitLinks {
306 | self.explicitLinkLocations = nil;
307 |
308 | [self attributedTextDidChange];
309 | }
310 |
311 | - (void)setTextAlignment:(NSTextAlignment)textAlignment {
312 | // We assume that the UILabel implementation will call setNeedsDisplay. Where we don't call super
313 | // we call setNeedsDisplay ourselves.
314 | if (NSTextAlignmentJustified == textAlignment) {
315 | // iOS 6.0 Beta 2 crashes when using justified text alignments for some reason.
316 | [super setTextAlignment:NSTextAlignmentLeft];
317 | } else {
318 | [super setTextAlignment:textAlignment];
319 | }
320 |
321 | if (nil != self.mutableAttributedString) {
322 | CTTextAlignment alignment = [self.class alignmentFromUITextAlignment:textAlignment];
323 | CTLineBreakMode lineBreak = [self.class lineBreakModeFromUILineBreakMode:self.lineBreakMode];
324 | [self.mutableAttributedString nimbuskit_setTextAlignment:alignment lineBreakMode:lineBreak lineHeight:self.lineHeight];
325 | }
326 | }
327 |
328 | - (void)setLineBreakMode:(NSLineBreakMode)lineBreakMode {
329 | [super setLineBreakMode:lineBreakMode];
330 |
331 | if (nil != self.mutableAttributedString) {
332 | CTTextAlignment alignment = [self.class alignmentFromUITextAlignment:self.textAlignment];
333 | CTLineBreakMode lineBreak = [self.class lineBreakModeFromUILineBreakMode:lineBreakMode];
334 | [self.mutableAttributedString nimbuskit_setTextAlignment:alignment lineBreakMode:lineBreak lineHeight:self.lineHeight];
335 | }
336 | }
337 |
338 | - (void)setTextColor:(UIColor *)textColor {
339 | [super setTextColor:textColor];
340 |
341 | [self.mutableAttributedString nimbuskit_setTextColor:textColor];
342 |
343 | [self attributedTextDidChange];
344 | }
345 |
346 | - (void)setTextColor:(UIColor *)textColor range:(NSRange)range {
347 | [self.mutableAttributedString nimbuskit_setTextColor:textColor range:range];
348 |
349 | [self attributedTextDidChange];
350 | }
351 |
352 | - (void)setFont:(UIFont *)font {
353 | [super setFont:font];
354 |
355 | [self.mutableAttributedString nimbuskit_setFont:font];
356 |
357 | [self attributedTextDidChange];
358 | }
359 |
360 | - (void)setFont:(UIFont *)font range:(NSRange)range {
361 | [self.mutableAttributedString nimbuskit_setFont:font range:range];
362 |
363 | [self attributedTextDidChange];
364 | }
365 |
366 | - (void)setUnderlineStyle:(CTUnderlineStyle)style {
367 | if (style != _underlineStyle) {
368 | _underlineStyle = style;
369 | [self.mutableAttributedString nimbuskit_setUnderlineStyle:style modifier:self.underlineStyleModifier];
370 |
371 | [self attributedTextDidChange];
372 | }
373 | }
374 |
375 | - (void)setUnderlineStyleModifier:(CTUnderlineStyleModifiers)modifier {
376 | if (modifier != _underlineStyleModifier) {
377 | _underlineStyleModifier = modifier;
378 | [self.mutableAttributedString nimbuskit_setUnderlineStyle:self.underlineStyle modifier:modifier];
379 |
380 | [self attributedTextDidChange];
381 | }
382 | }
383 |
384 | - (void)setUnderlineStyle:(CTUnderlineStyle)style modifier:(CTUnderlineStyleModifiers)modifier range:(NSRange)range {
385 | [self.mutableAttributedString nimbuskit_setUnderlineStyle:style modifier:modifier range:range];
386 |
387 | [self attributedTextDidChange];
388 | }
389 |
390 | - (void)setShadowBlur:(CGFloat)shadowBlur {
391 | if (_shadowBlur != shadowBlur) {
392 | _shadowBlur = shadowBlur;
393 |
394 | [self attributedTextDidChange];
395 | }
396 | }
397 |
398 | - (void)setStrokeWidth:(CGFloat)strokeWidth {
399 | if (_strokeWidth != strokeWidth) {
400 | _strokeWidth = strokeWidth;
401 | [self.mutableAttributedString nimbuskit_setStrokeWidth:strokeWidth];
402 |
403 | [self attributedTextDidChange];
404 | }
405 | }
406 |
407 | - (void)setStrokeWidth:(CGFloat)width range:(NSRange)range {
408 | [self.mutableAttributedString nimbuskit_setStrokeWidth:width range:range];
409 |
410 | [self attributedTextDidChange];
411 | }
412 |
413 | - (void)setStrokeColor:(UIColor *)strokeColor {
414 | if (_strokeColor != strokeColor) {
415 | _strokeColor = strokeColor;
416 | [self.mutableAttributedString nimbuskit_setStrokeColor:_strokeColor];
417 |
418 | [self attributedTextDidChange];
419 | }
420 | }
421 |
422 | - (void)setStrokeColor:(UIColor*)color range:(NSRange)range {
423 | [self.mutableAttributedString nimbuskit_setStrokeColor:color range:range];
424 |
425 | [self attributedTextDidChange];
426 | }
427 |
428 | - (void)setTextKern:(CGFloat)textKern {
429 | if (_textKern != textKern) {
430 | _textKern = textKern;
431 | [self.mutableAttributedString nimbuskit_setKern:_textKern];
432 |
433 | [self attributedTextDidChange];
434 | }
435 | }
436 |
437 | - (void)setTextKern:(CGFloat)kern range:(NSRange)range {
438 | [self.mutableAttributedString nimbuskit_setKern:kern range:range];
439 |
440 | [self attributedTextDidChange];
441 | }
442 |
443 | - (void)setTailTruncationString:(NSString *)tailTruncationString {
444 | if (![_tailTruncationString isEqualToString:tailTruncationString]) {
445 | _tailTruncationString = [tailTruncationString copy];
446 |
447 | [self attributedTextDidChange];
448 | }
449 | }
450 |
451 | - (void)setLinkColor:(UIColor *)linkColor {
452 | if (_linkColor != linkColor) {
453 | _linkColor = linkColor;
454 |
455 | [self attributedTextDidChange];
456 | }
457 | }
458 |
459 | - (void)setLineHeight:(CGFloat)lineHeight {
460 | _lineHeight = lineHeight;
461 |
462 | if (nil != self.mutableAttributedString) {
463 | CTTextAlignment alignment = [self.class alignmentFromUITextAlignment:self.textAlignment];
464 | CTLineBreakMode lineBreak = [self.class lineBreakModeFromUILineBreakMode:self.lineBreakMode];
465 | [self.mutableAttributedString nimbuskit_setTextAlignment:alignment lineBreakMode:lineBreak lineHeight:self.lineHeight];
466 |
467 | [self attributedTextDidChange];
468 | }
469 | }
470 |
471 | - (void)setHighlightedLinkBackgroundColor:(UIColor *)highlightedLinkBackgroundColor {
472 | if (_highlightedLinkBackgroundColor != highlightedLinkBackgroundColor) {
473 | _highlightedLinkBackgroundColor = highlightedLinkBackgroundColor;
474 |
475 | [self attributedTextDidChange];
476 | }
477 | }
478 |
479 | - (void)setLinksHaveUnderlines:(BOOL)linksHaveUnderlines {
480 | if (_linksHaveUnderlines != linksHaveUnderlines) {
481 | _linksHaveUnderlines = linksHaveUnderlines;
482 |
483 | [self attributedTextDidChange];
484 | }
485 | }
486 |
487 | - (void)setAttributesForLinks:(NSDictionary *)attributesForLinks {
488 | if (_attributesForLinks != attributesForLinks) {
489 | _attributesForLinks = attributesForLinks;
490 |
491 | [self attributedTextDidChange];
492 | }
493 | }
494 |
495 | - (void)setAttributesForHighlightedLink:(NSDictionary *)attributesForHighlightedLink {
496 | if (_attributesForHighlightedLink != attributesForHighlightedLink) {
497 | _attributesForHighlightedLink = attributesForHighlightedLink;
498 |
499 | [self attributedTextDidChange];
500 | }
501 | }
502 |
503 | - (void)setExplicitLinkLocations:(NSMutableArray *)explicitLinkLocations {
504 | if (_explicitLinkLocations != explicitLinkLocations) {
505 | _explicitLinkLocations = explicitLinkLocations;
506 | self.accessibleElements = nil;
507 | }
508 | }
509 |
510 | - (void)setDetectedlinkLocations:(NSArray *)detectedlinkLocations{
511 | if (_detectedlinkLocations != detectedlinkLocations) {
512 | _detectedlinkLocations = detectedlinkLocations;
513 | self.accessibleElements = nil;
514 | }
515 | }
516 |
517 | - (void)setHighlighted:(BOOL)highlighted {
518 | BOOL didChange = self.highlighted != highlighted;
519 | [super setHighlighted:highlighted];
520 |
521 | if (didChange) {
522 | [self attributedTextDidChange];
523 | }
524 | }
525 |
526 | - (void)setHighlightedTextColor:(UIColor *)highlightedTextColor {
527 | BOOL didChange = self.highlightedTextColor != highlightedTextColor;
528 | [super setHighlightedTextColor:highlightedTextColor];
529 |
530 | if (didChange) {
531 | [self attributedTextDidChange];
532 | }
533 | }
534 |
535 | - (NSArray *)_matchesFromAttributedString:(NSString *)string {
536 | NSError* error = nil;
537 | NSDataDetector* linkDetector = [NSDataDetector dataDetectorWithTypes:(NSTextCheckingTypes)self.dataDetectorTypes
538 | error:&error];
539 | NSRange range = NSMakeRange(0, string.length);
540 |
541 | return [linkDetector matchesInString:string options:0 range:range];
542 | }
543 |
544 | - (void)_deferLinkDetection {
545 | if (!self.detectingLinks) {
546 | self.detectingLinks = YES;
547 |
548 | NSString* string = [self.mutableAttributedString.string copy];
549 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
550 | NSArray* matches = [self _matchesFromAttributedString:string];
551 | self.detectingLinks = NO;
552 |
553 | dispatch_async(dispatch_get_main_queue(), ^{
554 | self.detectedlinkLocations = matches;
555 | self.linksHaveBeenDetected = YES;
556 |
557 | [self attributedTextDidChange];
558 | });
559 | });
560 | }
561 | }
562 |
563 | // Use an NSDataDetector to find any implicit links in the text. The results are cached until
564 | // the text changes.
565 | - (void)detectLinks {
566 | if (nil == self.mutableAttributedString) {
567 | return;
568 | }
569 |
570 | if (self.autoDetectLinks && !self.linksHaveBeenDetected) {
571 | if (self.deferLinkDetection) {
572 | [self _deferLinkDetection];
573 |
574 | } else {
575 | self.detectedlinkLocations = [self _matchesFromAttributedString:self.mutableAttributedString.string];
576 | self.linksHaveBeenDetected = YES;
577 | }
578 | }
579 | }
580 |
581 | - (CGRect)getLineBounds:(CTLineRef)line point:(CGPoint) point {
582 | CGFloat ascent = 0.0f;
583 | CGFloat descent = 0.0f;
584 | CGFloat leading = 0.0f;
585 | CGFloat width = (CGFloat)CTLineGetTypographicBounds(line, &ascent, &descent, &leading);
586 | CGFloat height = ascent + descent;
587 |
588 | return CGRectMake(point.x, point.y - descent, width, height);
589 | }
590 |
591 | - (NSTextCheckingResult *)linkAtIndex:(CFIndex)i {
592 | NSTextCheckingResult* foundResult = nil;
593 |
594 | if (self.autoDetectLinks) {
595 | [self detectLinks];
596 |
597 | for (NSTextCheckingResult* result in self.detectedlinkLocations) {
598 | if (NSLocationInRange(i, result.range)) {
599 | foundResult = result;
600 | break;
601 | }
602 | }
603 | }
604 |
605 | if (nil == foundResult) {
606 | for (NSTextCheckingResult* result in self.explicitLinkLocations) {
607 | if (NSLocationInRange(i, result.range)) {
608 | foundResult = result;
609 | break;
610 | }
611 | }
612 | }
613 |
614 | return foundResult;
615 | }
616 |
617 | - (void)_processLinksInAttributedString:(NSAttributedString *)attributedString {
618 | // Pull any attributes matching the link attribute from the attributed string and store them as
619 | // the current set of explicit links.
620 | __block NSMutableArray *links = [NSMutableArray array];
621 | [attributedString enumerateAttribute:NIAttributedLabelLinkAttributeName
622 | inRange:NSMakeRange(0, attributedString.length)
623 | options:0
624 | usingBlock:^(id value, NSRange range, BOOL *stop) {
625 | if (value != nil) {
626 | [links addObject:value];
627 | }
628 | }];
629 | self.explicitLinkLocations = links;
630 | }
631 |
632 | - (CGFloat)_verticalOffsetForBounds:(CGRect)bounds {
633 | CGFloat verticalOffset = 0;
634 | if (NIVerticalTextAlignmentTop != self.verticalTextAlignment) {
635 | // When the text is attached to the top we can easily just start drawing and leave the
636 | // remainder. This is the most performant case.
637 | // With other alignment modes we must calculate the size of the text first.
638 | CGSize textSize = [self sizeThatFits:CGSizeMake(bounds.size.width, CGFLOAT_MAX)];
639 |
640 | if (NIVerticalTextAlignmentMiddle == self.verticalTextAlignment) {
641 | verticalOffset = floor((bounds.size.height - textSize.height) / 2.f);
642 |
643 | } else if (NIVerticalTextAlignmentBottom == self.verticalTextAlignment) {
644 | verticalOffset = bounds.size.height - textSize.height;
645 | }
646 | }
647 | return verticalOffset;
648 | }
649 |
650 | - (CGAffineTransform)_transformForCoreText {
651 | // CoreText context coordinates are the opposite to UIKit so we flip the bounds
652 | return CGAffineTransformScale(CGAffineTransformMakeTranslation(0, self.bounds.size.height), 1.f, -1.f);
653 | }
654 |
655 | - (NSTextCheckingResult *)linkAtPoint:(CGPoint)point {
656 | if (!CGRectContainsPoint(CGRectInset(self.bounds, 0, -kVMargin), point)) {
657 | return nil;
658 | }
659 |
660 | CFArrayRef lines = CTFrameGetLines(self.textFrame);
661 | if (!lines) return nil;
662 | CFIndex count = CFArrayGetCount(lines);
663 |
664 | CGPoint origins[count];
665 | CTFrameGetLineOrigins(self.textFrame, CFRangeMake(0,0), origins);
666 |
667 | CGAffineTransform transform = [self _transformForCoreText];
668 | CGFloat verticalOffset = [self _verticalOffsetForBounds:self.bounds];
669 |
670 | for (int i = 0; i < count; i++) {
671 | CGPoint linePoint = origins[i];
672 |
673 | CTLineRef line = CFArrayGetValueAtIndex(lines, i);
674 | CGRect flippedRect = [self getLineBounds:line point:linePoint];
675 | CGRect rect = CGRectApplyAffineTransform(flippedRect, transform);
676 |
677 | rect = CGRectInset(rect, 0, -kVMargin);
678 | rect = CGRectOffset(rect, 0, verticalOffset);
679 |
680 | if (CGRectContainsPoint(rect, point)) {
681 | CGPoint relativePoint = CGPointMake(point.x-CGRectGetMinX(rect),
682 | point.y-CGRectGetMinY(rect));
683 | CFIndex idx = CTLineGetStringIndexForPosition(line, relativePoint);
684 |
685 | NSUInteger offset = 0;
686 | for (NIAttributedLabelImage *labelImage in self.images) {
687 | if (labelImage.index < idx) {
688 | offset++;
689 | }
690 | }
691 |
692 | NSTextCheckingResult* foundLink = [self linkAtIndex:idx - offset];
693 | if (foundLink) {
694 | return foundLink;
695 | }
696 | }
697 | }
698 | return nil;
699 | }
700 |
701 | - (CGRect)_rectForRange:(NSRange)range inLine:(CTLineRef)line lineOrigin:(CGPoint)lineOrigin {
702 | CGRect rectForRange = CGRectZero;
703 | CFArrayRef runs = CTLineGetGlyphRuns(line);
704 | CFIndex runCount = CFArrayGetCount(runs);
705 |
706 | // Iterate through each of the "runs" (i.e. a chunk of text) and find the runs that
707 | // intersect with the range.
708 | for (CFIndex k = 0; k < runCount; k++) {
709 | CTRunRef run = CFArrayGetValueAtIndex(runs, k);
710 |
711 | CFRange stringRunRange = CTRunGetStringRange(run);
712 | NSRange lineRunRange = NSMakeRange(stringRunRange.location, stringRunRange.length);
713 | NSRange intersectedRunRange = NSIntersectionRange(lineRunRange, range);
714 |
715 | if (intersectedRunRange.length == 0) {
716 | // This run doesn't intersect the range, so skip it.
717 | continue;
718 | }
719 |
720 | CGFloat ascent = 0.0f;
721 | CGFloat descent = 0.0f;
722 | CGFloat leading = 0.0f;
723 |
724 | // Use of 'leading' doesn't properly highlight Japanese-character link.
725 | CGFloat width = (CGFloat)CTRunGetTypographicBounds(run,
726 | CFRangeMake(0, 0),
727 | &ascent,
728 | &descent,
729 | NULL); //&leading);
730 | CGFloat height = ascent + descent;
731 |
732 | CGFloat xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, nil);
733 |
734 | CGRect linkRect = CGRectMake(lineOrigin.x + xOffset - leading, lineOrigin.y - descent, width + leading, height);
735 |
736 | linkRect.origin.y = round(linkRect.origin.y);
737 | linkRect.origin.x = round(linkRect.origin.x);
738 | linkRect.size.width = round(linkRect.size.width);
739 | linkRect.size.height = round(linkRect.size.height);
740 |
741 | if (CGRectIsEmpty(rectForRange)) {
742 | rectForRange = linkRect;
743 |
744 | } else {
745 | rectForRange = CGRectUnion(rectForRange, linkRect);
746 | }
747 | }
748 |
749 | return rectForRange;
750 | }
751 |
752 | - (BOOL)isPoint:(CGPoint)point nearLink:(NSTextCheckingResult *)link {
753 | CFArrayRef lines = CTFrameGetLines(self.textFrame);
754 | if (nil == lines) {
755 | return NO;
756 | }
757 | CFIndex count = CFArrayGetCount(lines);
758 | CGPoint lineOrigins[count];
759 | CTFrameGetLineOrigins(self.textFrame, CFRangeMake(0, 0), lineOrigins);
760 |
761 | CGAffineTransform transform = [self _transformForCoreText];
762 | CGFloat verticalOffset = [self _verticalOffsetForBounds:self.bounds];
763 |
764 | NSRange linkRange = link.range;
765 |
766 | BOOL isNearLink = NO;
767 | for (int i = 0; i < count; i++) {
768 | CTLineRef line = CFArrayGetValueAtIndex(lines, i);
769 |
770 | CGRect linkRect = [self _rectForRange:linkRange inLine:line lineOrigin:lineOrigins[i]];
771 |
772 | if (!CGRectIsEmpty(linkRect)) {
773 | linkRect = CGRectApplyAffineTransform(linkRect, transform);
774 | linkRect = CGRectOffset(linkRect, 0, verticalOffset);
775 | linkRect = CGRectInset(linkRect, -kTouchGutter, -kTouchGutter);
776 | if (CGRectContainsPoint(linkRect, point)) {
777 | isNearLink = YES;
778 | break;
779 | }
780 | }
781 | }
782 |
783 | return isNearLink;
784 | }
785 |
786 | - (NSArray *)_rectsForLink:(NSTextCheckingResult *)link {
787 | CFArrayRef lines = CTFrameGetLines(self.textFrame);
788 | if (nil == lines) {
789 | return nil;
790 | }
791 | CFIndex count = CFArrayGetCount(lines);
792 | CGPoint lineOrigins[count];
793 | CTFrameGetLineOrigins(self.textFrame, CFRangeMake(0, 0), lineOrigins);
794 |
795 | CGAffineTransform transform = [self _transformForCoreText];
796 | CGFloat verticalOffset = [self _verticalOffsetForBounds:self.bounds];
797 |
798 | NSRange linkRange = link.range;
799 |
800 | NSMutableArray* rects = [NSMutableArray array];
801 | for (int i = 0; i < count; i++) {
802 | CTLineRef line = CFArrayGetValueAtIndex(lines, i);
803 |
804 | CGRect linkRect = [self _rectForRange:linkRange inLine:line lineOrigin:lineOrigins[i]];
805 |
806 | if (!CGRectIsEmpty(linkRect)) {
807 | linkRect = CGRectApplyAffineTransform(linkRect, transform);
808 | linkRect = CGRectOffset(linkRect, 0, verticalOffset);
809 | [rects addObject:[NSValue valueWithCGRect:linkRect]];
810 | }
811 | }
812 |
813 | return [rects copy];
814 | }
815 |
816 | - (void)setTouchedLink:(NSTextCheckingResult *)touchedLink {
817 | if (_touchedLink != touchedLink) {
818 | _touchedLink = touchedLink;
819 |
820 | if (self.attributesForHighlightedLink.count > 0) {
821 | [self attributedTextDidChange];
822 | }
823 | }
824 | }
825 |
826 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
827 | UITouch* touch = [touches anyObject];
828 | CGPoint point = [touch locationInView:self];
829 |
830 | self.touchedLink = [self linkAtPoint:point];
831 | self.touchPoint = point;
832 | self.originalLink = self.touchedLink;
833 |
834 | if (self.originalLink) {
835 | [self.longPressTimer invalidate];
836 | if (nil != self.touchedLink) {
837 | self.longPressTimer = [NSTimer scheduledTimerWithTimeInterval:kLongPressTimeInterval target:self selector:@selector(_longPressTimerDidFire:) userInfo:nil repeats:NO];
838 | }
839 |
840 | } else {
841 | [super touchesBegan:touches withEvent:event];
842 | }
843 |
844 | [self setNeedsDisplay];
845 | }
846 |
847 | - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
848 | UITouch* touch = [touches anyObject];
849 | CGPoint point = [touch locationInView:self];
850 |
851 | if (self.originalLink) {
852 | // If the user moves their finger away from the original link, deselect it.
853 | // If the user moves their finger back to the original link, reselect it.
854 | // Don't allow other links to be selected other than the original link.
855 |
856 | if (nil != self.originalLink) {
857 | NSTextCheckingResult* oldTouchedLink = self.touchedLink;
858 |
859 | if ([self isPoint:point nearLink:self.originalLink]) {
860 | self.touchedLink = self.originalLink;
861 |
862 | } else {
863 | self.touchedLink = nil;
864 | }
865 |
866 | if (oldTouchedLink != self.touchedLink) {
867 | [self.longPressTimer invalidate];
868 | self.longPressTimer = nil;
869 | [self setNeedsDisplay];
870 | }
871 | }
872 |
873 | // If the user moves their finger within the link beyond a certain gutter amount, reset the
874 | // hold timer. The user must hold their finger still for the long press interval in order for
875 | // the long press action to fire.
876 | if (fabs(self.touchPoint.x - point.x) >= kLongPressGutter
877 | || fabs(self.touchPoint.y - point.y) >= kLongPressGutter) {
878 | [self.longPressTimer invalidate];
879 | self.longPressTimer = nil;
880 | if (nil != self.touchedLink) {
881 | self.longPressTimer = [NSTimer scheduledTimerWithTimeInterval:kLongPressTimeInterval target:self selector:@selector(_longPressTimerDidFire:) userInfo:nil repeats:NO];
882 | self.touchPoint = point;
883 | }
884 | }
885 | } else {
886 | [super touchesMoved:touches withEvent:event];
887 | }
888 | }
889 |
890 | - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
891 | if (self.originalLink) {
892 | [self.longPressTimer invalidate];
893 | self.longPressTimer = nil;
894 |
895 | UITouch* touch = [touches anyObject];
896 | CGPoint point = [touch locationInView:self];
897 |
898 | if (nil != self.originalLink) {
899 | if ([self isPoint:point nearLink:self.originalLink]
900 | && [self.delegate respondsToSelector:@selector(attributedLabel:didSelectTextCheckingResult:atPoint:)]) {
901 | [self.delegate attributedLabel:self didSelectTextCheckingResult:self.originalLink atPoint:point];
902 | }
903 | }
904 |
905 | self.touchedLink = nil;
906 | self.originalLink = nil;
907 | [self setNeedsDisplay];
908 |
909 | } else {
910 | [super touchesEnded:touches withEvent:event];
911 | }
912 | }
913 |
914 | - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
915 | [super touchesCancelled:touches withEvent:event];
916 |
917 | [self.longPressTimer invalidate];
918 | self.longPressTimer = nil;
919 |
920 | self.touchedLink = nil;
921 | self.originalLink = nil;
922 |
923 | [self setNeedsDisplay];
924 | }
925 |
926 | - (UIActionSheet *)actionSheetForResult:(NSTextCheckingResult *)result {
927 | UIActionSheet* actionSheet =
928 | [[UIActionSheet alloc] initWithTitle:nil
929 | delegate:self
930 | cancelButtonTitle:nil
931 | destructiveButtonTitle:nil
932 | otherButtonTitles:nil];
933 |
934 | NSString* title = nil;
935 | if (NSTextCheckingTypeLink == result.resultType) {
936 | if ([result.URL.scheme isEqualToString:@"mailto"]) {
937 | title = result.URL.resourceSpecifier;
938 | [actionSheet addButtonWithTitle:NSLocalizedString(@"Open in Mail", @"")];
939 | [actionSheet addButtonWithTitle:NSLocalizedString(@"Copy Email Address", @"")];
940 |
941 | } else {
942 | title = result.URL.absoluteString;
943 | [actionSheet addButtonWithTitle:NSLocalizedString(@"Open in Safari", @"")];
944 | [actionSheet addButtonWithTitle:NSLocalizedString(@"Copy URL", @"")];
945 | }
946 |
947 | } else if (NSTextCheckingTypePhoneNumber == result.resultType) {
948 | title = result.phoneNumber;
949 | [actionSheet addButtonWithTitle:NSLocalizedString(@"Call", @"")];
950 | [actionSheet addButtonWithTitle:NSLocalizedString(@"Copy Phone Number", @"")];
951 |
952 | } else if (NSTextCheckingTypeAddress == result.resultType) {
953 | title = [self.mutableAttributedString.string substringWithRange:self.actionSheetLink.range];
954 | [actionSheet addButtonWithTitle:NSLocalizedString(@"Open in Maps", @"")];
955 | [actionSheet addButtonWithTitle:NSLocalizedString(@"Copy Address", @"")];
956 |
957 | } else {
958 | // This type has not been implemented yet.
959 | NI_DASSERT(NO);
960 | [actionSheet addButtonWithTitle:NSLocalizedString(@"Copy", @"")];
961 | }
962 | actionSheet.title = title;
963 |
964 | if (!NIIsPad()) {
965 | [actionSheet setCancelButtonIndex:[actionSheet addButtonWithTitle:NSLocalizedString(@"Cancel", @"")]];
966 | }
967 |
968 | return actionSheet;
969 | }
970 |
971 | - (void)_longPressTimerDidFire:(NSTimer *)timer {
972 | self.longPressTimer = nil;
973 |
974 | if (nil != self.touchedLink) {
975 | self.actionSheetLink = self.touchedLink;
976 |
977 | UIActionSheet* actionSheet = [self actionSheetForResult:self.actionSheetLink];
978 |
979 | BOOL shouldPresent = YES;
980 | if ([self.delegate respondsToSelector:@selector(attributedLabel:shouldPresentActionSheet:withTextCheckingResult:atPoint:)]) {
981 | // Give the delegate the opportunity to not show the action sheet or to present its own.
982 | shouldPresent = [self.delegate attributedLabel:self shouldPresentActionSheet:actionSheet withTextCheckingResult:self.touchedLink atPoint:self.touchPoint];
983 | }
984 |
985 | if (shouldPresent) {
986 | if (NIIsPad()) {
987 | [actionSheet showFromRect:CGRectMake(self.touchPoint.x - 22, self.touchPoint.y - 22, 44, 44) inView:self animated:YES];
988 | } else {
989 | [actionSheet showInView:self];
990 | }
991 |
992 | } else {
993 | self.actionSheetLink = nil;
994 | }
995 | }
996 | }
997 |
998 | - (void)_applyLinkStyleWithResults:(NSArray *)results toAttributedString:(NSMutableAttributedString *)attributedString {
999 | for (NSTextCheckingResult* result in results) {
1000 | if (self.linkColor) {
1001 | [attributedString nimbuskit_setTextColor:self.linkColor range:result.range];
1002 | }
1003 |
1004 | // We add a no-op attribute in order to force a run to exist for each link. Otherwise the
1005 | // runCount will be one in this line, causing the entire line to be highlighted rather than
1006 | // just the link when when no special attributes are set.
1007 | [attributedString removeAttribute:NIAttributedLabelLinkAttributeName range:result.range];
1008 | [attributedString addAttribute:NIAttributedLabelLinkAttributeName
1009 | value:result
1010 | range:result.range];
1011 |
1012 | if (self.linksHaveUnderlines) {
1013 | [attributedString nimbuskit_setUnderlineStyle:kCTUnderlineStyleSingle
1014 | modifier:kCTUnderlinePatternSolid
1015 | range:result.range];
1016 | }
1017 |
1018 | if (self.attributesForLinks.count > 0) {
1019 | [attributedString addAttributes:self.attributesForLinks range:result.range];
1020 | }
1021 | if (self.attributesForHighlightedLink.count > 0 && NSEqualRanges(result.range, self.touchedLink.range)) {
1022 | [attributedString addAttributes:self.attributesForHighlightedLink range:result.range];
1023 | }
1024 | }
1025 | }
1026 |
1027 | // We apply the additional styles immediately before we render the attributed string. This
1028 | // composites the styles with the existing styles without losing any information. This
1029 | // makes it possible to turn off links or remove them altogether without losing the existing
1030 | // style information.
1031 | - (NSMutableAttributedString *)mutableAttributedStringWithAdditions {
1032 | NSMutableAttributedString* attributedString = [self.mutableAttributedString mutableCopy];
1033 | if (self.autoDetectLinks) {
1034 | [self _applyLinkStyleWithResults:self.detectedlinkLocations
1035 | toAttributedString:attributedString];
1036 | }
1037 |
1038 | [self _applyLinkStyleWithResults:self.explicitLinkLocations
1039 | toAttributedString:attributedString];
1040 |
1041 | if (self.images.count > 0) {
1042 | // Sort the label images in reverse order by index so that when we add them the string's indices
1043 | // remain relatively accurate to the original string. This is necessary because we're inserting
1044 | // spaces into the string.
1045 | [self.images sortUsingComparator:^NSComparisonResult(NIAttributedLabelImage* obj1, NIAttributedLabelImage* obj2) {
1046 | if (obj1.index < obj2.index) {
1047 | return NSOrderedDescending;
1048 | } else if (obj1.index > obj2.index) {
1049 | return NSOrderedAscending;
1050 | } else {
1051 | return NSOrderedSame;
1052 | }
1053 | }];
1054 |
1055 | for (NIAttributedLabelImage *labelImage in self.images) {
1056 | CTRunDelegateCallbacks callbacks;
1057 | memset(&callbacks, 0, sizeof(CTRunDelegateCallbacks));
1058 | callbacks.version = kCTRunDelegateVersion1;
1059 | callbacks.getAscent = NIImageDelegateGetAscentCallback;
1060 | callbacks.getDescent = NIImageDelegateGetDescentCallback;
1061 | callbacks.getWidth = NIImageDelegateGetWidthCallback;
1062 |
1063 | NSUInteger index = labelImage.index;
1064 | if (index >= attributedString.length) {
1065 | index = attributedString.length - 1;
1066 | }
1067 |
1068 | NSDictionary *attributes = [attributedString attributesAtIndex:index effectiveRange:NULL];
1069 | CTFontRef font = (__bridge CTFontRef)[attributes valueForKey:(__bridge id)kCTFontAttributeName];
1070 |
1071 | if (font != NULL) {
1072 | labelImage.fontAscent = CTFontGetAscent(font);
1073 | labelImage.fontDescent = CTFontGetDescent(font);
1074 | }
1075 |
1076 | CTRunDelegateRef delegate = CTRunDelegateCreate(&callbacks, (__bridge void *)labelImage);
1077 |
1078 | // Character to use as recommended by kCTRunDelegateAttributeName documentation.
1079 | unichar objectReplacementChar = 0xFFFC;
1080 | NSString *objectReplacementString = [NSString stringWithCharacters:&objectReplacementChar length:1];
1081 | NSMutableAttributedString* space = [[NSMutableAttributedString alloc] initWithString:objectReplacementString];
1082 |
1083 | CFRange range = CFRangeMake(0, 1);
1084 | CFMutableAttributedStringRef spaceString =
1085 | (__bridge_retained CFMutableAttributedStringRef)space;
1086 | CFAttributedStringSetAttribute(spaceString, range, kCTRunDelegateAttributeName, delegate);
1087 | // Explicitly set the writing direction of this string to LTR, because in 'drawImages' we draw
1088 | // for LTR by drawing at offset to offset + width vs to offset - width as you would for RTL.
1089 | CFAttributedStringSetAttribute(spaceString,
1090 | range,
1091 | kCTWritingDirectionAttributeName,
1092 | (__bridge CFArrayRef)@[@(kCTWritingDirectionLeftToRight)]);
1093 | CFRelease(delegate);
1094 | CFRelease(spaceString);
1095 |
1096 | [attributedString insertAttributedString:space atIndex:labelImage.index];
1097 | }
1098 | }
1099 |
1100 | if (self.isHighlighted) {
1101 | [attributedString nimbuskit_setTextColor:self.highlightedTextColor];
1102 | }
1103 |
1104 | return attributedString;
1105 | }
1106 |
1107 | - (NSInteger)numberOfDisplayedLines {
1108 | CFArrayRef lines = CTFrameGetLines(self.textFrame);
1109 | return self.numberOfLines > 0 ? MIN(self.numberOfLines, CFArrayGetCount(lines)) : CFArrayGetCount(lines);
1110 | }
1111 |
1112 | - (void)drawImages {
1113 | if (0 == self.images.count) {
1114 | return;
1115 | }
1116 |
1117 | CGContextRef ctx = UIGraphicsGetCurrentContext();
1118 |
1119 | CFArrayRef lines = CTFrameGetLines(self.textFrame);
1120 | CFIndex lineCount = CFArrayGetCount(lines);
1121 | CGPoint lineOrigins[lineCount];
1122 | CTFrameGetLineOrigins(self.textFrame, CFRangeMake(0, 0), lineOrigins);
1123 | NSInteger numberOfLines = [self numberOfDisplayedLines];
1124 |
1125 | for (CFIndex i = 0; i < numberOfLines; i++) {
1126 | CTLineRef line = CFArrayGetValueAtIndex(lines, i);
1127 | CFArrayRef runs = CTLineGetGlyphRuns(line);
1128 | CFIndex runCount = CFArrayGetCount(runs);
1129 | CGPoint lineOrigin = lineOrigins[i];
1130 | CGFloat lineAscent;
1131 | CGFloat lineDescent;
1132 | CTLineGetTypographicBounds(line, &lineAscent, &lineDescent, NULL);
1133 | CGFloat lineHeight = lineAscent + lineDescent;
1134 | CGFloat lineBottomY = lineOrigin.y - lineDescent;
1135 |
1136 | // Iterate through each of the "runs" (i.e. a chunk of text) and find the runs that
1137 | // intersect with the range.
1138 | for (CFIndex k = 0; k < runCount; k++) {
1139 | CTRunRef run = CFArrayGetValueAtIndex(runs, k);
1140 | NSDictionary *runAttributes = (__bridge NSDictionary *)CTRunGetAttributes(run);
1141 | CTRunDelegateRef delegate = (__bridge CTRunDelegateRef)[runAttributes valueForKey:(__bridge id)kCTRunDelegateAttributeName];
1142 | if (nil == delegate) {
1143 | continue;
1144 | }
1145 | NIAttributedLabelImage* labelImage = (__bridge NIAttributedLabelImage *)CTRunDelegateGetRefCon(delegate);
1146 |
1147 | CGFloat ascent = 0.0f;
1148 | CGFloat descent = 0.0f;
1149 | CGFloat width = (CGFloat)CTRunGetTypographicBounds(run,
1150 | CFRangeMake(0, 0),
1151 | &ascent,
1152 | &descent,
1153 | NULL);
1154 |
1155 | CGFloat imageBoxHeight = labelImage.boxSize.height;
1156 |
1157 | CGFloat xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, nil);
1158 |
1159 | CGFloat imageBoxOriginY = 0.0f;
1160 | switch (labelImage.verticalTextAlignment) {
1161 | case NIVerticalTextAlignmentTop:
1162 | imageBoxOriginY = lineBottomY + (lineHeight - imageBoxHeight);
1163 | break;
1164 | case NIVerticalTextAlignmentMiddle:
1165 | imageBoxOriginY = lineBottomY + (lineHeight - imageBoxHeight) / 2.f;
1166 | break;
1167 | case NIVerticalTextAlignmentBottom:
1168 | imageBoxOriginY = lineBottomY;
1169 | break;
1170 | }
1171 |
1172 | CGRect rect = CGRectMake(lineOrigin.x + xOffset, imageBoxOriginY, width, imageBoxHeight);
1173 | UIEdgeInsets flippedMargins = labelImage.margins;
1174 | CGFloat top = flippedMargins.top;
1175 | flippedMargins.top = flippedMargins.bottom;
1176 | flippedMargins.bottom = top;
1177 |
1178 | CGRect imageRect = UIEdgeInsetsInsetRect(rect, flippedMargins);
1179 | imageRect = CGRectOffset(imageRect, 0, -[self _verticalOffsetForBounds:self.bounds]);
1180 | CGContextDrawImage(ctx, imageRect, labelImage.image.CGImage);
1181 | }
1182 | }
1183 | }
1184 |
1185 | - (void)drawHighlightWithRect:(CGRect)rect {
1186 | if ((nil == self.touchedLink && nil == self.actionSheetLink) || nil == self.highlightedLinkBackgroundColor) {
1187 | return;
1188 | }
1189 | [self.highlightedLinkBackgroundColor setFill];
1190 |
1191 | NSRange linkRange = nil != self.touchedLink ? self.touchedLink.range : self.actionSheetLink.range;
1192 |
1193 | CFArrayRef lines = CTFrameGetLines(self.textFrame);
1194 | CFIndex count = CFArrayGetCount(lines);
1195 | CGPoint lineOrigins[count];
1196 | CTFrameGetLineOrigins(self.textFrame, CFRangeMake(0, 0), lineOrigins);
1197 | NSInteger numberOfLines = [self numberOfDisplayedLines];
1198 |
1199 | CGContextRef ctx = UIGraphicsGetCurrentContext();
1200 |
1201 | for (CFIndex i = 0; i < numberOfLines; i++) {
1202 | CTLineRef line = CFArrayGetValueAtIndex(lines, i);
1203 |
1204 | CFRange stringRange = CTLineGetStringRange(line);
1205 | NSRange lineRange = NSMakeRange(stringRange.location, stringRange.length);
1206 | NSRange intersectedRange = NSIntersectionRange(lineRange, linkRange);
1207 | if (intersectedRange.length == 0) {
1208 | continue;
1209 | }
1210 |
1211 | CGRect highlightRect = [self _rectForRange:linkRange inLine:line lineOrigin:lineOrigins[i]];
1212 | highlightRect = CGRectOffset(highlightRect, 0, -rect.origin.y);
1213 |
1214 | if (!CGRectIsEmpty(highlightRect)) {
1215 | CGFloat pi = (CGFloat)M_PI;
1216 |
1217 | CGFloat radius = 1.0f;
1218 | CGContextMoveToPoint(ctx, highlightRect.origin.x, highlightRect.origin.y + radius);
1219 | CGContextAddLineToPoint(ctx, highlightRect.origin.x, highlightRect.origin.y + highlightRect.size.height - radius);
1220 | CGContextAddArc(ctx, highlightRect.origin.x + radius, highlightRect.origin.y + highlightRect.size.height - radius,
1221 | radius, pi, pi / 2.0f, 1.0f);
1222 | CGContextAddLineToPoint(ctx, highlightRect.origin.x + highlightRect.size.width - radius,
1223 | highlightRect.origin.y + highlightRect.size.height);
1224 | CGContextAddArc(ctx, highlightRect.origin.x + highlightRect.size.width - radius,
1225 | highlightRect.origin.y + highlightRect.size.height - radius, radius, pi / 2, 0.0f, 1.0f);
1226 | CGContextAddLineToPoint(ctx, highlightRect.origin.x + highlightRect.size.width, highlightRect.origin.y + radius);
1227 | CGContextAddArc(ctx, highlightRect.origin.x + highlightRect.size.width - radius, highlightRect.origin.y + radius,
1228 | radius, 0.0f, -pi / 2.0f, 1.0f);
1229 | CGContextAddLineToPoint(ctx, highlightRect.origin.x + radius, highlightRect.origin.y);
1230 | CGContextAddArc(ctx, highlightRect.origin.x + radius, highlightRect.origin.y + radius, radius,
1231 | -pi / 2, pi, 1);
1232 | CGContextFillPath(ctx);
1233 | }
1234 | }
1235 | }
1236 |
1237 | - (void)drawAttributedString:(NSAttributedString *)attributedString rect:(CGRect)rect {
1238 | CGContextRef ctx = UIGraphicsGetCurrentContext();
1239 |
1240 | // This logic adapted from @mattt's TTTAttributedLabel
1241 | // https://github.com/mattt/TTTAttributedLabel
1242 |
1243 | CFArrayRef lines = CTFrameGetLines(self.textFrame);
1244 | NSInteger numberOfLines = [self numberOfDisplayedLines];
1245 |
1246 | BOOL truncatesLastLine = (self.lineBreakMode == NSLineBreakByTruncatingTail);
1247 | CGPoint lineOrigins[numberOfLines];
1248 | CTFrameGetLineOrigins(self.textFrame, CFRangeMake(0, numberOfLines), lineOrigins);
1249 |
1250 | for (CFIndex lineIndex = 0; lineIndex < numberOfLines; lineIndex++) {
1251 | CGPoint lineOrigin = lineOrigins[lineIndex];
1252 | lineOrigin.y -= rect.origin.y; // adjust for verticalTextAlignment
1253 | CGContextSetTextPosition(ctx, lineOrigin.x, lineOrigin.y);
1254 | CTLineRef line = CFArrayGetValueAtIndex(lines, lineIndex);
1255 |
1256 | BOOL shouldDrawLine = YES;
1257 |
1258 | if (truncatesLastLine && lineIndex == numberOfLines - 1) {
1259 | // Does the last line need truncation?
1260 | CFRange lastLineRange = CTLineGetStringRange(line);
1261 | if (lastLineRange.location + lastLineRange.length < (CFIndex)attributedString.length) {
1262 | CTLineTruncationType truncationType = kCTLineTruncationEnd;
1263 | NSUInteger truncationAttributePosition = lastLineRange.location + lastLineRange.length - 1;
1264 |
1265 | NSAttributedString* tokenAttributedString;
1266 | {
1267 | NSDictionary *tokenAttributes = [attributedString attributesAtIndex:truncationAttributePosition
1268 | effectiveRange:NULL];
1269 | NSString* tokenString = ((nil == self.tailTruncationString)
1270 | ? kEllipsesCharacter
1271 | : self.tailTruncationString);
1272 | tokenAttributedString = [[NSAttributedString alloc] initWithString:tokenString attributes:tokenAttributes];
1273 | }
1274 |
1275 | CTLineRef truncationToken = CTLineCreateWithAttributedString((__bridge CFAttributedStringRef)tokenAttributedString);
1276 |
1277 | NSMutableAttributedString *truncationString = [[attributedString attributedSubstringFromRange:NSMakeRange(lastLineRange.location, lastLineRange.length)] mutableCopy];
1278 | if (lastLineRange.length > 0) {
1279 | // Remove any whitespace at the end of the line.
1280 | unichar lastCharacter = [[truncationString string] characterAtIndex:lastLineRange.length - 1];
1281 | if ([[NSCharacterSet whitespaceAndNewlineCharacterSet] characterIsMember:lastCharacter]) {
1282 | [truncationString deleteCharactersInRange:NSMakeRange(lastLineRange.length - 1, 1)];
1283 | }
1284 | }
1285 | [truncationString appendAttributedString:tokenAttributedString];
1286 |
1287 | CTLineRef truncationLine = CTLineCreateWithAttributedString((__bridge CFAttributedStringRef)truncationString);
1288 | CTLineRef truncatedLine = CTLineCreateTruncatedLine(truncationLine, rect.size.width, truncationType, truncationToken);
1289 | if (!truncatedLine) {
1290 | // If the line is not as wide as the truncationToken, truncatedLine is NULL
1291 | truncatedLine = CFRetain(truncationToken);
1292 | }
1293 | CFRelease(truncationLine);
1294 | CFRelease(truncationToken);
1295 |
1296 | CTLineDraw(truncatedLine, ctx);
1297 | CFRelease(truncatedLine);
1298 |
1299 | shouldDrawLine = NO;
1300 | }
1301 | }
1302 |
1303 | if (shouldDrawLine) {
1304 | CTLineDraw(line, ctx);
1305 | }
1306 | }
1307 | }
1308 |
1309 | - (void)drawTextInRect:(CGRect)rect {
1310 | if (NIVerticalTextAlignmentTop != self.verticalTextAlignment) {
1311 | rect.origin.y = [self _verticalOffsetForBounds:rect];
1312 | }
1313 |
1314 | if (self.autoDetectLinks) {
1315 | [self detectLinks];
1316 | }
1317 |
1318 | NSMutableAttributedString* attributedStringWithLinks = [self mutableAttributedStringWithAdditions];
1319 | if (self.detectedlinkLocations.count > 0 || self.explicitLinkLocations.count > 0) {
1320 | self.userInteractionEnabled = YES;
1321 | }
1322 |
1323 | if (nil != attributedStringWithLinks) {
1324 | CGContextRef ctx = UIGraphicsGetCurrentContext();
1325 | CGContextSaveGState(ctx);
1326 |
1327 | CGAffineTransform transform = [self _transformForCoreText];
1328 | CGContextConcatCTM(ctx, transform);
1329 |
1330 | [self drawImages];
1331 | [self drawHighlightWithRect:rect];
1332 |
1333 | if (nil != self.shadowColor) {
1334 | CGContextSetShadowWithColor(ctx, self.shadowOffset, self.shadowBlur, self.shadowColor.CGColor);
1335 | }
1336 |
1337 | [self drawAttributedString:attributedStringWithLinks rect:rect];
1338 |
1339 | CGContextRestoreGState(ctx);
1340 |
1341 | } else {
1342 | [super drawTextInRect:rect];
1343 | }
1344 | }
1345 |
1346 | #pragma mark - Accessibility
1347 |
1348 | - (void)invalidateAccessibleElements {
1349 | self.accessibleElements = nil;
1350 | }
1351 |
1352 | - (NSArray *)accessibleElements {
1353 | if (nil != _accessibleElements) {
1354 | return _accessibleElements;
1355 | }
1356 |
1357 | NSMutableArray* accessibleElements = [NSMutableArray array];
1358 |
1359 | // NSArray arrayWithArray:self.detectedlinkLocations ensures that we're not working with a nil
1360 | // array.
1361 | NSArray* allLinks = [[NSArray arrayWithArray:self.detectedlinkLocations]
1362 | arrayByAddingObjectsFromArray:self.explicitLinkLocations];
1363 |
1364 | for (NSTextCheckingResult* result in allLinks) {
1365 | NSArray* rectsForLink = [self _rectsForLink:result];
1366 | if (0 == rectsForLink.count) {
1367 | continue;
1368 | }
1369 |
1370 | NSString* label = [self.mutableAttributedString.string substringWithRange:result.range];
1371 | for (NSValue* rectValue in rectsForLink) {
1372 | UIAccessibilityElement* element = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self];
1373 | element.accessibilityLabel = label;
1374 | element.accessibilityFrame = [self convertRect:rectValue.CGRectValue toView:self.window];
1375 | element.accessibilityTraits = UIAccessibilityTraitLink;
1376 | [accessibleElements addObject:element];
1377 | }
1378 | }
1379 |
1380 | UIAccessibilityElement* element = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self];
1381 | element.accessibilityLabel = self.attributedText.string;
1382 | element.accessibilityFrame = [self convertRect:self.bounds toView:self.window];
1383 | element.accessibilityTraits = UIAccessibilityTraitNone;
1384 | if (_shouldSortLinksLast) {
1385 | [accessibleElements insertObject:element atIndex:0];
1386 | } else {
1387 | [accessibleElements addObject:element];
1388 | }
1389 |
1390 | _accessibleElements = [accessibleElements copy];
1391 | return _accessibleElements;
1392 | }
1393 |
1394 | - (BOOL)isAccessibilityElement {
1395 | return NO; // We handle accessibility for this element in -accessibleElements.
1396 | }
1397 |
1398 | - (NSInteger)accessibilityElementCount {
1399 | return self.accessibleElements.count;
1400 | }
1401 |
1402 | - (id)accessibilityElementAtIndex:(NSInteger)index {
1403 | return [self.accessibleElements objectAtIndex:index];
1404 | }
1405 |
1406 | - (NSInteger)indexOfAccessibilityElement:(id)element {
1407 | return [self.accessibleElements indexOfObject:element];
1408 | }
1409 |
1410 | #pragma mark - UIActionSheetDelegate
1411 |
1412 | - (void)actionSheet:(UIActionSheet*)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
1413 | if (NSTextCheckingTypeLink == self.actionSheetLink.resultType) {
1414 | if (buttonIndex == 0) {
1415 | [[UIApplication sharedApplication] openURL:self.actionSheetLink.URL];
1416 |
1417 | } else if (buttonIndex == 1) {
1418 | if ([self.actionSheetLink.URL.scheme isEqualToString:@"mailto"]) {
1419 | [[UIPasteboard generalPasteboard] setString:self.actionSheetLink.URL.resourceSpecifier];
1420 |
1421 | } else {
1422 | [[UIPasteboard generalPasteboard] setURL:self.actionSheetLink.URL];
1423 | }
1424 | }
1425 |
1426 | } else if (NSTextCheckingTypePhoneNumber == self.actionSheetLink.resultType) {
1427 | if (buttonIndex == 0) {
1428 | [[UIApplication sharedApplication] openURL:[NSURL URLWithString:[@"tel:" stringByAppendingString:self.actionSheetLink.phoneNumber]]];
1429 |
1430 | } else if (buttonIndex == 1) {
1431 | [[UIPasteboard generalPasteboard] setString:self.actionSheetLink.phoneNumber];
1432 | }
1433 |
1434 | } else if (NSTextCheckingTypeAddress == self.actionSheetLink.resultType) {
1435 | NSString* address = [self.mutableAttributedString.string substringWithRange:self.actionSheetLink.range];
1436 | if (buttonIndex == 0) {
1437 | [[UIApplication sharedApplication] openURL:[NSURL URLWithString:[[@"http://maps.google.com/maps?q=" stringByAppendingString:address] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];
1438 |
1439 | } else if (buttonIndex == 1) {
1440 | [[UIPasteboard generalPasteboard] setString:address];
1441 | }
1442 |
1443 | } else {
1444 | // Unsupported data type only allows the user to copy.
1445 | if (buttonIndex == 0) {
1446 | NSString* text = [self.mutableAttributedString.string substringWithRange:self.actionSheetLink.range];
1447 | [[UIPasteboard generalPasteboard] setString:text];
1448 | }
1449 | }
1450 |
1451 | self.actionSheetLink = nil;
1452 | [self setNeedsDisplay];
1453 | }
1454 |
1455 | - (void)actionSheetCancel:(UIActionSheet *)actionSheet {
1456 | self.actionSheetLink = nil;
1457 | [self setNeedsDisplay];
1458 | }
1459 |
1460 | #pragma mark - Inline Image Support
1461 |
1462 | CGFloat NIImageDelegateGetAscentCallback(void* refCon) {
1463 | NIAttributedLabelImage *labelImage = (__bridge NIAttributedLabelImage *)refCon;
1464 |
1465 | switch (labelImage.verticalTextAlignment) {
1466 | case NIVerticalTextAlignmentMiddle:
1467 | {
1468 | CGFloat ascent = labelImage.fontAscent;
1469 | CGFloat descent = labelImage.fontDescent;
1470 | CGFloat baselineFromMid = (ascent + descent) / 2 - descent;
1471 |
1472 | return labelImage.boxSize.height / 2 + baselineFromMid;
1473 | }
1474 | case NIVerticalTextAlignmentTop:
1475 | return labelImage.fontAscent;
1476 | case NIVerticalTextAlignmentBottom:
1477 | default:
1478 | return labelImage.boxSize.height - labelImage.fontDescent;
1479 | }
1480 | }
1481 |
1482 | CGFloat NIImageDelegateGetDescentCallback(void* refCon) {
1483 | NIAttributedLabelImage *labelImage = (__bridge NIAttributedLabelImage *)refCon;
1484 |
1485 | switch (labelImage.verticalTextAlignment) {
1486 | case NIVerticalTextAlignmentMiddle:
1487 | {
1488 | CGFloat ascent = labelImage.fontAscent;
1489 | CGFloat descent = labelImage.fontDescent;
1490 | CGFloat baselineFromMid = (ascent + descent) / 2 - descent;
1491 |
1492 | return labelImage.boxSize.height / 2 - baselineFromMid;
1493 | }
1494 | case NIVerticalTextAlignmentTop:
1495 | return labelImage.boxSize.height - labelImage.fontAscent;
1496 | case NIVerticalTextAlignmentBottom:
1497 | default:
1498 | return labelImage.fontDescent;
1499 | }
1500 | }
1501 |
1502 | CGFloat NIImageDelegateGetWidthCallback(void* refCon) {
1503 | NIAttributedLabelImage *labelImage = (__bridge NIAttributedLabelImage *)refCon;
1504 | return labelImage.image.size.width + labelImage.margins.left + labelImage.margins.right;
1505 | }
1506 |
1507 | - (void)insertImage:(UIImage *)image atIndex:(NSInteger)index {
1508 | [self insertImage:image atIndex:index margins:UIEdgeInsetsZero verticalTextAlignment:NIVerticalTextAlignmentBottom];
1509 | }
1510 |
1511 | - (void)insertImage:(UIImage *)image atIndex:(NSInteger)index margins:(UIEdgeInsets)margins {
1512 | [self insertImage:image atIndex:index margins:margins verticalTextAlignment:NIVerticalTextAlignmentBottom];
1513 | }
1514 |
1515 | - (void)insertImage:(UIImage *)image atIndex:(NSInteger)index margins:(UIEdgeInsets)margins verticalTextAlignment:(NIVerticalTextAlignment)verticalTextAlignment {
1516 | NIAttributedLabelImage* labelImage = [[NIAttributedLabelImage alloc] init];
1517 | labelImage.index = index;
1518 | labelImage.image = image;
1519 | labelImage.margins = margins;
1520 | labelImage.verticalTextAlignment = verticalTextAlignment;
1521 | if (nil == self.images) {
1522 | self.images = [NSMutableArray array];
1523 | }
1524 | [self.images addObject:labelImage];
1525 | }
1526 |
1527 | @end
1528 |
1529 | @implementation NIAttributedLabel (ConversionUtilities)
1530 |
1531 | + (CTTextAlignment)alignmentFromUITextAlignment:(NSTextAlignment)alignment {
1532 | switch (alignment) {
1533 | case NSTextAlignmentLeft: return kCTLeftTextAlignment;
1534 | case NSTextAlignmentCenter: return kCTCenterTextAlignment;
1535 | case NSTextAlignmentRight: return kCTRightTextAlignment;
1536 | case NSTextAlignmentJustified: return kCTJustifiedTextAlignment;
1537 | default: return kCTNaturalTextAlignment;
1538 | }
1539 | }
1540 |
1541 | + (CTLineBreakMode)lineBreakModeFromUILineBreakMode:(NSLineBreakMode)lineBreakMode {
1542 | switch (lineBreakMode) {
1543 | case NSLineBreakByWordWrapping: return kCTLineBreakByWordWrapping;
1544 | case NSLineBreakByCharWrapping: return kCTLineBreakByCharWrapping;
1545 | case NSLineBreakByClipping: return kCTLineBreakByClipping;
1546 | case NSLineBreakByTruncatingHead: return kCTLineBreakByTruncatingHead;
1547 | case NSLineBreakByTruncatingTail: return kCTLineBreakByWordWrapping; // We handle truncation ourself.
1548 | case NSLineBreakByTruncatingMiddle: return kCTLineBreakByTruncatingMiddle;
1549 | default: return 0;
1550 | }
1551 | }
1552 |
1553 | + (NSMutableAttributedString *)mutableAttributedStringFromLabel:(UILabel *)label {
1554 | NSMutableAttributedString* attributedString = nil;
1555 |
1556 | if (label.text.length > 0) {
1557 | attributedString = [[NSMutableAttributedString alloc] initWithString:label.text];
1558 |
1559 | [attributedString nimbuskit_setFont:label.font];
1560 | [attributedString nimbuskit_setTextColor:label.textColor];
1561 |
1562 | CTTextAlignment textAlignment = [self alignmentFromUITextAlignment:label.textAlignment];
1563 | CTLineBreakMode lineBreak = [self.class lineBreakModeFromUILineBreakMode:label.lineBreakMode];
1564 |
1565 | CGFloat lineHeight = 0;
1566 | if ([label isKindOfClass:[NIAttributedLabel class]]) {
1567 | lineHeight = [(NIAttributedLabel *)label lineHeight];
1568 | }
1569 | [attributedString nimbuskit_setTextAlignment:textAlignment lineBreakMode:lineBreak lineHeight:lineHeight];
1570 | }
1571 |
1572 | return attributedString;
1573 | }
1574 |
1575 | @end
1576 |
--------------------------------------------------------------------------------
/src/NSMutableAttributedString+NimbusKitAttributedLabel.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2011-present, NimbusKit. All rights reserved.
3 | Originally created by Roger Chapman
4 |
5 | This source code is licensed under the BSD-style license found in the LICENSE file in the root
6 | directory of this source tree and at the http://nimbuskit.info/license url. An additional grant of
7 | patent rights can be found in the PATENTS file in the same directory and url.
8 | */
9 |
10 | #import
11 | #import
12 | #import
13 |
14 | /**
15 | * This NimbusKit extension of the NSMutableAttributedString class provides a number of convenience
16 | * methods for setting styles on attributed strings.
17 | *
18 | * All methods will remove the attribute from the modification range before applying the changed
19 | * attribute.
20 | *
21 | * @ingroup NimbusKitAttributedLabel
22 | */
23 | @interface NSMutableAttributedString (NimbusKitAttributedLabel)
24 |
25 | - (void)nimbuskit_setFont:(UIFont *)font;
26 | - (void)nimbuskit_setTextAlignment:(CTTextAlignment)textAlignment lineBreakMode:(CTLineBreakMode)lineBreakMode lineHeight:(CGFloat)lineHeight;
27 | - (void)nimbuskit_setTextColor:(UIColor *)color;
28 | - (void)nimbuskit_setBackgroundColor:(UIColor *)color;
29 | - (void)nimbuskit_setLigaturesEnabled:(BOOL)enabled;
30 | - (void)nimbuskit_setKern:(CGFloat)kern;
31 | - (void)nimbuskit_setUnderlineStyle:(CTUnderlineStyle)style modifier:(CTUnderlineStyleModifiers)modifier;
32 | - (void)nimbuskit_setStrokeWidth:(CGFloat)width;
33 | - (void)nimbuskit_setStrokeColor:(UIColor *)color;
34 | - (void)nimbuskit_setLetterpressEnabled:(BOOL)enabled;
35 |
36 | - (void)nimbuskit_setFont:(UIFont *)font range:(NSRange)range;
37 | - (void)nimbuskit_setTextAlignment:(CTTextAlignment)textAlignment lineBreakMode:(CTLineBreakMode)lineBreakMode lineHeight:(CGFloat)lineHeight range:(NSRange)range;
38 | - (void)nimbuskit_setTextColor:(UIColor *)color range:(NSRange)range;
39 | - (void)nimbuskit_setBackgroundColor:(UIColor *)color range:(NSRange)range;
40 | - (void)nimbuskit_setLigaturesEnabled:(BOOL)enabled range:(NSRange)range;
41 | - (void)nimbuskit_setKern:(CGFloat)kern range:(NSRange)range;
42 | - (void)nimbuskit_setUnderlineStyle:(CTUnderlineStyle)style modifier:(CTUnderlineStyleModifiers)modifier range:(NSRange)range;
43 | - (void)nimbuskit_setStrokeWidth:(CGFloat)width range:(NSRange)range;
44 | - (void)nimbuskit_setStrokeColor:(UIColor *)color range:(NSRange)range;
45 | - (void)nimbuskit_setLetterpressEnabled:(BOOL)enabled range:(NSRange)range;
46 |
47 | @end
48 |
49 | /** @name Modifying Styles for the Entire String */
50 |
51 | /**
52 | * Sets the font for the entire string.
53 | *
54 | * @sa nimbuskit_setFont:range:
55 | * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setFont:
56 | */
57 |
58 | /**
59 | * Sets the text alignment and the line break mode for the entire string.
60 | *
61 | * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setTextAlignment:lineBreakMode:lineHeight:
62 | */
63 |
64 | /**
65 | * Sets the text color for the entire string.
66 | *
67 | * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setTextColor:
68 | */
69 |
70 | /**
71 | * Sets the background color for the entire string.
72 | *
73 | * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setBackgroundColor:
74 | */
75 |
76 | /**
77 | * Sets whether or not ligatures are enabled for the entire string.
78 | *
79 | * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setLigaturesEnabled:
80 | */
81 |
82 | /**
83 | * Sets the text kern for the entire string.
84 | *
85 | * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setKern:
86 | */
87 |
88 | /**
89 | * Sets the underline style and modifier for the entire string.
90 | *
91 | * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setUnderlineStyle:modifier:
92 | */
93 |
94 | /**
95 | * Sets the stroke width for the entire string.
96 | *
97 | * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setStrokeWidth:
98 | */
99 |
100 | /**
101 | * Sets the stroke color for the entire string.
102 | *
103 | * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setStrokeColor:
104 | */
105 |
106 | /**
107 | * Sets whether or not the letterpress text style is enabled for the entire string.
108 | *
109 | * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setLetterpressEnabled:
110 | */
111 |
112 | /** @name Modifying Styles for Ranges of the String */
113 |
114 | /**
115 | * Sets the font for a given range.
116 | *
117 | * @sa nimbuskit_setFont:
118 | * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setFont:range:
119 | */
120 |
121 | /**
122 | * Sets the text alignment and line break mode for a given range.
123 | *
124 | * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setTextAlignment:lineBreakMode:lineHeight:range:
125 | */
126 |
127 | /**
128 | * Sets the text color for a given range.
129 | *
130 | * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setTextColor:range:
131 | */
132 |
133 | /**
134 | * Sets the background color for a given range.
135 | *
136 | * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setBackgroundColor:range:
137 | */
138 |
139 | /**
140 | * Sets whether or not ligatures are enabled for a given range.
141 | *
142 | * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setLigaturesEnabled:range:
143 | */
144 |
145 | /**
146 | * Sets the text kern for a given range.
147 | *
148 | * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setKern:range:
149 | */
150 |
151 | /**
152 | * Sets the underline style and modifier for a given range.
153 | *
154 | * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setUnderlineStyle:modifier:range:
155 | */
156 |
157 | /**
158 | * Sets the stroke width for a given range.
159 | *
160 | * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setStrokeWidth:range:
161 | */
162 |
163 | /**
164 | * Sets the stroke color for a given range.
165 | *
166 | * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setStrokeColor:range:
167 | */
168 |
169 | /**
170 | * Sets whether or not the letterpress text style is enabled for a given range.
171 | *
172 | * Does nothing on pre-iOS 7 devices.
173 | *
174 | * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setLetterpressEnabled:range:
175 | */
176 |
--------------------------------------------------------------------------------
/src/NSMutableAttributedString+NimbusKitAttributedLabel.m:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2011-present, NimbusKit. All rights reserved.
3 | Originally created by Roger Chapman
4 |
5 | This source code is licensed under the BSD-style license found in the LICENSE file in the root
6 | directory of this source tree and at the http://nimbuskit.info/license url. An additional grant of
7 | patent rights can be found in the PATENTS file in the same directory and url.
8 | */
9 |
10 | #import "NSMutableAttributedString+NimbusKitAttributedLabel.h"
11 |
12 | #import "NimbusKitBasics.h"
13 |
14 | #if !defined(__has_feature) || !__has_feature(objc_arc)
15 | #error "NimbusKit requires ARC support."
16 | #endif
17 |
18 | #if __IPHONE_OS_VERSION_MIN_REQUIRED < NI_IOS_6_0
19 | #error "NIAttributedLabel requires iOS 6 or higher."
20 | #endif
21 |
22 | NI_FIX_CATEGORY_BUG(NSMutableAttributedStringNimbusKitAttributedLabel)
23 |
24 | @implementation NSMutableAttributedString (NimbusKitAttributedLabel)
25 |
26 | + (NSLineBreakMode)nimbuskit_lineBreakModeFromCTLineBreakMode:(CTLineBreakMode)mode {
27 | switch (mode) {
28 | case kCTLineBreakByWordWrapping: return NSLineBreakByWordWrapping;
29 | case kCTLineBreakByCharWrapping: return NSLineBreakByCharWrapping;
30 | case kCTLineBreakByClipping: return NSLineBreakByClipping;
31 | case kCTLineBreakByTruncatingHead: return NSLineBreakByTruncatingHead;
32 | case kCTLineBreakByTruncatingTail: return NSLineBreakByTruncatingTail;
33 | case kCTLineBreakByTruncatingMiddle: return NSLineBreakByTruncatingMiddle;
34 | }
35 | }
36 |
37 | - (NSRange)nimbuskit_rangeOfEntireString {
38 | return NSMakeRange(0, self.length);
39 | }
40 |
41 | - (void)nimbuskit_setFont:(UIFont*)font {
42 | [self nimbuskit_setFont:font range:self.nimbuskit_rangeOfEntireString];
43 | }
44 |
45 | - (void)nimbuskit_setTextAlignment:(CTTextAlignment)textAlignment lineBreakMode:(CTLineBreakMode)lineBreakMode lineHeight:(CGFloat)lineHeight {
46 | [self nimbuskit_setTextAlignment:textAlignment
47 | lineBreakMode:lineBreakMode
48 | lineHeight:lineHeight
49 | range:self.nimbuskit_rangeOfEntireString];
50 | }
51 |
52 | - (void)nimbuskit_setTextColor:(UIColor *)color {
53 | [self nimbuskit_setTextColor:color range:self.nimbuskit_rangeOfEntireString];
54 | }
55 |
56 | - (void)nimbuskit_setBackgroundColor:(UIColor *)color {
57 | [self nimbuskit_setBackgroundColor:color range:self.nimbuskit_rangeOfEntireString];
58 | }
59 |
60 | - (void)nimbuskit_setLigaturesEnabled:(BOOL)enabled {
61 | [self nimbuskit_setLigaturesEnabled:enabled range:self.nimbuskit_rangeOfEntireString];
62 | }
63 |
64 | - (void)nimbuskit_setKern:(CGFloat)kern {
65 | [self nimbuskit_setKern:kern range:self.nimbuskit_rangeOfEntireString];
66 | }
67 |
68 | - (void)nimbuskit_setUnderlineStyle:(CTUnderlineStyle)style modifier:(CTUnderlineStyleModifiers)modifier {
69 | [self nimbuskit_setUnderlineStyle:style modifier:modifier range:self.nimbuskit_rangeOfEntireString];
70 | }
71 |
72 | - (void)nimbuskit_setStrokeWidth:(CGFloat)width {
73 | [self nimbuskit_setStrokeWidth:width range:self.nimbuskit_rangeOfEntireString];
74 | }
75 |
76 | - (void)nimbuskit_setStrokeColor:(UIColor *)color {
77 | [self nimbuskit_setStrokeColor:color range:self.nimbuskit_rangeOfEntireString];
78 | }
79 |
80 | - (void)nimbuskit_setLetterpressEnabled:(BOOL)enabled {
81 | [self nimbuskit_setLetterpressEnabled:enabled range:self.nimbuskit_rangeOfEntireString];
82 | }
83 |
84 | - (void)nimbuskit_setFont:(UIFont *)font range:(NSRange)range {
85 | [self removeAttribute:NSFontAttributeName range:range];
86 |
87 | if (nil != font) {
88 | [self addAttribute:NSFontAttributeName value:font range:range];
89 | }
90 | }
91 |
92 | - (void)nimbuskit_setTextAlignment:(CTTextAlignment)textAlignment
93 | lineBreakMode:(CTLineBreakMode)lineBreakMode
94 | lineHeight:(CGFloat)lineHeight
95 | range:(NSRange)range {
96 | NSMutableParagraphStyle* paragraphStyle = [[NSMutableParagraphStyle alloc] init];
97 | paragraphStyle.alignment = NSTextAlignmentFromCTTextAlignment(textAlignment);
98 | paragraphStyle.lineBreakMode = [[self class] nimbuskit_lineBreakModeFromCTLineBreakMode:lineBreakMode];
99 | paragraphStyle.minimumLineHeight = lineHeight;
100 | paragraphStyle.maximumLineHeight = lineHeight;
101 | [self addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:range];
102 | }
103 |
104 | - (void)nimbuskit_setTextColor:(UIColor *)color range:(NSRange)range {
105 | [self removeAttribute:NSForegroundColorAttributeName range:range];
106 |
107 | if (nil != color) {
108 | [self addAttribute:NSForegroundColorAttributeName value:color range:range];
109 | }
110 | }
111 |
112 | - (void)nimbuskit_setBackgroundColor:(UIColor *)color range:(NSRange)range {
113 | [self removeAttribute:NSBackgroundColorAttributeName range:range];
114 |
115 | if (nil != color) {
116 | [self addAttribute:NSBackgroundColorAttributeName value:color range:range];
117 | }
118 | }
119 |
120 | - (void)nimbuskit_setLigaturesEnabled:(BOOL)enabled range:(NSRange)range {
121 | [self removeAttribute:NSLigatureAttributeName range:range];
122 | [self addAttribute:NSLigatureAttributeName value:@((enabled ? TRUE : FALSE)) range:range];
123 | }
124 |
125 | - (void)nimbuskit_setKern:(CGFloat)kern range:(NSRange)range {
126 | [self removeAttribute:NSKernAttributeName range:range];
127 | [self addAttribute:NSKernAttributeName value:@(kern) range:range];
128 | }
129 |
130 | - (void)nimbuskit_setUnderlineStyle:(CTUnderlineStyle)style modifier:(CTUnderlineStyleModifiers)modifier range:(NSRange)range {
131 | [self removeAttribute:NSUnderlineStyleAttributeName range:range];
132 | [self addAttribute:NSUnderlineStyleAttributeName value:@(style|modifier) range:range];
133 | }
134 |
135 | - (void)nimbuskit_setStrokeWidth:(CGFloat)width range:(NSRange)range {
136 | [self removeAttribute:NSStrokeWidthAttributeName range:range];
137 | [self addAttribute:NSStrokeWidthAttributeName value:@(width) range:range];
138 | }
139 |
140 | - (void)nimbuskit_setStrokeColor:(UIColor *)color range:(NSRange)range {
141 | [self removeAttribute:NSStrokeColorAttributeName range:range];
142 | if (nil != color.CGColor) {
143 | [self addAttribute:NSStrokeColorAttributeName value:color range:range];
144 | }
145 | }
146 |
147 | - (void)nimbuskit_setLetterpressEnabled:(BOOL)enabled range:(NSRange)range {
148 | // Introduced in iOS 7 - avoid crashing on older OSes.
149 | if (nil == NSTextEffectLetterpressStyle) {
150 | return;
151 | }
152 |
153 | [self removeAttribute:NSTextEffectAttributeName range:range];
154 | if (enabled) {
155 | [self addAttribute:NSTextEffectAttributeName value:NSTextEffectLetterpressStyle range:range];
156 | }
157 | }
158 |
159 | @end
160 |
--------------------------------------------------------------------------------
/src/NimbusKitAttributedLabel.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2011-present, NimbusKit. All rights reserved.
3 | Originally created by Roger Chapman
4 |
5 | This source code is licensed under the BSD-style license found in the LICENSE file in the root
6 | directory of this source tree and at the http://nimbuskit.info/license url. An additional grant of
7 | patent rights can be found in the PATENTS file in the same directory and url.
8 | */
9 |
10 | #ifndef _NIMBUSKIT_ATTRIBUTEDLABEL_H_
11 | #define _NIMBUSKIT_ATTRIBUTEDLABEL_H_
12 |
13 | #import "NIAttributedLabel.h"
14 |
15 | #import
16 | #import
17 | #import
18 |
19 | #pragma mark Current Version
20 |
21 | #ifndef NIMBUSKIT_ATTRIBUTEDLABEL_VERSION
22 | #define NIMBUSKIT_ATTRIBUTEDLABEL_VERSION NIMBUSKIT_ATTRIBUTEDLABEL_1_0_0
23 | #endif
24 |
25 | #endif // _NIMBUSKIT_ATTRIBUTEDLABEL_H_
26 |
27 | #pragma mark All Known Versions
28 |
29 | #ifndef NIMBUSKIT_ATTRIBUTEDLABEL_1_0_0
30 | #define NIMBUSKIT_ATTRIBUTEDLABEL_1_0_0 10000
31 | #endif
32 |
33 | #pragma mark Version Check
34 |
35 | #ifndef NI_SUPPRESS_VERSION_WARNINGS
36 |
37 | #if NIMBUSKIT_ATTRIBUTEDLABEL_VERSION < NIMBUSKIT_ATTRIBUTEDLABEL_1_0_0
38 |
39 | // These macros allow us to inline C-strings with macro values.
40 | #ifndef NI_MACRO_DEFER
41 | #define NI_MACRO_DEFER(M,...) M(__VA_ARGS__)
42 | #endif
43 | #ifndef NI_MACRO_STR
44 | #define NI_MACRO_STR(X) #X
45 | #endif
46 | #ifndef NI_MACRO_INLINE_STR
47 | #define NI_MACRO_INLINE_STR(str) NI_MACRO_DEFER(NI_MACRO_STR, str)
48 | #endif
49 |
50 | #pragma message "An older version (" NI_MACRO_INLINE_STR(NIMBUSKIT_ATTRIBUTEDLABEL_VERSION) ") of NimbusKit's Attributed Label was imported prior to this version (" NI_MACRO_INLINE_STR(NIMBUSKIT_ATTRIBUTEDLABEL_1_0_0) "). This may cause unexpected behavior. You may suppress this warning by defining NI_SUPPRESS_VERSION_WARNINGS"
51 |
52 | #endif // NIMBUSKIT_ATTRIBUTEDLABEL_VERSION check
53 |
54 | #endif // #ifndef NI_SUPPRESS_VERSION_WARNINGS
55 |
56 |
--------------------------------------------------------------------------------
/src/NimbusKitBasics.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2011-present, NimbusKit. All rights reserved.
3 |
4 | This source code is licensed under the BSD-style license found in the LICENSE file in the root
5 | directory of this source tree and at the http://nimbuskit.info/license url. An additional grant of
6 | patent rights can be found in the PATENTS file in the same directory and url.
7 | */
8 |
9 | #import
10 | #import
11 |
12 | // All macros #ifndef'd so that they can be individually overwritten if necessary.
13 |
14 | #ifndef _NIMBUSKIT_BASICS_H_
15 | #define _NIMBUSKIT_BASICS_H_
16 |
17 |
18 | #pragma mark Compiler Features
19 |
20 |
21 | #ifndef NI_DEPRECATED_METHOD
22 | #if __has_feature(attribute_deprecated_with_message)
23 |
24 | #define NI_DEPRECATED_METHOD(_msg) __attribute__((deprecated(_msg)))
25 |
26 | // Example:
27 | // - (void)yourDeprecatedMethod:(id)arg NI_DESIGNATED_INITIALIZER;
28 |
29 | #else
30 | #define NI_DEPRECATED_METHOD(_msg) __attribute__((deprecated))
31 | #endif // #if __has_feature
32 | #endif // #ifndef NI_DEPRECATED_METHOD
33 |
34 |
35 | #ifndef NI_DESIGNATED_INITIALIZER
36 | #if __has_attribute(objc_designated_initializer)
37 |
38 | #define NI_DESIGNATED_INITIALIZER __attribute((objc_designated_initializer))
39 |
40 | // Example:
41 | // - (instancetype)initWithArg:(id)arg NI_DESIGNATED_INITIALIZER;
42 |
43 | #else
44 | #define NI_DESIGNATED_INITIALIZER
45 | #endif // #if __has_feature
46 | #endif // #ifndef NI_DESIGNATED_INITIALIZER
47 |
48 |
49 | // For use in sources which contain only categories. Removes need for -force_load -all_load when building libraries.
50 | // Use once per source (.m) file (not per category).
51 | // name must be globally unique. Generally a good idea to prefix it.
52 | #ifndef NI_FIX_CATEGORY_BUG
53 | #define NI_FIX_CATEGORY_BUG(name) @interface NI_FIX_CATEGORY_BUG_##name : NSObject @end \
54 | @implementation NI_FIX_CATEGORY_BUG_##name @end
55 |
56 | // Example:
57 | // NI_FIX_CATEGORY_BUG(NSMutableAttributedStringNimbusAttributedLabel)
58 | // @implementation NSMutableAttributedString (NimbusAttributedLabel)
59 |
60 | #endif
61 |
62 |
63 | #ifndef NI_IS_FLAG_SET
64 | #define NI_IS_FLAG_SET(value, flag) (((value) & (flag)) == (flag))
65 |
66 | // Example:
67 | // if (NI_IS_FLAG_SET(autoresizingMask, UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight)
68 | // YES only if BOTH width and height are specified on the mask.
69 |
70 | #endif
71 |
72 |
73 | #pragma mark UIColor Generators
74 |
75 |
76 | #ifndef NI_RGBCOLOR
77 | #define NI_RGBCOLOR(r,g,b) [UIColor colorWithRed:(r)/255.0f green:(g)/255.0f blue:(b)/255.0f alpha:1]
78 |
79 | // Example:
80 | // NI_RGBCOLOR(255, 0, 255) for a vibrant debugging color
81 |
82 | #endif
83 |
84 |
85 | // `a` is a floating point value [0...1].
86 | #ifndef NI_RGBACOLOR
87 | #define NI_RGBACOLOR(r,g,b,a) [UIColor colorWithRed:(r)/255.0f green:(g)/255.0f blue:(b)/255.0f alpha:(a)]
88 |
89 | // Example:
90 | // NI_RGBACOLOR(255, 0, 255, 0.5) for a semi-translucently vibrant debugging color
91 |
92 | #endif
93 |
94 |
95 | #ifndef NI_HEXCOLOR
96 | #define NI_HEXCOLOR(hex) RGBCOLOR(((hex >> 16) & 0xFF), ((hex >> 8) & 0xFF), ((hex) & 0xFF))
97 |
98 | // Example:
99 | // NI_HEXCOLOR(0xFF00FF) for colors pasted from DigitalColor Meter (handy tool, use it!)
100 |
101 | #endif
102 |
103 |
104 | // `a` is a floating point value [0...1].
105 | #ifndef NI_HEXACOLOR
106 | #define NI_HEXACOLOR(hex,a) RGBACOLOR(((hex >> 16) & 0xFF), ((hex >> 8) & 0xFF), ((hex) & 0xFF), (a))
107 |
108 | // Example:
109 | // NI_HEXACOLOR(0xFF00FF, 0.5) for colors pasted from DigitalColor Meter, but with alpha
110 |
111 | #endif
112 |
113 |
114 | #pragma mark Tools for Debugging
115 |
116 |
117 | #if defined(DEBUG) && !defined(NI_DISABLE_DASSERT)
118 |
119 | #import
120 | #import
121 | #import
122 |
123 | // From: http://developer.apple.com/mac/library/qa/qa2004/qa1361.html
124 | CG_INLINE int NIIsInDebugger(void) {
125 | int mib[4];
126 | struct kinfo_proc info;
127 | size_t size;
128 |
129 | // Initialize the flags so that, if sysctl fails for some bizarre reason, we get a predictable result.
130 | info.kp_proc.p_flag = 0;
131 |
132 | // Initialize mib, which tells sysctl the info we want, in this case we're looking for information
133 | // about a specific process ID.
134 | mib[0] = CTL_KERN;
135 | mib[1] = KERN_PROC;
136 | mib[2] = KERN_PROC_PID;
137 | mib[3] = getpid();
138 |
139 | size = sizeof(info);
140 | sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0);
141 |
142 | // We're being debugged if the P_TRACED flag is set.
143 | return (info.kp_proc.p_flag & P_TRACED) != 0;
144 | }
145 |
146 | CG_INLINE BOOL NIIsRunningTests(void) {
147 | NSString* injectBundle = [[NSProcessInfo processInfo] environment][@"XCInjectBundle"];
148 | NSString* pathExtension = [injectBundle pathExtension];
149 | return ([pathExtension isEqualToString:@"octest"] || [pathExtension isEqualToString:@"xctest"]);
150 | }
151 |
152 | #if TARGET_IPHONE_SIMULATOR
153 | // We use the __asm__ in this macro so that when a break occurs, we don't have to step out of
154 | // a "breakInDebugger" function.
155 | #define NI_DASSERT(xx) { if (!(xx)) { NI_DPRINT(@"NI_DASSERT failed: %s", #xx); \
156 | if (NIIsInDebugger() && !NIIsRunningTests()) { __asm__("int $3\n" : : ); } } \
157 | } ((void)0)
158 | #else
159 | #define NI_DASSERT(xx) { if (!(xx)) { NI_DPRINT(@"NI_DASSERT failed: %s", #xx); \
160 | if (NIIsInDebugger() && !NIIsRunningTests()) { raise(SIGTRAP); } } \
161 | } ((void)0)
162 | #endif // #if TARGET_IPHONE_SIMULATOR
163 |
164 | #else
165 | // The ((void)0) syntax allows us force macros to be terminated with a `;` as though they were functions.
166 | #define NI_DASSERT(xx) ((void)0)
167 |
168 | #endif // #if defined(DEBUG) && !defined(NI_DISABLE_DASSERT)
169 |
170 |
171 | #if defined(DEBUG)
172 | #define NI_DPRINT(xx, ...) NSLog(@"%s(%d): " xx, __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
173 | #else
174 | #define NI_DPRINT(xx, ...) ((void)0)
175 | #endif
176 |
177 |
178 | #if defined(DEBUG)
179 | #define NI_DCONDITIONLOG(condition, xx, ...) { if ((condition)) { NI_DPRINT(xx, ##__VA_ARGS__); } } ((void)0)
180 | #else
181 | #define NI_DCONDITIONLOG(condition, xx, ...) ((void)0)
182 | #endif
183 |
184 |
185 | #define NI_DPRINTMETHODNAME() NI_DPRINT(@"%s", __PRETTY_FUNCTION__)
186 |
187 |
188 | #pragma mark Short-hand runtime checks.
189 |
190 |
191 | CG_INLINE BOOL NIIsPad(void) {
192 | return [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad;
193 | }
194 |
195 | CG_INLINE BOOL NIIsPhone(void) {
196 | return [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone;
197 | }
198 |
199 | CG_INLINE CGFloat NIScreenScale(void) {
200 | return [[UIScreen mainScreen] scale];
201 | }
202 |
203 | CG_INLINE BOOL NIIsRetina(void) {
204 | return [[UIScreen mainScreen] scale] == 2.f;
205 | }
206 |
207 | // Pre-iOS 7-safe mechanism for accessing UIView's tintColor.
208 | CG_INLINE UIColor* NITintColorForViewWithFallback(UIView* view, UIColor* fallbackColor) {
209 | return [view respondsToSelector:@selector(tintColor)] ? view.tintColor : fallbackColor;
210 | }
211 |
212 | CG_INLINE BOOL NIDeviceOSVersionIsAtLeast(double versionNumber) {
213 | return kCFCoreFoundationVersionNumber >= versionNumber;
214 | }
215 |
216 | #pragma mark iOS Version Numbers
217 |
218 | /** Released on July 11, 2008 */
219 | #define NI_IOS_2_0 20000
220 |
221 | /** Released on September 9, 2008 */
222 | #define NI_IOS_2_1 20100
223 |
224 | /** Released on November 21, 2008 */
225 | #define NI_IOS_2_2 20200
226 |
227 | /** Released on June 17, 2009 */
228 | #define NI_IOS_3_0 30000
229 |
230 | /** Released on September 9, 2009 */
231 | #define NI_IOS_3_1 30100
232 |
233 | /** Released on April 3, 2010 */
234 | #define NI_IOS_3_2 30200
235 |
236 | /** Released on June 21, 2010 */
237 | #define NI_IOS_4_0 40000
238 |
239 | /** Released on September 8, 2010 */
240 | #define NI_IOS_4_1 40100
241 |
242 | /** Released on November 22, 2010 */
243 | #define NI_IOS_4_2 40200
244 |
245 | /** Released on March 9, 2011 */
246 | #define NI_IOS_4_3 40300
247 |
248 | /** Released on October 12, 2011. */
249 | #define NI_IOS_5_0 50000
250 |
251 | /** Released on March 7, 2012. */
252 | #define NI_IOS_5_1 50100
253 |
254 | /** Released on September 19, 2012. */
255 | #define NI_IOS_6_0 60000
256 |
257 | /** Released on January 28, 2013. */
258 | #define NI_IOS_6_1 60100
259 |
260 | /** Released on September 18, 2013 */
261 | #define NI_IOS_7_0 70000
262 |
263 | #ifndef kCFCoreFoundationVersionNumber_iPhoneOS_2_0
264 | #define kCFCoreFoundationVersionNumber_iPhoneOS_2_0 478.23
265 | #endif
266 |
267 | #ifndef kCFCoreFoundationVersionNumber_iPhoneOS_2_1
268 | #define kCFCoreFoundationVersionNumber_iPhoneOS_2_1 478.26
269 | #endif
270 |
271 | #ifndef kCFCoreFoundationVersionNumber_iPhoneOS_2_2
272 | #define kCFCoreFoundationVersionNumber_iPhoneOS_2_2 478.29
273 | #endif
274 |
275 | #ifndef kCFCoreFoundationVersionNumber_iPhoneOS_3_0
276 | #define kCFCoreFoundationVersionNumber_iPhoneOS_3_0 478.47
277 | #endif
278 |
279 | #ifndef kCFCoreFoundationVersionNumber_iPhoneOS_3_1
280 | #define kCFCoreFoundationVersionNumber_iPhoneOS_3_1 478.52
281 | #endif
282 |
283 | #ifndef kCFCoreFoundationVersionNumber_iPhoneOS_3_2
284 | #define kCFCoreFoundationVersionNumber_iPhoneOS_3_2 478.61
285 | #endif
286 |
287 | #ifndef kCFCoreFoundationVersionNumber_iOS_4_0
288 | #define kCFCoreFoundationVersionNumber_iOS_4_0 550.32
289 | #endif
290 |
291 | #ifndef kCFCoreFoundationVersionNumber_iOS_4_1
292 | #define kCFCoreFoundationVersionNumber_iOS_4_1 550.38
293 | #endif
294 |
295 | #ifndef kCFCoreFoundationVersionNumber_iOS_4_2
296 | #define kCFCoreFoundationVersionNumber_iOS_4_2 550.52
297 | #endif
298 |
299 | #ifndef kCFCoreFoundationVersionNumber_iOS_4_3
300 | #define kCFCoreFoundationVersionNumber_iOS_4_3 550.52
301 | #endif
302 |
303 | #ifndef kCFCoreFoundationVersionNumber_iOS_5_0
304 | #define kCFCoreFoundationVersionNumber_iOS_5_0 675.00
305 | #endif
306 |
307 | #ifndef kCFCoreFoundationVersionNumber_iOS_5_1
308 | #define kCFCoreFoundationVersionNumber_iOS_5_1 690.10
309 | #endif
310 |
311 | #ifndef kCFCoreFoundationVersionNumber_iOS_6_0
312 | #define kCFCoreFoundationVersionNumber_iOS_6_0 793.00
313 | #endif
314 |
315 | #ifndef kCFCoreFoundationVersionNumber_iOS_6_1
316 | #define kCFCoreFoundationVersionNumber_iOS_6_1 793.00
317 | #endif
318 |
319 |
320 | #pragma mark 32/64 Bit Support
321 |
322 |
323 | #if CGFLOAT_IS_DOUBLE
324 | #define NI_CGFLOAT_EPSILON DBL_EPSILON
325 | #else
326 | #define NI_CGFLOAT_EPSILON FLT_EPSILON
327 | #endif
328 |
329 | #ifndef NI_DISABLE_GENERIC_MATH
330 |
331 | #import
332 |
333 | // Until tgmath.h is able to work with modules enabled, the following explicit remappings of the
334 | // common math functions are provided.
335 | // http://stackoverflow.com/questions/23333287/tgmath-h-doesnt-work-if-modules-are-enabled
336 | // http://www.openradar.me/16744288
337 |
338 | #undef acos
339 | #define acos(__x) __tg_acos(__tg_promote1((__x))(__x))
340 |
341 | #undef asin
342 | #define asin(__x) __tg_asin(__tg_promote1((__x))(__x))
343 |
344 | #undef atan
345 | #define atan(__x) __tg_atan(__tg_promote1((__x))(__x))
346 |
347 | #undef atan2
348 | #define atan2(__x, __y) __tg_atan2(__tg_promote2((__x), (__y))(__x), __tg_promote2((__x), (__y))(__y))
349 |
350 | #undef cos
351 | #define cos(__x) __tg_cos(__tg_promote1((__x))(__x))
352 |
353 | #undef sin
354 | #define sin(__x) __tg_sin(__tg_promote1((__x))(__x))
355 |
356 | #undef tan
357 | #define tan(__x) __tg_tan(__tg_promote1((__x))(__x))
358 |
359 | #undef acosh
360 | #define acosh(__x) __tg_acosh(__tg_promote1((__x))(__x))
361 |
362 | #undef asinh
363 | #define asinh(__x) __tg_asinh(__tg_promote1((__x))(__x))
364 |
365 | #undef atanh
366 | #define atanh(__x) __tg_atanh(__tg_promote1((__x))(__x))
367 |
368 | #undef cosh
369 | #define cosh(__x) __tg_cosh(__tg_promote1((__x))(__x))
370 |
371 | #undef sinh
372 | #define sinh(__x) __tg_sinh(__tg_promote1((__x))(__x))
373 |
374 | #undef tanh
375 | #define tanh(__x) __tg_tanh(__tg_promote1((__x))(__x))
376 |
377 | #undef exp
378 | #define exp(__x) __tg_exp(__tg_promote1((__x))(__x))
379 |
380 | #undef exp2
381 | #define exp2(__x) __tg_exp2(__tg_promote1((__x))(__x))
382 |
383 | #undef expm1
384 | #define expm1(__x) __tg_expm1(__tg_promote1((__x))(__x))
385 |
386 | #undef log
387 | #define log(__x) __tg_log(__tg_promote1((__x))(__x))
388 |
389 | #undef log10
390 | #define log10(__x) __tg_log10(__tg_promote1((__x))(__x))
391 |
392 | #undef log2
393 | #define log2(__x) __tg_log2(__tg_promote1((__x))(__x))
394 |
395 | #undef log1p
396 | #define log1p(__x) __tg_log1p(__tg_promote1((__x))(__x))
397 |
398 | #undef logb
399 | #define logb(__x) __tg_logb(__tg_promote1((__x))(__x))
400 |
401 | #undef fabs
402 | #define fabs(__x) __tg_fabs(__tg_promote1((__x))(__x))
403 |
404 | #undef cbrt
405 | #define cbrt(__x) __tg_cbrt(__tg_promote1((__x))(__x))
406 |
407 | #undef hypot
408 | #define hypot(__x, __y) __tg_hypot(__tg_promote2((__x), (__y))(__x), __tg_promote2((__x), (__y))(__y))
409 |
410 | #undef pow
411 | #define pow(__x, __y) __tg_pow(__tg_promote2((__x), (__y))(__x), __tg_promote2((__x), (__y))(__y))
412 |
413 | #undef sqrt
414 | #define sqrt(__x) __tg_sqrt(__tg_promote1((__x))(__x))
415 |
416 | #undef erf
417 | #define erf(__x) __tg_erf(__tg_promote1((__x))(__x))
418 |
419 | #undef erfc
420 | #define erfc(__x) __tg_erfc(__tg_promote1((__x))(__x))
421 |
422 | #undef lgamma
423 | #define lgamma(__x) __tg_lgamma(__tg_promote1((__x))(__x))
424 |
425 | #undef tgamma
426 | #define tgamma(__x) __tg_tgamma(__tg_promote1((__x))(__x))
427 |
428 | #undef ceil
429 | #define ceil(__x) __tg_ceil(__tg_promote1((__x))(__x))
430 |
431 | #undef floor
432 | #define floor(__x) __tg_floor(__tg_promote1((__x))(__x))
433 |
434 | #undef nearbyint
435 | #define nearbyint(__x) __tg_nearbyint(__tg_promote1((__x))(__x))
436 |
437 | #undef rint
438 | #define rint(__x) __tg_rint(__tg_promote1((__x))(__x))
439 |
440 | #undef round
441 | #define round(__x) __tg_round(__tg_promote1((__x))(__x))
442 |
443 | #undef trunc
444 | #define trunc(__x) __tg_trunc(__tg_promote1((__x))(__x))
445 |
446 | #undef fmod
447 | #define fmod(__x, __y) __tg_fmod(__tg_promote2((__x), (__y))(__x), __tg_promote2((__x), (__y))(__y))
448 |
449 | #undef remainder
450 | #define remainder(__x, __y) __tg_remainder(__tg_promote2((__x), (__y))(__x), __tg_promote2((__x), (__y))(__y))
451 |
452 | #undef copysign
453 | #define copysign(__x, __y) __tg_copysign(__tg_promote2((__x), (__y))(__x), __tg_promote2((__x), (__y))(__y))
454 |
455 | #undef nextafter
456 | #define nextafter(__x, __y) __tg_nextafter(__tg_promote2((__x), (__y))(__x), __tg_promote2((__x), (__y))(__y))
457 |
458 | #undef fdim
459 | #define fdim(__x, __y) __tg_fdim(__tg_promote2((__x), (__y))(__x), __tg_promote2((__x), (__y))(__y))
460 |
461 | #undef fmax
462 | #define fmax(__x, __y) __tg_fmax(__tg_promote2((__x), (__y))(__x), __tg_promote2((__x), (__y))(__y))
463 |
464 | #undef fmin
465 | #define fmin(__x, __y) __tg_fmin(__tg_promote2((__x), (__y))(__x), __tg_promote2((__x), (__y))(__y))
466 |
467 | #endif // #ifndef NI_DISABLE_GENERIC_MATH
468 |
469 | #pragma mark Current Version
470 |
471 | #ifndef NIMBUSKIT_BASICS_VERSION
472 | #define NIMBUSKIT_BASICS_VERSION NIMBUSKIT_BASICS_1_2_1
473 | #endif
474 |
475 | #endif // #ifndef _NIMBUSKIT_BASICS_H_
476 |
477 | #pragma mark All Known Versions
478 |
479 | #ifndef NIMBUSKIT_BASICS_1_0_0
480 | #define NIMBUSKIT_BASICS_1_0_0 10000
481 | #endif
482 |
483 | #ifndef NIMBUSKIT_BASICS_1_1_0
484 | #define NIMBUSKIT_BASICS_1_1_0 10100
485 | #endif
486 |
487 | #ifndef NIMBUSKIT_BASICS_1_2_0
488 | #define NIMBUSKIT_BASICS_1_2_0 10200
489 | #endif
490 |
491 | #ifndef NIMBUSKIT_BASICS_1_2_1
492 | #define NIMBUSKIT_BASICS_1_2_1 10201
493 | #endif
494 |
495 | #pragma mark Version Check
496 |
497 | #ifndef NI_SUPPRESS_VERSION_WARNINGS
498 |
499 | #if NIMBUSKIT_BASICS_VERSION < NIMBUSKIT_BASICS_1_2_1
500 |
501 | // These macros allow us to inline C-strings with macro values.
502 | #ifndef NI_MACRO_DEFER
503 | #define NI_MACRO_DEFER(M,...) M(__VA_ARGS__)
504 | #endif
505 | #ifndef NI_MACRO_STR
506 | #define NI_MACRO_STR(X) #X
507 | #endif
508 | #ifndef NI_MACRO_INLINE_STR
509 | #define NI_MACRO_INLINE_STR(str) NI_MACRO_DEFER(NI_MACRO_STR, str)
510 | #endif
511 |
512 | #pragma message "An older version (" NI_MACRO_INLINE_STR(NIMBUSKIT_BASICS_VERSION) ") of NimbusKit's Basics was imported prior to this version (" NI_MACRO_INLINE_STR(NIMBUSKIT_BASICS_1_2_1) "). This may cause unexpected behavior. You may suppress this warning by defining NI_SUPPRESS_VERSION_WARNINGS"
513 |
514 | #endif // NIMBUSKIT_BASICS_VERSION check
515 |
516 | #endif // #ifndef NI_SUPPRESS_VERSION_WARNINGS
517 |
518 | #pragma mark - ~~~ Docs ~~~
519 |
520 | /** @name Macros */
521 |
522 | /**
523 | * Marks a method or property as deprecated to the compiler.
524 | *
525 | * To be used like so:
526 | *
527 | * - (void)someMethod NI_DEPRECATED_METHOD("use someOtherMethod instead");
528 | *
529 | * Note that the macro expects a C-string (no @-prefix), not an Objective-C NSString.
530 | *
531 | * Any use of a deprecated method or property will flag a warning when compiling.
532 | *
533 | * @param msg A C-string explaining the deprecation. Used in the message
534 | * " is deprecated: %s".
535 | * @fn #NI_DEPRECATED_METHOD(msg)
536 | * @ingroup NimbusKitBasics
537 | */
538 |
539 | /**
540 | * Marks an initializer method as the designated initializer for a class.
541 | *
542 | * Causes Xcode to throw warnings if the initializer chain is not implemented correctly.
543 | * This macro can only be specified on a single initializer.
544 | *
545 | * @fn #NI_DESIGNATED_INITIALIZER
546 | * @ingroup NimbusKitBasics
547 | */
548 |
549 | /**
550 | * Force a category to be loaded when an app starts up.
551 | *
552 | * Add this macro in every source file that only contains a category implementation.
553 | *
554 | * When linking to a library, Xcode will NOT link symbols from .m source that only contain
555 | * categories. In order to force Xcode to do this you must use the -all_load or -force_load linker
556 | * flags.
557 | *
558 | * By placing this macro in any category .m source you generate an empty class that will cause Xcode
559 | * to link all of the contents of the .m without requiring the -all_load or -force_load flags.
560 | *
561 | * See http://developer.apple.com/library/mac/#qa/qa2006/qa1490.html for more info.
562 | *
563 | * @fn #NI_FIX_CATEGORY_BUG(name)
564 | * @ingroup NimbusKitBasics
565 | */
566 |
567 | /**
568 | * Checks whether a \p flag is set on \p value.
569 | *
570 | * This macro may be used to correctly check if a mask has a complex flag (more than one bit in the
571 | * flag) enabled.
572 | *
573 | * It is a common error to check for a flag by simply using the & operator, but this only checks if
574 | * ANY subset of the flag's bits are set, not that ALL of them are set.
575 | *
576 | * By using the & operator and then comparing the result to the original \p flag, we ensure that all
577 | * bits in \p flag are set on \p value. This macro simplifies that check.
578 | *
579 | * @fn #NI_IS_FLAG_SET(value, flag)
580 | * @ingroup NimbusKitBasics
581 | */
582 |
583 | /**
584 | * Creates an opaque UIColor object from a byte-value color definition.
585 | *
586 | * @fn #NI_RGBCOLOR(r,g,b)
587 | * @ingroup NimbusKitBasics
588 | */
589 |
590 | /**
591 | * Creates a UIColor object from a byte-value color definition and alpha transparency.
592 | *
593 | * @fn #NI_RGBACOLOR(r,g,b,a)
594 | * @ingroup NimbusKitBasics
595 | */
596 |
597 | /**
598 | * Creates an opaque UIColor object from a hex color definition of the form 0xRRGGBB.
599 | *
600 | * @fn #NI_HEXCOLOR(hex)
601 | * @ingroup NimbusKitBasics
602 | */
603 |
604 | /**
605 | * Creates a UIColor object from a hex color definition of the form 0xRRGGBB with alpha
606 | * transparency.
607 | *
608 | * @fn #NI_HEXACOLOR(hex,a)
609 | * @ingroup NimbusKitBasics
610 | */
611 |
612 | /** @name Querying the Debugger State */
613 |
614 | /**
615 | * Returns a Boolean value indicating whether or not a debugger is attached to the process.
616 | *
617 | * @fn NIIsInDebugger()
618 | * @ingroup NimbusKitBasics
619 | */
620 |
621 | /** @name Debug Assertions */
622 |
623 | /**
624 | * If this assertion fails then this macro mimics a breakpoint when a debugger is attached.
625 | *
626 | * This macro may be used to safely pause execution of a program before it enters crashland.
627 | *
628 | * The source for this macro is only compiler when the DEBUG flag is defined.
629 | * If you wish to explicitly disable NI_DASSERT from being compiled, define NI_DISABLE_DASSERT in
630 | * your target's preprocessor macros.
631 | *
632 | * @fn #NI_DASSERT(xx)
633 | * @ingroup NimbusKitBasics
634 | */
635 |
636 | /** @name Debug Logging */
637 |
638 | /**
639 | * Only writes to the log when `DEBUG` is defined.
640 | *
641 | * When `DEBUG` is defined, this log method will always write to the log, regardless of log levels.
642 | * It is used by all of the other logging methods in Nimbus' debugging library.
643 | *
644 | * @fn #NI_DPRINT(xx, ...)
645 | * @ingroup NimbusKitBasics
646 | */
647 |
648 | /**
649 | * Write the containing method's name to the log using NI_DPRINT.
650 | *
651 | * @fn #NI_DPRINTMETHODNAME()
652 | * @ingroup NimbusKitBasics
653 | */
654 |
655 | /**
656 | * Only writes to the log if \p condition is satisified.
657 | *
658 | * This macro powers the level-based loggers. It can also be used for conditionally enabling
659 | * families of logs.
660 | *
661 | * @fn #NI_DCONDITIONLOG(condition, xx, ...)
662 | * @ingroup NimbusKitBasics
663 | */
664 |
665 | /** @name Querying the Hardware */
666 |
667 | /**
668 | * Checks whether the device the app is currently running on is an iPad or not.
669 | *
670 | * @returns YES if the device is an iPad.
671 | * @fn NIIsPad()
672 | * @ingroup NimbusKitBasics
673 | */
674 |
675 | /**
676 | * Checks whether the device the app is currently running on is an
677 | * iPhone/iPod touch or not.
678 | *
679 | * @returns YES if the device is an iPhone or iPod touch.
680 | * @fn NIIsPhone()
681 | * @ingroup NimbusKitBasics
682 | */
683 |
684 | /**
685 | * Returns the screen's scale.
686 | *
687 | * @fn NIScreenScale()
688 | * @ingroup NimbusKitBasics
689 | */
690 |
691 | /**
692 | * Returns YES if the screen is a retina display, NO otherwise.
693 | *
694 | * @fn NIIsRetina()
695 | * @ingroup NimbusKitBasics
696 | */
697 |
698 | /** @name Determining Feature Support */
699 |
700 | /**
701 | * An SDK-agnostic mechanism for getting the tint color of a view.
702 | *
703 | * On pre-iOS 7 devices, will always return \p fallbackColor.
704 | * On devices that support -tintColor on UIView, returns `view.tintColor`.
705 | *
706 | * tintColor was introduced in iOS 7 as a global mechanism for changing tint color in an app.
707 | *
708 | * @fn NITintColorForViewWithFallback(UIView* view, UIColor* fallbackColor)
709 | * @ingroup NimbusKitBasics
710 | */
711 |
712 | /**
713 | * Checks whether the device's OS version is at least the given version number.
714 | *
715 | * Useful for runtime checks of the device's version number.
716 | *
717 | * @attention Apple recommends using respondsToSelector where possible to check for
718 | * feature support. Use this method as a last resort.
719 | *
720 | * @param versionNumber Any value of kCFCoreFoundationVersionNumber.
721 | * @fn NIDeviceOSVersionIsAtLeast(double versionNumber)
722 | * @ingroup NimbusKitBasics
723 | */
724 |
--------------------------------------------------------------------------------