├── .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 | ![](https://github.com/NimbusKit/attributedlabel/raw/master/docs/gfx/banner.gif "") 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 | ![](https://github.com/NimbusKit/attributedlabel/raw/master/docs/gfx/NIAttributedLabelExample1.png "A mashup of possible label styles") 10 | 11 | ![](https://github.com/NimbusKit/attributedlabel/raw/master/docs/gfx/NIAttributedLabel_inlineimages.png "Inline images") 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 | ![](https://github.com/NimbusKit/attributedlabel/raw/master/docs/gfx/NIAttributedLabelIB.png "Configuring an Attributed Label in Interface Builder") 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 | ![](https://github.com/NimbusKit/attributedlabel/raw/master/docs/gfx/NIAttributedLabel_autoDetectLinksOff.png "Before enabling autoDetectLinks") 93 | 94 | ```objc 95 | // Enable link detection on the label. 96 | myLabel.autoDetectLinks = YES; 97 | ``` 98 | 99 | ![](https://github.com/NimbusKit/attributedlabel/raw/master/docs/gfx/NIAttributedLabel_autoDetectLinksOn.png "After enabling autoDetectLinks") 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 | ![](https://github.com/NimbusKit/attributedlabel/raw/master/docs/gfx/NIAttributedLabelLinkAttributes.png "Link attributes") 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 | ![](https://github.com/NimbusKit/attributedlabel/raw/master/docs/gfx/NIAttributedLabelExample6.png "Inline Images") 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 | ![](https://github.com/NimbusKit/attributedlabel/raw/master/docs/gfx/NIAttributedLabelExample2.png "Underline styles") 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 | ![](https://github.com/NimbusKit/attributedlabel/raw/master/docs/gfx/NIAttributedLabelExample3.png "Black stroke of 3.0") 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 | ![](https://github.com/NimbusKit/attributedlabel/raw/master/docs/gfx/NIAttributedLabelExample4.png "Black stroke of -3.0") 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 | ![](https://github.com/NimbusKit/attributedlabel/raw/master/docs/gfx/NIAttributedLabelExample5.png "Text kern of -6.0") 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 | --------------------------------------------------------------------------------