Repository: NimbusKit/attributedlabel Branch: master Commit: 400da8538127 Files: 24 Total size: 143.8 KB Directory structure: gitextract_k2lgs059/ ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── NimbusKit-AttributedLabel.podspec ├── PATENTS ├── README.md ├── attributedlabel.xcworkspace/ │ └── contents.xcworkspacedata ├── catalog/ │ └── BasicInstantiation/ │ ├── BasicInstantiation/ │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── BasicInstantiation-Info.plist │ │ ├── BasicInstantiation-Prefix.pch │ │ ├── Images.xcassets/ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ └── LaunchImage.launchimage/ │ │ │ └── Contents.json │ │ ├── controllers/ │ │ │ ├── BasicInstantiationViewController.h │ │ │ └── BasicInstantiationViewController.m │ │ ├── en.lproj/ │ │ │ └── InfoPlist.strings │ │ └── main.m │ └── BasicInstantiation.xcodeproj/ │ └── project.pbxproj └── src/ ├── NIAttributedLabel.h ├── NIAttributedLabel.m ├── NSMutableAttributedString+NimbusKitAttributedLabel.h ├── NSMutableAttributedString+NimbusKitAttributedLabel.m ├── NimbusKitAttributedLabel.h └── NimbusKitBasics.h ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # OS X .DS_Store # Xcode build/ *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata *.xccheckout profile *.moved-aside DerivedData *.hmap *.ipa # CocoaPods Pods ================================================ FILE: CONTRIBUTING.md ================================================ Doing the following will ensure speedy merging of pull requests: - Document any new functionality. - Add your name to the README.md file's contributors section, if it's not already there. Thanks for contributing! <3 Jeff ================================================ FILE: LICENSE ================================================ BSD License For NimbusKit Attributed Label. Copyright (c) 2014, NimbusKit. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name NimbusKit nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: NimbusKit-AttributedLabel.podspec ================================================ Pod::Spec.new do |s| s.name = "NimbusKit-AttributedLabel" s.version = "1.0.0" s.license = { :type => 'BSD' } s.summary = "UILabel subsitute with Core Text rendering, link detection, and inline images." s.description = <<-DESC A UILabel substitute with data detectors, links, inline images, and Core Text attributes available right out of the box. DESC s.homepage = "https://github.com/nimbuskit/attributedlabel" s.author = { "Jeff Verkoeyen" => "jverkoey@gmail.com" } s.social_media_url = "http://twitter.com/featherless" s.requires_arc = true s.platform = :ios, '6.0' s.source = { :git => "https://github.com/nimbuskit/attributedlabel.git", :tag => "1.0.0" } s.source_files = 'src' s.public_header_files = 'src/{NimbusKitAttributedLabel,NIAttributedLabel}.h' s.frameworks = 'CoreText', 'CoreGraphics', 'QuartzCore' s.screenshots = [ "https://raw.githubusercontent.com/NimbusKit/attributedlabel/master/docs/gfx/NIAttributedLabelExample1.png", "https://raw.githubusercontent.com/NimbusKit/attributedlabel/master/docs/gfx/NIAttributedLabel_inlineimages.png" ] end ================================================ FILE: PATENTS ================================================ Additional Grant of Patent Rights "Software" means the Attributed Label software distributed by NimbusKit. NimbusKit hereby grants you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (subject to the termination provision below) license under any rights in any patent claims owned by NimbusKit, to make, have made, use, sell, offer to sell, import, and otherwise transfer the Software. For avoidance of doubt, no license is granted under NimbusKit's rights in any patent claims that are infringed by (i) modifications to the Software made by you or a third party, or (ii) the Software in combination with any software or other technology provided by you or a third party. The license granted hereunder will terminate, automatically and without notice, for anyone that makes any claim (including by filing any lawsuit, assertion or other action) alleging (a) direct, indirect, or contributory infringement or inducement to infringe any patent: (i) by NimbusKit or any of its subsidiaries or affiliates, whether or not such claim is related to the Software, (ii) by any party if such claim arises in whole or in part from any software, product or service of NimbusKit or any of its subsidiaries or affiliates, whether or not such claim is related to the Software, or (iii) by any party relating to the Software; or (b) that any right in any patent claim of NimbusKit is invalid or unenforceable. ================================================ FILE: README.md ================================================ ![](https://github.com/NimbusKit/attributedlabel/raw/master/docs/gfx/banner.gif "")
A UILabel substitute with data detectors, links, inline images, and Core Text attributes available right out of the box. ![](https://github.com/NimbusKit/attributedlabel/raw/master/docs/gfx/NIAttributedLabelExample1.png "A mashup of possible label styles") ![](https://github.com/NimbusKit/attributedlabel/raw/master/docs/gfx/NIAttributedLabel_inlineimages.png "Inline images") iOS 6 introduced supported for attributed text via `attributedText` but it still lacks several significant features that NimbusKit provides: - Links, both explicit and implicitly via data detection. - Inline images. - Adopting any existing UILabel styles when the text is changed. - Convenience methods for modifying substrings of the label. If you do not need any of these features then you should consider simply using UILabel. Adding it to your Project ========================= Drag all of the files from the `src` directory into your project and then import the library header. ```objc #import "NimbusKitAttributedLabel.h" ``` If you would like to use the internal helper methods on `NSMutableAttributedString`, import: ```objc #import "NSMutableAttributedString+NimbusKitAttributedLabel.h" ``` This category header is not included in the library header. Using NIAttributedLabel ======================= 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! Creating a Label in Code ------------------------ 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. ```objc NIAttributedLabel* label = [[NIAttributedLabel alloc] initWithFrame:CGRectZero]; // The internal NSAttributedString will apply all of UILabel's style attributes when // we assign text. label.text = @"Nimbus"; [label sizeToFit]; [view addSubview:label]; ``` Creating a Label in Interface Builder ------------------------------------- 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. ![](https://github.com/NimbusKit/attributedlabel/raw/master/docs/gfx/NIAttributedLabelIB.png "Configuring an Attributed Label in Interface Builder") Features Overview ================= - Automatic link detection using data detectors - Link attributes - Explicit links - Inline images - Underlining - Justifying paragraphs - Stroking - Kerning - Setting rich text styles at specific ranges Links ----- ### Automatic Link Detection Automatic link detection is provided via NSDataDetector. 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. @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. ![](https://github.com/NimbusKit/attributedlabel/raw/master/docs/gfx/NIAttributedLabel_autoDetectLinksOff.png "Before enabling autoDetectLinks") ```objc // Enable link detection on the label. myLabel.autoDetectLinks = YES; ``` ![](https://github.com/NimbusKit/attributedlabel/raw/master/docs/gfx/NIAttributedLabel_autoDetectLinksOn.png "After enabling autoDetectLinks") Enabling automatic link detection will automatically enable user interation with the label view so that the user can tap the detected links. ### Link Attributes 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. ![](https://github.com/NimbusKit/attributedlabel/raw/master/docs/gfx/NIAttributedLabelLinkAttributes.png "Link attributes") #### A Note on Performance 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. ### Handling Taps on Links 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. ```objc - (void)attributedLabel:(NIAttributedLabel)attributedLabel didSelectTextCheckingResult:(NSTextCheckingResult)result atPoint:(CGPoint)point { [[UIApplication sharedApplication] openURL:result.URL]; } ``` ### Explicit Links Links can be added explicitly using NIAttributedLabel::addLink:range:. ```objc // Add a link to the string 'nimbus' in myLabel. [myLabel addLink:[NSURL URLWithString:@"nimbus://custom/url"] range:[myLabel.text rangeOfString:@"nimbus"]]; ``` Inline Images ------------- Inline images may be inserted using the `-insertImage:atIndex:` family of methods. ```objc NIAttributedLabel* label = [NIAttributedLabel new]; label.text = @"NimbusKit 2.0"; label.font = [UIFont systemFontOfSize:24]; [label insertImage:[UIImage imageNamed:@"AppIcon60x60"] atIndex:@"NimbusKit".length margins:UIEdgeInsetsZero verticalTextAlignment:NIVerticalTextAlignmentMiddle]; label.frame = (CGRect){CGPointMake(20, 80), CGSizeZero}; [label sizeToFit]; [self.view addSubview:label]; ``` Generates the following output: ![](https://github.com/NimbusKit/attributedlabel/raw/master/docs/gfx/NIAttributedLabelExample6.png "Inline Images") Modifying Style Attributes -------------------------- ### Underlining Text To underline an entire label: ```objc // Underline the whole label with a single line. myLabel.underlineStyle = kCTUnderlineStyleSingle; ``` Underline modifiers can also be added: ```objc // Underline the whole label with a dash dot single line. myLabel.underlineStyle = kCTUnderlineStyleSingle; myLabel.underlineStyleModifier = kCTUnderlinePatternDashDot; ``` Underline styles and modifiers can be mixed to create the desired effect, which is shown in the following screenshot: ![](https://github.com/NimbusKit/attributedlabel/raw/master/docs/gfx/NIAttributedLabelExample2.png "Underline styles") @remarks Underline style kCTUnderlineStyleThick does not draw a thicker line. ### Justifying Paragraphs NIAttributedLabel supports justified text using UITextAlignmentJustify. ```objc myLabel.textAlignment = UITextAlignmentJustify; ``` ### Stroking Text ```objc myLabel.strokeWidth = 3.0; myLabel.strokeColor = [UIColor blackColor]; ``` A positive stroke width will render only the stroke. ![](https://github.com/NimbusKit/attributedlabel/raw/master/docs/gfx/NIAttributedLabelExample3.png "Black stroke of 3.0") A negative number will fill the stroke with textColor: ```objc myLabel.strokeWidth = -3.0; myLabel.strokeColor = [UIColor blackColor]; ``` ![](https://github.com/NimbusKit/attributedlabel/raw/master/docs/gfx/NIAttributedLabelExample4.png "Black stroke of -3.0") ### Kerning Text Kerning is the space between characters in points. A positive kern will increase the space between letters. Correspondingly a negative number will decrease the space. ```objc myLabel.textKern = -6.0; ``` ![](https://github.com/NimbusKit/attributedlabel/raw/master/docs/gfx/NIAttributedLabelExample5.png "Text kern of -6.0") ### Modifying Style at Specific Ranges All styles that can be added to the whole label (as well as default UILabel styles like font and text color) can be added to just a range of text. ```objc [myLabel setTextColor:[UIColor orangeColor] range:[myLabel.text rangeOfString:@"Nimbus"]]; [myLabel setFont:[UIFont boldSystemFontOfSize:22] range:[myLabel.text rangeOfString:@"iOS"]]; ``` Requirements ============ NIAttributedLabel must be compiled with the iOS 6 SDK or above. You must link to the CoreText and Core Graphics frameworks. Version History =============== 1.0.0 on Apr 30, 2014 ----- Initial release. Includes: - Zero dependencies! - Link detection. - Inline images. - Helper category on NSMutableAttributedString. Credits ======= NIAttributedLabel was extracted from Nimbus 1.2.0 by [Jeff Verkoeyen](http://jeffverkoeyen.com/) ([featherless](http://twitter.com/)). Contributors ------------ You can be the first! [Open a pull request now](https://github.com/NimbusKit/Basics/compare/). License ======= 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. ================================================ FILE: attributedlabel.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: catalog/BasicInstantiation/BasicInstantiation/AppDelegate.h ================================================ /* Copyright (c) 2011-present, NimbusKit. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree and at the http://nimbuskit.info/license url. An additional grant of patent rights can be found in the PATENTS file in the same directory and url. */ #import @interface AppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; @end ================================================ FILE: catalog/BasicInstantiation/BasicInstantiation/AppDelegate.m ================================================ /* Copyright (c) 2011-present, NimbusKit. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree and at the http://nimbuskit.info/license url. An additional grant of patent rights can be found in the PATENTS file in the same directory and url. */ #import "AppDelegate.h" #import "BasicInstantiationViewController.h" @implementation AppDelegate #pragma mark - Standard Scaffolding - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.window.backgroundColor = [UIColor whiteColor]; self.window.rootViewController = [BasicInstantiationViewController new]; [self.window makeKeyAndVisible]; return YES; } @end ================================================ FILE: catalog/BasicInstantiation/BasicInstantiation/BasicInstantiation-Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleDisplayName ${PRODUCT_NAME} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier com.nimbuskit.${PRODUCT_NAME:rfc1034identifier} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 LSRequiresIPhoneOS UIRequiredDeviceCapabilities armv7 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: catalog/BasicInstantiation/BasicInstantiation/BasicInstantiation-Prefix.pch ================================================ /* Copyright (c) 2011-present, NimbusKit. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree and at the http://nimbuskit.info/license url. An additional grant of patent rights can be found in the PATENTS file in the same directory and url. */ #import #ifndef __IPHONE_3_0 #warning "This project uses features only available in iOS SDK 3.0 and later." #endif #ifdef __OBJC__ #import #import #endif ================================================ FILE: catalog/BasicInstantiation/BasicInstantiation/Images.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "size" : "29x29", "scale" : "2x" }, { "idiom" : "iphone", "size" : "40x40", "scale" : "2x" }, { "size" : "57x57", "idiom" : "iphone", "filename" : "Icon.png", "scale" : "1x" }, { "size" : "57x57", "idiom" : "iphone", "filename" : "Icon@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-iOS7@2x.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" }, "properties" : { "pre-rendered" : true } } ================================================ FILE: catalog/BasicInstantiation/BasicInstantiation/Images.xcassets/LaunchImage.launchimage/Contents.json ================================================ { "images" : [ { "orientation" : "portrait", "idiom" : "iphone", "extent" : "full-screen", "minimum-system-version" : "7.0", "filename" : "Default-iOS7-flat@2x.png", "scale" : "2x" }, { "extent" : "full-screen", "idiom" : "iphone", "subtype" : "retina4", "filename" : "Default-568h-iOS7-flat@2x-1.png", "minimum-system-version" : "7.0", "orientation" : "portrait", "scale" : "2x" }, { "orientation" : "portrait", "idiom" : "iphone", "extent" : "full-screen", "filename" : "Default-flat.png", "scale" : "1x" }, { "orientation" : "portrait", "idiom" : "iphone", "extent" : "full-screen", "filename" : "Default-568h-iOS7-flat@2x.png", "subtype" : "retina4", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: catalog/BasicInstantiation/BasicInstantiation/controllers/BasicInstantiationViewController.h ================================================ /* Copyright (c) 2011-present, NimbusKit. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree and at the http://nimbuskit.info/license url. An additional grant of patent rights can be found in the PATENTS file in the same directory and url. */ #import @interface BasicInstantiationViewController : UIViewController @end ================================================ FILE: catalog/BasicInstantiation/BasicInstantiation/controllers/BasicInstantiationViewController.m ================================================ /* Copyright (c) 2011-present, NimbusKit. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree and at the http://nimbuskit.info/license url. An additional grant of patent rights can be found in the PATENTS file in the same directory and url. */ #import "BasicInstantiationViewController.h" // Using the library import, rather than directly importing, has some nice advantages: // // - We don't have to import any dependent framework headers. // - Any file movement within the library happens transparently to us. // #import "NimbusKitAttributedLabel.h" @interface BasicInstantiationViewController () @end @implementation BasicInstantiationViewController - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) { self.title = @"Basic Instantiation"; } return self; } - (void)viewDidLoad { [super viewDidLoad]; // Standard instantiation { NIAttributedLabel* label = [NIAttributedLabel new]; label.text = @"NimbusKit 2.0"; // Standard style properties immediately apply to the label as a whole. label.font = [UIFont systemFontOfSize:24]; // NIAttributedLabel provides a number of convenience methods to modify attributes for ranges of // the label's text. [label setFont:[UIFont boldSystemFontOfSize:24] range:NSMakeRange(0, @"Nimbus".length)]; [label setTextColor:[UIColor orangeColor] range:NSMakeRange(0, @"NimbusKit".length)]; label.frame = (CGRect){CGPointMake(20, 50), CGSizeZero}; [label sizeToFit]; [self.view addSubview:label]; } // Inline images { NIAttributedLabel* label = [NIAttributedLabel new]; label.text = @"NimbusKit 2.0"; label.font = [UIFont systemFontOfSize:24]; [label insertImage:[UIImage imageNamed:@"AppIcon60x60"] atIndex:@"NimbusKit".length margins:UIEdgeInsetsZero verticalTextAlignment:NIVerticalTextAlignmentMiddle]; label.frame = (CGRect){CGPointMake(20, 80), CGSizeZero}; [label sizeToFit]; [self.view addSubview:label]; } // Links { NIAttributedLabel* label = [NIAttributedLabel new]; label.text = @"NimbusKit 2.0"; label.font = [UIFont systemFontOfSize:24]; [label addLink:[NSURL URLWithString:@"http://nimbuskit.info"] range:NSMakeRange(0, label.text.length)]; label.delegate = self; label.frame = (CGRect){CGPointMake(20, 150), CGSizeZero}; [label sizeToFit]; [self.view addSubview:label]; } } #pragma mark - NIAttributedLabelDelegate - (void)attributedLabel:(NIAttributedLabel *)attributedLabel didSelectTextCheckingResult:(NSTextCheckingResult *)result atPoint:(CGPoint)point { if (result.resultType == NSTextCheckingTypeLink) { [[UIApplication sharedApplication] openURL:result.URL]; } } @end ================================================ FILE: catalog/BasicInstantiation/BasicInstantiation/en.lproj/InfoPlist.strings ================================================ /* Localized versions of Info.plist keys */ ================================================ FILE: catalog/BasicInstantiation/BasicInstantiation/main.m ================================================ /* Copyright (c) 2011-present, NimbusKit. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree and at the http://nimbuskit.info/license url. An additional grant of patent rights can be found in the PATENTS file in the same directory and url. */ #import #import "AppDelegate.h" int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } ================================================ FILE: catalog/BasicInstantiation/BasicInstantiation.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 66A3E0C8191002AD0094A9B6 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 66A3E0C7191002AD0094A9B6 /* Foundation.framework */; }; 66A3E0CA191002AD0094A9B6 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 66A3E0C9191002AD0094A9B6 /* CoreGraphics.framework */; }; 66A3E0CC191002AD0094A9B6 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 66A3E0CB191002AD0094A9B6 /* UIKit.framework */; }; 66A3E0D2191002AD0094A9B6 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 66A3E0D0191002AD0094A9B6 /* InfoPlist.strings */; }; 66A3E0D4191002AD0094A9B6 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 66A3E0D3191002AD0094A9B6 /* main.m */; }; 66A3E0D8191002AD0094A9B6 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 66A3E0D7191002AD0094A9B6 /* AppDelegate.m */; }; 66A3E0DA191002AD0094A9B6 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 66A3E0D9191002AD0094A9B6 /* Images.xcassets */; }; 66A3E0FC191002E50094A9B6 /* NIAttributedLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = 66A3E0F8191002E50094A9B6 /* NIAttributedLabel.m */; }; 66A3E0FD191002E50094A9B6 /* NSMutableAttributedString+NimbusKitAttributedLabel.m in Sources */ = {isa = PBXBuildFile; fileRef = 66A3E0FB191002E50094A9B6 /* NSMutableAttributedString+NimbusKitAttributedLabel.m */; }; 66A3E10A191008260094A9B6 /* BasicInstantiationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 66A3E109191008260094A9B6 /* BasicInstantiationViewController.m */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 66A3E0C4191002AD0094A9B6 /* BasicInstantiation.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BasicInstantiation.app; sourceTree = BUILT_PRODUCTS_DIR; }; 66A3E0C7191002AD0094A9B6 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 66A3E0C9191002AD0094A9B6 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 66A3E0CB191002AD0094A9B6 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 66A3E0CF191002AD0094A9B6 /* BasicInstantiation-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "BasicInstantiation-Info.plist"; sourceTree = ""; }; 66A3E0D1191002AD0094A9B6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 66A3E0D3191002AD0094A9B6 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 66A3E0D5191002AD0094A9B6 /* BasicInstantiation-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BasicInstantiation-Prefix.pch"; sourceTree = ""; }; 66A3E0D6191002AD0094A9B6 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 66A3E0D7191002AD0094A9B6 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 66A3E0D9191002AD0094A9B6 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 66A3E0E0191002AD0094A9B6 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 66A3E0F7191002E50094A9B6 /* NIAttributedLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NIAttributedLabel.h; sourceTree = ""; }; 66A3E0F8191002E50094A9B6 /* NIAttributedLabel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NIAttributedLabel.m; sourceTree = ""; }; 66A3E0F9191002E50094A9B6 /* NimbusKitAttributedLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NimbusKitAttributedLabel.h; sourceTree = ""; }; 66A3E0FA191002E50094A9B6 /* NSMutableAttributedString+NimbusKitAttributedLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSMutableAttributedString+NimbusKitAttributedLabel.h"; sourceTree = ""; }; 66A3E0FB191002E50094A9B6 /* NSMutableAttributedString+NimbusKitAttributedLabel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSMutableAttributedString+NimbusKitAttributedLabel.m"; sourceTree = ""; }; 66A3E0FE191002F70094A9B6 /* NimbusKitBasics.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NimbusKitBasics.h; sourceTree = ""; }; 66A3E108191008260094A9B6 /* BasicInstantiationViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BasicInstantiationViewController.h; sourceTree = ""; }; 66A3E109191008260094A9B6 /* BasicInstantiationViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BasicInstantiationViewController.m; sourceTree = ""; }; 66AB557E191756000010FDCC /* NimbusKit-AttributedLabel.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; name = "NimbusKit-AttributedLabel.podspec"; path = "../../NimbusKit-AttributedLabel.podspec"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 66A3E0C1191002AD0094A9B6 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 66A3E0CA191002AD0094A9B6 /* CoreGraphics.framework in Frameworks */, 66A3E0CC191002AD0094A9B6 /* UIKit.framework in Frameworks */, 66A3E0C8191002AD0094A9B6 /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 66A3E0BB191002AD0094A9B6 = { isa = PBXGroup; children = ( 66AB557E191756000010FDCC /* NimbusKit-AttributedLabel.podspec */, 66A3E0F6191002CD0094A9B6 /* attributedlabel */, 66A3E0CD191002AD0094A9B6 /* BasicInstantiation */, 66A3E0C6191002AD0094A9B6 /* Frameworks */, 66A3E0C5191002AD0094A9B6 /* Products */, ); sourceTree = ""; }; 66A3E0C5191002AD0094A9B6 /* Products */ = { isa = PBXGroup; children = ( 66A3E0C4191002AD0094A9B6 /* BasicInstantiation.app */, ); name = Products; sourceTree = ""; }; 66A3E0C6191002AD0094A9B6 /* Frameworks */ = { isa = PBXGroup; children = ( 66A3E0C7191002AD0094A9B6 /* Foundation.framework */, 66A3E0C9191002AD0094A9B6 /* CoreGraphics.framework */, 66A3E0CB191002AD0094A9B6 /* UIKit.framework */, 66A3E0E0191002AD0094A9B6 /* XCTest.framework */, ); name = Frameworks; sourceTree = ""; }; 66A3E0CD191002AD0094A9B6 /* BasicInstantiation */ = { isa = PBXGroup; children = ( 66A3E107191008070094A9B6 /* controllers */, 66A3E0D6191002AD0094A9B6 /* AppDelegate.h */, 66A3E0D7191002AD0094A9B6 /* AppDelegate.m */, 66A3E0D9191002AD0094A9B6 /* Images.xcassets */, 66A3E0CE191002AD0094A9B6 /* Supporting Files */, ); path = BasicInstantiation; sourceTree = ""; }; 66A3E0CE191002AD0094A9B6 /* Supporting Files */ = { isa = PBXGroup; children = ( 66A3E0CF191002AD0094A9B6 /* BasicInstantiation-Info.plist */, 66A3E0D0191002AD0094A9B6 /* InfoPlist.strings */, 66A3E0D3191002AD0094A9B6 /* main.m */, 66A3E0D5191002AD0094A9B6 /* BasicInstantiation-Prefix.pch */, ); name = "Supporting Files"; sourceTree = ""; }; 66A3E0F6191002CD0094A9B6 /* attributedlabel */ = { isa = PBXGroup; children = ( 66A3E0F7191002E50094A9B6 /* NIAttributedLabel.h */, 66A3E0F8191002E50094A9B6 /* NIAttributedLabel.m */, 66A3E0F9191002E50094A9B6 /* NimbusKitAttributedLabel.h */, 66A3E0FA191002E50094A9B6 /* NSMutableAttributedString+NimbusKitAttributedLabel.h */, 66A3E0FB191002E50094A9B6 /* NSMutableAttributedString+NimbusKitAttributedLabel.m */, 66A3E0FE191002F70094A9B6 /* NimbusKitBasics.h */, ); name = attributedlabel; path = ../../src; sourceTree = ""; }; 66A3E107191008070094A9B6 /* controllers */ = { isa = PBXGroup; children = ( 66A3E108191008260094A9B6 /* BasicInstantiationViewController.h */, 66A3E109191008260094A9B6 /* BasicInstantiationViewController.m */, ); path = controllers; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 66A3E0C3191002AD0094A9B6 /* BasicInstantiation */ = { isa = PBXNativeTarget; buildConfigurationList = 66A3E0F0191002AD0094A9B6 /* Build configuration list for PBXNativeTarget "BasicInstantiation" */; buildPhases = ( 66A3E0C0191002AD0094A9B6 /* Sources */, 66A3E0C1191002AD0094A9B6 /* Frameworks */, 66A3E0C2191002AD0094A9B6 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = BasicInstantiation; productName = BasicInstantiation; productReference = 66A3E0C4191002AD0094A9B6 /* BasicInstantiation.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 66A3E0BC191002AD0094A9B6 /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 0510; ORGANIZATIONNAME = NimbusKit; }; buildConfigurationList = 66A3E0BF191002AD0094A9B6 /* Build configuration list for PBXProject "BasicInstantiation" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, ); mainGroup = 66A3E0BB191002AD0094A9B6; productRefGroup = 66A3E0C5191002AD0094A9B6 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 66A3E0C3191002AD0094A9B6 /* BasicInstantiation */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 66A3E0C2191002AD0094A9B6 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 66A3E0D2191002AD0094A9B6 /* InfoPlist.strings in Resources */, 66A3E0DA191002AD0094A9B6 /* Images.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 66A3E0C0191002AD0094A9B6 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 66A3E0D8191002AD0094A9B6 /* AppDelegate.m in Sources */, 66A3E0D4191002AD0094A9B6 /* main.m in Sources */, 66A3E10A191008260094A9B6 /* BasicInstantiationViewController.m in Sources */, 66A3E0FC191002E50094A9B6 /* NIAttributedLabel.m in Sources */, 66A3E0FD191002E50094A9B6 /* NSMutableAttributedString+NimbusKitAttributedLabel.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ 66A3E0D0191002AD0094A9B6 /* InfoPlist.strings */ = { isa = PBXVariantGroup; children = ( 66A3E0D1191002AD0094A9B6 /* en */, ); name = InfoPlist.strings; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 66A3E0EE191002AD0094A9B6 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 7.1; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; }; name = Debug; }; 66A3E0EF191002AD0094A9B6 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 7.1; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; }; name = Release; }; 66A3E0F1191002AD0094A9B6 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "BasicInstantiation/BasicInstantiation-Prefix.pch"; INFOPLIST_FILE = "BasicInstantiation/BasicInstantiation-Info.plist"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; name = Debug; }; 66A3E0F2191002AD0094A9B6 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "BasicInstantiation/BasicInstantiation-Prefix.pch"; INFOPLIST_FILE = "BasicInstantiation/BasicInstantiation-Info.plist"; PRODUCT_NAME = "$(TARGET_NAME)"; WRAPPER_EXTENSION = app; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 66A3E0BF191002AD0094A9B6 /* Build configuration list for PBXProject "BasicInstantiation" */ = { isa = XCConfigurationList; buildConfigurations = ( 66A3E0EE191002AD0094A9B6 /* Debug */, 66A3E0EF191002AD0094A9B6 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 66A3E0F0191002AD0094A9B6 /* Build configuration list for PBXNativeTarget "BasicInstantiation" */ = { isa = XCConfigurationList; buildConfigurations = ( 66A3E0F1191002AD0094A9B6 /* Debug */, 66A3E0F2191002AD0094A9B6 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 66A3E0BC191002AD0094A9B6 /* Project object */; } ================================================ FILE: src/NIAttributedLabel.h ================================================ /* Copyright (c) 2011-present, NimbusKit. All rights reserved. Originally created by Roger Chapman This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree and at the http://nimbuskit.info/license url. An additional grant of patent rights can be found in the PATENTS file in the same directory and url. */ #import #import #import #if defined __cplusplus extern "C" { #endif /** * Calculates the ideal dimensions of an attributed string fitting a given size. * * This calculation is performed off the raw attributed string so this calculation may differ * slightly from NIAttributedLabel's use of it due to lack of link and image attributes. * * This method is used in NIAttributedLabel to calculate its size after all additional * styling attributes have been set. */ CGSize NISizeOfAttributedStringConstrainedToSize(NSAttributedString* attributedString, CGSize size, NSInteger numberOfLines); #if defined __cplusplus }; #endif // Vertical alignments for NIAttributedLabel. typedef enum { NIVerticalTextAlignmentTop = 0, NIVerticalTextAlignmentMiddle, NIVerticalTextAlignmentBottom, } NIVerticalTextAlignment; extern NSString* const NIAttributedLabelLinkAttributeName; // Value is an NSTextCheckingResult. @protocol NIAttributedLabelDelegate; /** * The NIAttributedLabel class provides support for displaying rich text with selectable links and * embedded images. * * Differences between UILabel and NIAttributedLabel: * * - @c NSLineBreakByTruncatingHead and @c NSLineBreakByTruncatingMiddle only apply to single * lines and will not wrap the label regardless of the @c numberOfLines property. To wrap lines * with any of these line break modes you must explicitly add newline characters to the string. * - When you assign an NSString to the text property the attributed label will create an * attributed string that inherits all of the label's current styles. * - Text is aligned vertically to the top of the bounds by default rather than centered. You can * change this behavior using @link NIAttributedLabel::verticalTextAlignment verticalTextAlignment@endlink. * - CoreText fills the frame with glyphs until they no longer fit. This is an important difference * from UILabel because it means that CoreText will not add any glyphs that won't fit in the * frame, while UILabel does. This can result in empty NIAttributedLabels if your frame is too * small where UILabel would draw clipped text. It is recommended that you use sizeToFit to get * the correct dimensions of the attributed label before setting the frame. * * NIAttributedLabel implements the UIAccessibilityContainer methods to expose each link as an * accessibility item. * * @ingroup NimbusKitAttributedLabel */ @interface NIAttributedLabel : UILabel @property (nonatomic) BOOL autoDetectLinks; // Default: NO @property (nonatomic) NSTextCheckingType dataDetectorTypes; // Default: NSTextCheckingTypeLink @property (nonatomic) BOOL deferLinkDetection; // Default: NO - (void)addLink:(NSURL *)urlLink range:(NSRange)range; - (void)removeAllExplicitLinks; // Removes all links that were added by addLink:range:. Does not remove autodetected links. @property (nonatomic, strong) UIColor* linkColor; // Default: self.tintColor (iOS 7) or [UIColor blueColor] (iOS 6) @property (nonatomic, strong) UIColor* highlightedLinkBackgroundColor; // Default: [UIColor colorWithWhite:0.5 alpha:0.5 @property (nonatomic) BOOL linksHaveUnderlines; // Default: NO @property (nonatomic, copy) NSDictionary* attributesForLinks; // Default: nil @property (nonatomic, copy) NSDictionary* attributesForHighlightedLink; // Default: nil @property (nonatomic) CGFloat lineHeight; @property (nonatomic) NIVerticalTextAlignment verticalTextAlignment; // Default: NIVerticalTextAlignmentTop @property (nonatomic) CTUnderlineStyle underlineStyle; @property (nonatomic) CTUnderlineStyleModifiers underlineStyleModifier; @property (nonatomic) CGFloat shadowBlur; // Default: 0 @property (nonatomic) CGFloat strokeWidth; @property (nonatomic, strong) UIColor* strokeColor; @property (nonatomic) CGFloat textKern; @property (nonatomic, copy) NSString* tailTruncationString; @property (nonatomic) BOOL shouldSortLinksLast; // Sort the links in the text as the last elements in accessible elements. Default: NO - (void)setFont:(UIFont *)font range:(NSRange)range; - (void)setStrokeColor:(UIColor *)color range:(NSRange)range; - (void)setStrokeWidth:(CGFloat)width range:(NSRange)range; - (void)setTextColor:(UIColor *)textColor range:(NSRange)range; - (void)setTextKern:(CGFloat)kern range:(NSRange)range; - (void)setUnderlineStyle:(CTUnderlineStyle)style modifier:(CTUnderlineStyleModifiers)modifier range:(NSRange)range; - (void)insertImage:(UIImage *)image atIndex:(NSInteger)index; - (void)insertImage:(UIImage *)image atIndex:(NSInteger)index margins:(UIEdgeInsets)margins; - (void)insertImage:(UIImage *)image atIndex:(NSInteger)index margins:(UIEdgeInsets)margins verticalTextAlignment:(NIVerticalTextAlignment)verticalTextAlignment; - (void)invalidateAccessibleElements; @property (nonatomic, weak) IBOutlet id delegate; @end /** * The methods declared by the NIAttributedLabelDelegate protocol allow the adopting delegate to * respond to messages from the NIAttributedLabel class and thus respond to selections. * * @ingroup NimbusKitAttributedLabel */ @protocol NIAttributedLabelDelegate @optional /** @name Managing Selections */ /** * Informs the receiver that a data detector result has been selected. * * @param attributedLabel An attributed label informing the receiver of the selection. * @param result The data detector result that was selected. * @param point The point within @c attributedLabel where the result was tapped. */ - (void)attributedLabel:(NIAttributedLabel *)attributedLabel didSelectTextCheckingResult:(NSTextCheckingResult *)result atPoint:(CGPoint)point; /** * Asks the receiver whether an action sheet should be displayed at the given point. * * If this method is not implemented by the receiver then @c actionSheet will always be displayed. * * @c actionSheet will be populated with actions that match the data type that was selected. For * example, a link will have the actions "Open in Safari" and "Copy URL". A phone number will have * @"Call" and "Copy Phone Number". * * @param attributedLabel An attributed label asking the delegate whether to display the action * sheet. * @param actionSheet The action sheet that will be displayed if YES is returned. * @param result The data detector result that was selected. * @param point The point within @c attributedLabel where the result was tapped. * @returns YES if @c actionSheet should be displayed. NO if @c actionSheet should not be * displayed. */ - (BOOL)attributedLabel:(NIAttributedLabel *)attributedLabel shouldPresentActionSheet:(UIActionSheet *)actionSheet withTextCheckingResult:(NSTextCheckingResult *)result atPoint:(CGPoint)point; @end /** @name Accessing and Detecting Links */ /** * A Booelan value indicating whether to automatically detect links in the string. * * By default this is disabled. * * Link detection is deferred until the label is displayed for the first time. If the text changes * then all of the links will be cleared and re-detected when the label displays again. * * Note that link detection is an expensive operation. If you are planning to use attributed labels * in table views or similar high-performance situations then you should consider enabling defered * link detection by setting @link NIAttributedLabel::deferLinkDetection deferLinkDetection@endlink * to YES. * * @sa NIAttributedLabel::dataDetectorTypes * @sa NIAttributedLabel::deferLinkDetection * @fn NIAttributedLabel::autoDetectLinks */ /** * A Boolean value indicating whether to defer link detection to a separate thread. * * By default this is disabled. * * When defering is enabled, link detection will be performed on a separate thread. This will cause * your label to appear without any links briefly before being redrawn with the detected links. * This offloads the data detection to a separate thread so that your labels can be displayed * faster. * * @fn NIAttributedLabel::deferLinkDetection */ /** * The types of data that will be detected when * @link NIAttributedLabel::autoDetectLinks autoDetectLinks@endlink is enabled. * * By default this is NSTextCheckingTypeLink. All available data detector types. * * @fn NIAttributedLabel::dataDetectorTypes */ /** * Adds a link to a URL at a given range. * * Adding any links will immediately enable user interaction on this label. Explicitly added * links are removed whenever the text changes. * * @fn NIAttributedLabel::addLink:range: */ /** * Removes all explicit links from the label. * * If you wish to remove automatically-detected links, set autoDetectLinks to NO. * * @fn NIAttributedLabel::removeAllExplicitLinks */ /** @name Accessing Link Display Styles */ /** * The text color of detected links. * * The default color is [UIColor blueColor] on pre-iOS 7 devices or self.tintColor on iOS 7 devices. * If linkColor is assigned nil then links will not be given any special color. Use * attributesForLinks to specify alternative styling. * * @image html NIAttributedLabelLinkAttributes.png "Link attributes" * * @fn NIAttributedLabel::linkColor */ /** * The background color of the link's selection frame when the user is touching the link. * * The default is [UIColor colorWithWhite:0.5 alpha:0.5]. * * If you do not want links to be highlighted when touched, set this to nil. * * @image html NIAttributedLabelLinkAttributes.png "Link attributes" * * @fn NIAttributedLabel::highlightedLinkBackgroundColor */ /** * A Boolean value indicating whether links should have underlines. * * By default this is disabled. * * This affects all links in the label. * * @fn NIAttributedLabel::linksHaveUnderlines */ /** * A dictionary of CoreText attributes to apply to links. * * This dictionary must contain CoreText attributes. These attributes are applied after the color * and underline styles have been applied to the link. * * @fn NIAttributedLabel::attributesForLinks */ /** * A dictionary of CoreText attributes to apply to the highlighted link. * * This dictionary must contain CoreText attributes. These attributes are applied after * attributesForLinks have been applied to the highlighted link. * * @fn NIAttributedLabel::attributesForHighlightedLink */ /** @name Modifying Rich Text Styles for All Text */ /** * The vertical alignment of the text within the label's bounds. * * The default is @c NIVerticalTextAlignmentTop. This is for performance reasons because the other * modes require more computation. Aligning to the top is generally what you want anyway. * * @c NIVerticalTextAlignmentBottom will align the text to the bottom of the bounds, while * @c NIVerticalTextAlignmentMiddle will center the text vertically. * * @fn NIAttributedLabel::verticalTextAlignment */ /** * The underline style for the entire label. * * By default this is @c kCTUnderlineStyleNone. * * View all available styles. * * @fn NIAttributedLabel::underlineStyle */ /** * The underline style modifier for the entire label. * * By default this is @c kCTUnderlinePatternSolid. * * View all available style * modifiers. * * @fn NIAttributedLabel::underlineStyleModifier */ /** * A non-negative number specifying the amount of blur to apply to the label's text shadow. * * By default this is zero. * * @fn NIAttributedLabel::shadowBlur */ /** * Sets the stroke width for the text. * * By default this is zero. * * Positive numbers will draw the stroke. Negative numbers will draw the stroke and fill. * * @fn NIAttributedLabel::strokeWidth */ /** * Sets the stroke color for the text. * * By default this is nil. * * @fn NIAttributedLabel::strokeColor */ /** * Sets the line height for the text. * * By default this is zero. * * Setting this value to zero will make the label use the default line height for the text's font. * * @fn NIAttributedLabel::lineHeight */ /** * Sets the kern for the text. * * By default this is zero. * * The text kern indicates how many points each character should be shifted from its default offset. * A positive kern indicates a shift farther away. A negative kern indicates a shift closer. * * @fn NIAttributedLabel::textKern */ /** @name Modifying Tail Truncation Properties */ /** * The string to display for tail truncation. * * By default this is nil and the default ellipses character, \u2026, is used. * * @fn NIAttributedLabel::tailTruncationString */ /** @name Modifying Rich Text Styles in Ranges */ /** * Sets the text color for text in a given range. * * @fn NIAttributedLabel::setTextColor:range: */ /** * Sets the font for text in a given range. * * @fn NIAttributedLabel::setFont:range: */ /** * Sets the underline style and modifier for text in a given range. * * View all available styles. * * View all available style * modifiers. * * @fn NIAttributedLabel::setUnderlineStyle:modifier:range: */ /** * Sets the stroke width for text in a given range. * * Positive numbers will draw the stroke. Negative numbers will draw the stroke and fill. * * @fn NIAttributedLabel::setStrokeWidth:range: */ /** * Sets the stroke color for text in a given range. * * @fn NIAttributedLabel::setStrokeColor:range: */ /** * Sets the kern for text in a given range. * * The text kern indicates how many points each character should be shifted from its default offset. * A positive kern indicates a shift farther away. A negative kern indicates a shift closer. * * @fn NIAttributedLabel::setTextKern:range: */ /** @name Adding Inline Images */ /** * Inserts the given image inline at the given index in the receiver's text. * * The image will have no margins. * The image's vertical text alignment will be NIVerticalTextAlignmentBottom. * * @param image The image to add to the receiver. * @param index The index into the receiver's text at which to insert the image. * @fn NIAttributedLabel::insertImage:atIndex: */ /** * Inserts the given image inline at the given index in the receiver's text. * * The image's vertical text alignment will be NIVerticalTextAlignmentBottom. * * @param image The image to add to the receiver. * @param index The index into the receiver's text at which to insert the image. * @param margins The space around the image on all sides in points. * @fn NIAttributedLabel::insertImage:atIndex:margins: */ /** * Inserts the given image inline at the given index in the receiver's text. * * @attention * Images do not currently support NIVerticalTextAlignmentTop and the receiver will fire * multiple debug assertions if you attempt to use it. * * @param image The image to add to the receiver. * @param index The index into the receiver's text at which to insert the image. * @param margins The space around the image on all sides in points. * @param verticalTextAlignment The position of the text relative to the image. * @fn NIAttributedLabel::insertImage:atIndex:margins:verticalTextAlignment: */ /** @name Accessibility */ /** * Invalidates this label's accessible elements. * * When a label is contained within another view and that parent view moves, the label will not be * informed of this change and any existing accessibility elements will still point to the old * screen location. If this happens you must call -invalidateAccessibleElements in order to force * the label to refresh its accessibile elements. * * @fn NIAttributedLabel::invalidateAccessibleElements */ /** @name Accessing the Delegate */ /** * The delegate of the attributed-label object. * * The delegate must adopt the NIAttributedLabelDelegate protocol. The NIAttributedLabel class, * which does not strong the delegate, invokes each protocol method the delegate implements. * * @fn NIAttributedLabel::delegate */ ================================================ FILE: src/NIAttributedLabel.m ================================================ /* Copyright (c) 2011-present, NimbusKit. All rights reserved. Originally created by Roger Chapman This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree and at the http://nimbuskit.info/license url. An additional grant of patent rights can be found in the PATENTS file in the same directory and url. */ #import "NIAttributedLabel.h" #import "NSMutableAttributedString+NimbusKitAttributedLabel.h" #import "NimbusKitBasics.h" #import #if !defined(__has_feature) || !__has_feature(objc_arc) #error "NimbusKit requires ARC support." #endif #if __IPHONE_OS_VERSION_MIN_REQUIRED < NI_IOS_6_0 #error "NIAttributedLabel requires iOS 6 or higher." #endif // The number of seconds to wait before executing a long press action on the tapped link. static const NSTimeInterval kLongPressTimeInterval = 0.5; // The number of pixels the user's finger must move before cancelling the long press timer. static const CGFloat kLongPressGutter = 22; // The touch gutter is the amount of space around a link that will still register as tapping // "within" the link. static const CGFloat kTouchGutter = 22; static const CGFloat kVMargin = 5.0f; // \u2026 is the Unicode horizontal ellipsis character code static NSString* const kEllipsesCharacter = @"\u2026"; NSString* const NIAttributedLabelLinkAttributeName = @"NIAttributedLabel:Link"; // For supporting images. CGFloat NIImageDelegateGetAscentCallback(void* refCon); CGFloat NIImageDelegateGetDescentCallback(void* refCon); CGFloat NIImageDelegateGetWidthCallback(void* refCon); CGSize NISizeOfAttributedStringConstrainedToSize(NSAttributedString* attributedString, CGSize constraintSize, NSInteger numberOfLines) { if (nil == attributedString) { return CGSizeZero; } CFAttributedStringRef attributedStringRef = (__bridge CFAttributedStringRef)attributedString; CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attributedStringRef); CFRange range = CFRangeMake(0, 0); // This logic adapted from @mattt's TTTAttributedLabel // https://github.com/mattt/TTTAttributedLabel if (numberOfLines == 1) { constraintSize = CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX); } else if (numberOfLines > 0 && nil != framesetter) { CGMutablePathRef path = CGPathCreateMutable(); CGPathAddRect(path, NULL, CGRectMake(0, 0, constraintSize.width, constraintSize.height)); CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL); CFArrayRef lines = CTFrameGetLines(frame); if (nil != lines && CFArrayGetCount(lines) > 0) { NSInteger lastVisibleLineIndex = MIN(numberOfLines, CFArrayGetCount(lines)) - 1; CTLineRef lastVisibleLine = CFArrayGetValueAtIndex(lines, lastVisibleLineIndex); CFRange rangeToLayout = CTLineGetStringRange(lastVisibleLine); range = CFRangeMake(0, rangeToLayout.location + rangeToLayout.length); } CFRelease(frame); CFRelease(path); } CGSize newSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, range, NULL, constraintSize, NULL); if (nil != framesetter) { CFRelease(framesetter); framesetter = nil; } return CGSizeMake(ceil(newSize.width), ceil(newSize.height)); } @interface NIAttributedLabelImage : NSObject - (CGSize)boxSize; // imageSize + margins @property (nonatomic) NSInteger index; @property (nonatomic, strong) UIImage* image; @property (nonatomic) UIEdgeInsets margins; @property (nonatomic) NIVerticalTextAlignment verticalTextAlignment; @property (nonatomic) CGFloat fontAscent; @property (nonatomic) CGFloat fontDescent; @end @implementation NIAttributedLabelImage - (CGSize)boxSize { return CGSizeMake(self.image.size.width + self.margins.left + self.margins.right, self.image.size.height + self.margins.top + self.margins.bottom); } @end @interface NIAttributedLabel() @property (nonatomic, strong) NSMutableAttributedString* mutableAttributedString; @property (nonatomic) CTFrameRef textFrame; // CFType, manually managed lifetime, see setter. @property (assign) BOOL detectingLinks; // Atomic. @property (nonatomic) BOOL linksHaveBeenDetected; @property (nonatomic, copy) NSArray* detectedlinkLocations; @property (nonatomic, strong) NSMutableArray* explicitLinkLocations; @property (nonatomic, strong) NSTextCheckingResult* originalLink; @property (nonatomic, strong) NSTextCheckingResult* touchedLink; @property (nonatomic, strong) NSTimer* longPressTimer; @property (nonatomic) CGPoint touchPoint; @property (nonatomic, strong) NSTextCheckingResult* actionSheetLink; @property (nonatomic, copy) NSArray* accessibleElements; @property (nonatomic, strong) NSMutableArray *images; @end @interface NIAttributedLabel (ConversionUtilities) + (CTTextAlignment)alignmentFromUITextAlignment:(NSTextAlignment)alignment; + (CTLineBreakMode)lineBreakModeFromUILineBreakMode:(NSLineBreakMode)lineBreakMode; + (NSMutableAttributedString *)mutableAttributedStringFromLabel:(UILabel *)label; @end @implementation NIAttributedLabel @synthesize textFrame = _textFrame; - (void)dealloc { [_longPressTimer invalidate]; // The property is marked 'assign', but retain count for this CFType is managed here and via // the setter. if (NULL != _textFrame) { CFRelease(_textFrame); } } - (CTFrameRef)textFrame { if (NULL == _textFrame) { NSMutableAttributedString* attributedStringWithLinks = [self mutableAttributedStringWithAdditions]; CFAttributedStringRef attributedString = (__bridge CFAttributedStringRef)attributedStringWithLinks; CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(attributedString); CGMutablePathRef path = CGPathCreateMutable(); CGPathAddRect(path, NULL, self.bounds); CTFrameRef textFrame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, NULL); self.textFrame = textFrame; if (textFrame) { CFRelease(textFrame); } CGPathRelease(path); CFRelease(framesetter); } return _textFrame; } - (void)setTextFrame:(CTFrameRef)textFrame { // The property is marked 'assign', but retain count for this CFType is managed via this setter // and -dealloc. if (textFrame != _textFrame) { if (NULL != _textFrame) { CFRelease(_textFrame); } if (NULL != textFrame) { CFRetain(textFrame); } _textFrame = textFrame; } } - (void)_configureDefaults { self.verticalTextAlignment = NIVerticalTextAlignmentTop; self.linkColor = NITintColorForViewWithFallback(self, [UIColor blueColor]); self.dataDetectorTypes = NSTextCheckingTypeLink; self.highlightedLinkBackgroundColor = [UIColor colorWithWhite:0.5f alpha:0.5f]; } - (id)initWithFrame:(CGRect)frame { if ((self = [super initWithFrame:frame])) { [self _configureDefaults]; } return self; } - (void)awakeFromNib { [super awakeFromNib]; [self _configureDefaults]; self.attributedText = [[self class] mutableAttributedStringFromLabel:self]; } - (void)resetTextFrame { self.textFrame = NULL; self.accessibleElements = nil; } - (void)attributedTextDidChange { [self resetTextFrame]; [self invalidateIntrinsicContentSize]; [self setNeedsDisplay]; } - (void)setFrame:(CGRect)frame { BOOL frameDidChange = !CGRectEqualToRect(self.frame, frame); [super setFrame:frame]; if (frameDidChange) { [self attributedTextDidChange]; } } - (CGSize)sizeThatFits:(CGSize)size { if (nil == self.mutableAttributedString) { return CGSizeZero; } return NISizeOfAttributedStringConstrainedToSize([self mutableAttributedStringWithAdditions], size, self.numberOfLines); } - (CGSize)intrinsicContentSize { return [self sizeThatFits:[super intrinsicContentSize]]; } #pragma mark - Public - (void)setText:(NSString *)text { [super setText:text]; self.attributedText = [[self class] mutableAttributedStringFromLabel:self]; // Apply NIAttributedLabel-specific styles. [self.mutableAttributedString nimbuskit_setUnderlineStyle:_underlineStyle modifier:_underlineStyleModifier]; [self.mutableAttributedString nimbuskit_setStrokeWidth:_strokeWidth]; [self.mutableAttributedString nimbuskit_setStrokeColor:_strokeColor]; [self.mutableAttributedString nimbuskit_setKern:_textKern]; } - (NSAttributedString *)attributedText { return [self.mutableAttributedString copy]; } - (void)setAttributedText:(NSAttributedString *)attributedText { if (self.mutableAttributedString != attributedText) { self.mutableAttributedString = [attributedText mutableCopy]; // Clear the link caches. self.detectedlinkLocations = nil; self.linksHaveBeenDetected = NO; [self removeAllExplicitLinks]; // Remove all images. self.images = nil; // Pull any explicit links from the attributed string itself [self _processLinksInAttributedString:self.mutableAttributedString]; [self attributedTextDidChange]; } } - (void)setAutoDetectLinks:(BOOL)autoDetectLinks { _autoDetectLinks = autoDetectLinks; [self attributedTextDidChange]; } - (void)addLink:(NSURL *)urlLink range:(NSRange)range { if (nil == self.explicitLinkLocations) { self.explicitLinkLocations = [[NSMutableArray alloc] init]; } NSTextCheckingResult* result = [NSTextCheckingResult linkCheckingResultWithRange:range URL:urlLink]; [self.explicitLinkLocations addObject:result]; [self attributedTextDidChange]; } - (void)removeAllExplicitLinks { self.explicitLinkLocations = nil; [self attributedTextDidChange]; } - (void)setTextAlignment:(NSTextAlignment)textAlignment { // We assume that the UILabel implementation will call setNeedsDisplay. Where we don't call super // we call setNeedsDisplay ourselves. if (NSTextAlignmentJustified == textAlignment) { // iOS 6.0 Beta 2 crashes when using justified text alignments for some reason. [super setTextAlignment:NSTextAlignmentLeft]; } else { [super setTextAlignment:textAlignment]; } if (nil != self.mutableAttributedString) { CTTextAlignment alignment = [self.class alignmentFromUITextAlignment:textAlignment]; CTLineBreakMode lineBreak = [self.class lineBreakModeFromUILineBreakMode:self.lineBreakMode]; [self.mutableAttributedString nimbuskit_setTextAlignment:alignment lineBreakMode:lineBreak lineHeight:self.lineHeight]; } } - (void)setLineBreakMode:(NSLineBreakMode)lineBreakMode { [super setLineBreakMode:lineBreakMode]; if (nil != self.mutableAttributedString) { CTTextAlignment alignment = [self.class alignmentFromUITextAlignment:self.textAlignment]; CTLineBreakMode lineBreak = [self.class lineBreakModeFromUILineBreakMode:lineBreakMode]; [self.mutableAttributedString nimbuskit_setTextAlignment:alignment lineBreakMode:lineBreak lineHeight:self.lineHeight]; } } - (void)setTextColor:(UIColor *)textColor { [super setTextColor:textColor]; [self.mutableAttributedString nimbuskit_setTextColor:textColor]; [self attributedTextDidChange]; } - (void)setTextColor:(UIColor *)textColor range:(NSRange)range { [self.mutableAttributedString nimbuskit_setTextColor:textColor range:range]; [self attributedTextDidChange]; } - (void)setFont:(UIFont *)font { [super setFont:font]; [self.mutableAttributedString nimbuskit_setFont:font]; [self attributedTextDidChange]; } - (void)setFont:(UIFont *)font range:(NSRange)range { [self.mutableAttributedString nimbuskit_setFont:font range:range]; [self attributedTextDidChange]; } - (void)setUnderlineStyle:(CTUnderlineStyle)style { if (style != _underlineStyle) { _underlineStyle = style; [self.mutableAttributedString nimbuskit_setUnderlineStyle:style modifier:self.underlineStyleModifier]; [self attributedTextDidChange]; } } - (void)setUnderlineStyleModifier:(CTUnderlineStyleModifiers)modifier { if (modifier != _underlineStyleModifier) { _underlineStyleModifier = modifier; [self.mutableAttributedString nimbuskit_setUnderlineStyle:self.underlineStyle modifier:modifier]; [self attributedTextDidChange]; } } - (void)setUnderlineStyle:(CTUnderlineStyle)style modifier:(CTUnderlineStyleModifiers)modifier range:(NSRange)range { [self.mutableAttributedString nimbuskit_setUnderlineStyle:style modifier:modifier range:range]; [self attributedTextDidChange]; } - (void)setShadowBlur:(CGFloat)shadowBlur { if (_shadowBlur != shadowBlur) { _shadowBlur = shadowBlur; [self attributedTextDidChange]; } } - (void)setStrokeWidth:(CGFloat)strokeWidth { if (_strokeWidth != strokeWidth) { _strokeWidth = strokeWidth; [self.mutableAttributedString nimbuskit_setStrokeWidth:strokeWidth]; [self attributedTextDidChange]; } } - (void)setStrokeWidth:(CGFloat)width range:(NSRange)range { [self.mutableAttributedString nimbuskit_setStrokeWidth:width range:range]; [self attributedTextDidChange]; } - (void)setStrokeColor:(UIColor *)strokeColor { if (_strokeColor != strokeColor) { _strokeColor = strokeColor; [self.mutableAttributedString nimbuskit_setStrokeColor:_strokeColor]; [self attributedTextDidChange]; } } - (void)setStrokeColor:(UIColor*)color range:(NSRange)range { [self.mutableAttributedString nimbuskit_setStrokeColor:color range:range]; [self attributedTextDidChange]; } - (void)setTextKern:(CGFloat)textKern { if (_textKern != textKern) { _textKern = textKern; [self.mutableAttributedString nimbuskit_setKern:_textKern]; [self attributedTextDidChange]; } } - (void)setTextKern:(CGFloat)kern range:(NSRange)range { [self.mutableAttributedString nimbuskit_setKern:kern range:range]; [self attributedTextDidChange]; } - (void)setTailTruncationString:(NSString *)tailTruncationString { if (![_tailTruncationString isEqualToString:tailTruncationString]) { _tailTruncationString = [tailTruncationString copy]; [self attributedTextDidChange]; } } - (void)setLinkColor:(UIColor *)linkColor { if (_linkColor != linkColor) { _linkColor = linkColor; [self attributedTextDidChange]; } } - (void)setLineHeight:(CGFloat)lineHeight { _lineHeight = lineHeight; if (nil != self.mutableAttributedString) { CTTextAlignment alignment = [self.class alignmentFromUITextAlignment:self.textAlignment]; CTLineBreakMode lineBreak = [self.class lineBreakModeFromUILineBreakMode:self.lineBreakMode]; [self.mutableAttributedString nimbuskit_setTextAlignment:alignment lineBreakMode:lineBreak lineHeight:self.lineHeight]; [self attributedTextDidChange]; } } - (void)setHighlightedLinkBackgroundColor:(UIColor *)highlightedLinkBackgroundColor { if (_highlightedLinkBackgroundColor != highlightedLinkBackgroundColor) { _highlightedLinkBackgroundColor = highlightedLinkBackgroundColor; [self attributedTextDidChange]; } } - (void)setLinksHaveUnderlines:(BOOL)linksHaveUnderlines { if (_linksHaveUnderlines != linksHaveUnderlines) { _linksHaveUnderlines = linksHaveUnderlines; [self attributedTextDidChange]; } } - (void)setAttributesForLinks:(NSDictionary *)attributesForLinks { if (_attributesForLinks != attributesForLinks) { _attributesForLinks = attributesForLinks; [self attributedTextDidChange]; } } - (void)setAttributesForHighlightedLink:(NSDictionary *)attributesForHighlightedLink { if (_attributesForHighlightedLink != attributesForHighlightedLink) { _attributesForHighlightedLink = attributesForHighlightedLink; [self attributedTextDidChange]; } } - (void)setExplicitLinkLocations:(NSMutableArray *)explicitLinkLocations { if (_explicitLinkLocations != explicitLinkLocations) { _explicitLinkLocations = explicitLinkLocations; self.accessibleElements = nil; } } - (void)setDetectedlinkLocations:(NSArray *)detectedlinkLocations{ if (_detectedlinkLocations != detectedlinkLocations) { _detectedlinkLocations = detectedlinkLocations; self.accessibleElements = nil; } } - (void)setHighlighted:(BOOL)highlighted { BOOL didChange = self.highlighted != highlighted; [super setHighlighted:highlighted]; if (didChange) { [self attributedTextDidChange]; } } - (void)setHighlightedTextColor:(UIColor *)highlightedTextColor { BOOL didChange = self.highlightedTextColor != highlightedTextColor; [super setHighlightedTextColor:highlightedTextColor]; if (didChange) { [self attributedTextDidChange]; } } - (NSArray *)_matchesFromAttributedString:(NSString *)string { NSError* error = nil; NSDataDetector* linkDetector = [NSDataDetector dataDetectorWithTypes:(NSTextCheckingTypes)self.dataDetectorTypes error:&error]; NSRange range = NSMakeRange(0, string.length); return [linkDetector matchesInString:string options:0 range:range]; } - (void)_deferLinkDetection { if (!self.detectingLinks) { self.detectingLinks = YES; NSString* string = [self.mutableAttributedString.string copy]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSArray* matches = [self _matchesFromAttributedString:string]; self.detectingLinks = NO; dispatch_async(dispatch_get_main_queue(), ^{ self.detectedlinkLocations = matches; self.linksHaveBeenDetected = YES; [self attributedTextDidChange]; }); }); } } // Use an NSDataDetector to find any implicit links in the text. The results are cached until // the text changes. - (void)detectLinks { if (nil == self.mutableAttributedString) { return; } if (self.autoDetectLinks && !self.linksHaveBeenDetected) { if (self.deferLinkDetection) { [self _deferLinkDetection]; } else { self.detectedlinkLocations = [self _matchesFromAttributedString:self.mutableAttributedString.string]; self.linksHaveBeenDetected = YES; } } } - (CGRect)getLineBounds:(CTLineRef)line point:(CGPoint) point { CGFloat ascent = 0.0f; CGFloat descent = 0.0f; CGFloat leading = 0.0f; CGFloat width = (CGFloat)CTLineGetTypographicBounds(line, &ascent, &descent, &leading); CGFloat height = ascent + descent; return CGRectMake(point.x, point.y - descent, width, height); } - (NSTextCheckingResult *)linkAtIndex:(CFIndex)i { NSTextCheckingResult* foundResult = nil; if (self.autoDetectLinks) { [self detectLinks]; for (NSTextCheckingResult* result in self.detectedlinkLocations) { if (NSLocationInRange(i, result.range)) { foundResult = result; break; } } } if (nil == foundResult) { for (NSTextCheckingResult* result in self.explicitLinkLocations) { if (NSLocationInRange(i, result.range)) { foundResult = result; break; } } } return foundResult; } - (void)_processLinksInAttributedString:(NSAttributedString *)attributedString { // Pull any attributes matching the link attribute from the attributed string and store them as // the current set of explicit links. __block NSMutableArray *links = [NSMutableArray array]; [attributedString enumerateAttribute:NIAttributedLabelLinkAttributeName inRange:NSMakeRange(0, attributedString.length) options:0 usingBlock:^(id value, NSRange range, BOOL *stop) { if (value != nil) { [links addObject:value]; } }]; self.explicitLinkLocations = links; } - (CGFloat)_verticalOffsetForBounds:(CGRect)bounds { CGFloat verticalOffset = 0; if (NIVerticalTextAlignmentTop != self.verticalTextAlignment) { // When the text is attached to the top we can easily just start drawing and leave the // remainder. This is the most performant case. // With other alignment modes we must calculate the size of the text first. CGSize textSize = [self sizeThatFits:CGSizeMake(bounds.size.width, CGFLOAT_MAX)]; if (NIVerticalTextAlignmentMiddle == self.verticalTextAlignment) { verticalOffset = floor((bounds.size.height - textSize.height) / 2.f); } else if (NIVerticalTextAlignmentBottom == self.verticalTextAlignment) { verticalOffset = bounds.size.height - textSize.height; } } return verticalOffset; } - (CGAffineTransform)_transformForCoreText { // CoreText context coordinates are the opposite to UIKit so we flip the bounds return CGAffineTransformScale(CGAffineTransformMakeTranslation(0, self.bounds.size.height), 1.f, -1.f); } - (NSTextCheckingResult *)linkAtPoint:(CGPoint)point { if (!CGRectContainsPoint(CGRectInset(self.bounds, 0, -kVMargin), point)) { return nil; } CFArrayRef lines = CTFrameGetLines(self.textFrame); if (!lines) return nil; CFIndex count = CFArrayGetCount(lines); CGPoint origins[count]; CTFrameGetLineOrigins(self.textFrame, CFRangeMake(0,0), origins); CGAffineTransform transform = [self _transformForCoreText]; CGFloat verticalOffset = [self _verticalOffsetForBounds:self.bounds]; for (int i = 0; i < count; i++) { CGPoint linePoint = origins[i]; CTLineRef line = CFArrayGetValueAtIndex(lines, i); CGRect flippedRect = [self getLineBounds:line point:linePoint]; CGRect rect = CGRectApplyAffineTransform(flippedRect, transform); rect = CGRectInset(rect, 0, -kVMargin); rect = CGRectOffset(rect, 0, verticalOffset); if (CGRectContainsPoint(rect, point)) { CGPoint relativePoint = CGPointMake(point.x-CGRectGetMinX(rect), point.y-CGRectGetMinY(rect)); CFIndex idx = CTLineGetStringIndexForPosition(line, relativePoint); NSUInteger offset = 0; for (NIAttributedLabelImage *labelImage in self.images) { if (labelImage.index < idx) { offset++; } } NSTextCheckingResult* foundLink = [self linkAtIndex:idx - offset]; if (foundLink) { return foundLink; } } } return nil; } - (CGRect)_rectForRange:(NSRange)range inLine:(CTLineRef)line lineOrigin:(CGPoint)lineOrigin { CGRect rectForRange = CGRectZero; CFArrayRef runs = CTLineGetGlyphRuns(line); CFIndex runCount = CFArrayGetCount(runs); // Iterate through each of the "runs" (i.e. a chunk of text) and find the runs that // intersect with the range. for (CFIndex k = 0; k < runCount; k++) { CTRunRef run = CFArrayGetValueAtIndex(runs, k); CFRange stringRunRange = CTRunGetStringRange(run); NSRange lineRunRange = NSMakeRange(stringRunRange.location, stringRunRange.length); NSRange intersectedRunRange = NSIntersectionRange(lineRunRange, range); if (intersectedRunRange.length == 0) { // This run doesn't intersect the range, so skip it. continue; } CGFloat ascent = 0.0f; CGFloat descent = 0.0f; CGFloat leading = 0.0f; // Use of 'leading' doesn't properly highlight Japanese-character link. CGFloat width = (CGFloat)CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, NULL); //&leading); CGFloat height = ascent + descent; CGFloat xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, nil); CGRect linkRect = CGRectMake(lineOrigin.x + xOffset - leading, lineOrigin.y - descent, width + leading, height); linkRect.origin.y = round(linkRect.origin.y); linkRect.origin.x = round(linkRect.origin.x); linkRect.size.width = round(linkRect.size.width); linkRect.size.height = round(linkRect.size.height); if (CGRectIsEmpty(rectForRange)) { rectForRange = linkRect; } else { rectForRange = CGRectUnion(rectForRange, linkRect); } } return rectForRange; } - (BOOL)isPoint:(CGPoint)point nearLink:(NSTextCheckingResult *)link { CFArrayRef lines = CTFrameGetLines(self.textFrame); if (nil == lines) { return NO; } CFIndex count = CFArrayGetCount(lines); CGPoint lineOrigins[count]; CTFrameGetLineOrigins(self.textFrame, CFRangeMake(0, 0), lineOrigins); CGAffineTransform transform = [self _transformForCoreText]; CGFloat verticalOffset = [self _verticalOffsetForBounds:self.bounds]; NSRange linkRange = link.range; BOOL isNearLink = NO; for (int i = 0; i < count; i++) { CTLineRef line = CFArrayGetValueAtIndex(lines, i); CGRect linkRect = [self _rectForRange:linkRange inLine:line lineOrigin:lineOrigins[i]]; if (!CGRectIsEmpty(linkRect)) { linkRect = CGRectApplyAffineTransform(linkRect, transform); linkRect = CGRectOffset(linkRect, 0, verticalOffset); linkRect = CGRectInset(linkRect, -kTouchGutter, -kTouchGutter); if (CGRectContainsPoint(linkRect, point)) { isNearLink = YES; break; } } } return isNearLink; } - (NSArray *)_rectsForLink:(NSTextCheckingResult *)link { CFArrayRef lines = CTFrameGetLines(self.textFrame); if (nil == lines) { return nil; } CFIndex count = CFArrayGetCount(lines); CGPoint lineOrigins[count]; CTFrameGetLineOrigins(self.textFrame, CFRangeMake(0, 0), lineOrigins); CGAffineTransform transform = [self _transformForCoreText]; CGFloat verticalOffset = [self _verticalOffsetForBounds:self.bounds]; NSRange linkRange = link.range; NSMutableArray* rects = [NSMutableArray array]; for (int i = 0; i < count; i++) { CTLineRef line = CFArrayGetValueAtIndex(lines, i); CGRect linkRect = [self _rectForRange:linkRange inLine:line lineOrigin:lineOrigins[i]]; if (!CGRectIsEmpty(linkRect)) { linkRect = CGRectApplyAffineTransform(linkRect, transform); linkRect = CGRectOffset(linkRect, 0, verticalOffset); [rects addObject:[NSValue valueWithCGRect:linkRect]]; } } return [rects copy]; } - (void)setTouchedLink:(NSTextCheckingResult *)touchedLink { if (_touchedLink != touchedLink) { _touchedLink = touchedLink; if (self.attributesForHighlightedLink.count > 0) { [self attributedTextDidChange]; } } } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { UITouch* touch = [touches anyObject]; CGPoint point = [touch locationInView:self]; self.touchedLink = [self linkAtPoint:point]; self.touchPoint = point; self.originalLink = self.touchedLink; if (self.originalLink) { [self.longPressTimer invalidate]; if (nil != self.touchedLink) { self.longPressTimer = [NSTimer scheduledTimerWithTimeInterval:kLongPressTimeInterval target:self selector:@selector(_longPressTimerDidFire:) userInfo:nil repeats:NO]; } } else { [super touchesBegan:touches withEvent:event]; } [self setNeedsDisplay]; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { UITouch* touch = [touches anyObject]; CGPoint point = [touch locationInView:self]; if (self.originalLink) { // If the user moves their finger away from the original link, deselect it. // If the user moves their finger back to the original link, reselect it. // Don't allow other links to be selected other than the original link. if (nil != self.originalLink) { NSTextCheckingResult* oldTouchedLink = self.touchedLink; if ([self isPoint:point nearLink:self.originalLink]) { self.touchedLink = self.originalLink; } else { self.touchedLink = nil; } if (oldTouchedLink != self.touchedLink) { [self.longPressTimer invalidate]; self.longPressTimer = nil; [self setNeedsDisplay]; } } // If the user moves their finger within the link beyond a certain gutter amount, reset the // hold timer. The user must hold their finger still for the long press interval in order for // the long press action to fire. if (fabs(self.touchPoint.x - point.x) >= kLongPressGutter || fabs(self.touchPoint.y - point.y) >= kLongPressGutter) { [self.longPressTimer invalidate]; self.longPressTimer = nil; if (nil != self.touchedLink) { self.longPressTimer = [NSTimer scheduledTimerWithTimeInterval:kLongPressTimeInterval target:self selector:@selector(_longPressTimerDidFire:) userInfo:nil repeats:NO]; self.touchPoint = point; } } } else { [super touchesMoved:touches withEvent:event]; } } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { if (self.originalLink) { [self.longPressTimer invalidate]; self.longPressTimer = nil; UITouch* touch = [touches anyObject]; CGPoint point = [touch locationInView:self]; if (nil != self.originalLink) { if ([self isPoint:point nearLink:self.originalLink] && [self.delegate respondsToSelector:@selector(attributedLabel:didSelectTextCheckingResult:atPoint:)]) { [self.delegate attributedLabel:self didSelectTextCheckingResult:self.originalLink atPoint:point]; } } self.touchedLink = nil; self.originalLink = nil; [self setNeedsDisplay]; } else { [super touchesEnded:touches withEvent:event]; } } - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesCancelled:touches withEvent:event]; [self.longPressTimer invalidate]; self.longPressTimer = nil; self.touchedLink = nil; self.originalLink = nil; [self setNeedsDisplay]; } - (UIActionSheet *)actionSheetForResult:(NSTextCheckingResult *)result { UIActionSheet* actionSheet = [[UIActionSheet alloc] initWithTitle:nil delegate:self cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles:nil]; NSString* title = nil; if (NSTextCheckingTypeLink == result.resultType) { if ([result.URL.scheme isEqualToString:@"mailto"]) { title = result.URL.resourceSpecifier; [actionSheet addButtonWithTitle:NSLocalizedString(@"Open in Mail", @"")]; [actionSheet addButtonWithTitle:NSLocalizedString(@"Copy Email Address", @"")]; } else { title = result.URL.absoluteString; [actionSheet addButtonWithTitle:NSLocalizedString(@"Open in Safari", @"")]; [actionSheet addButtonWithTitle:NSLocalizedString(@"Copy URL", @"")]; } } else if (NSTextCheckingTypePhoneNumber == result.resultType) { title = result.phoneNumber; [actionSheet addButtonWithTitle:NSLocalizedString(@"Call", @"")]; [actionSheet addButtonWithTitle:NSLocalizedString(@"Copy Phone Number", @"")]; } else if (NSTextCheckingTypeAddress == result.resultType) { title = [self.mutableAttributedString.string substringWithRange:self.actionSheetLink.range]; [actionSheet addButtonWithTitle:NSLocalizedString(@"Open in Maps", @"")]; [actionSheet addButtonWithTitle:NSLocalizedString(@"Copy Address", @"")]; } else { // This type has not been implemented yet. NI_DASSERT(NO); [actionSheet addButtonWithTitle:NSLocalizedString(@"Copy", @"")]; } actionSheet.title = title; if (!NIIsPad()) { [actionSheet setCancelButtonIndex:[actionSheet addButtonWithTitle:NSLocalizedString(@"Cancel", @"")]]; } return actionSheet; } - (void)_longPressTimerDidFire:(NSTimer *)timer { self.longPressTimer = nil; if (nil != self.touchedLink) { self.actionSheetLink = self.touchedLink; UIActionSheet* actionSheet = [self actionSheetForResult:self.actionSheetLink]; BOOL shouldPresent = YES; if ([self.delegate respondsToSelector:@selector(attributedLabel:shouldPresentActionSheet:withTextCheckingResult:atPoint:)]) { // Give the delegate the opportunity to not show the action sheet or to present its own. shouldPresent = [self.delegate attributedLabel:self shouldPresentActionSheet:actionSheet withTextCheckingResult:self.touchedLink atPoint:self.touchPoint]; } if (shouldPresent) { if (NIIsPad()) { [actionSheet showFromRect:CGRectMake(self.touchPoint.x - 22, self.touchPoint.y - 22, 44, 44) inView:self animated:YES]; } else { [actionSheet showInView:self]; } } else { self.actionSheetLink = nil; } } } - (void)_applyLinkStyleWithResults:(NSArray *)results toAttributedString:(NSMutableAttributedString *)attributedString { for (NSTextCheckingResult* result in results) { if (self.linkColor) { [attributedString nimbuskit_setTextColor:self.linkColor range:result.range]; } // We add a no-op attribute in order to force a run to exist for each link. Otherwise the // runCount will be one in this line, causing the entire line to be highlighted rather than // just the link when when no special attributes are set. [attributedString removeAttribute:NIAttributedLabelLinkAttributeName range:result.range]; [attributedString addAttribute:NIAttributedLabelLinkAttributeName value:result range:result.range]; if (self.linksHaveUnderlines) { [attributedString nimbuskit_setUnderlineStyle:kCTUnderlineStyleSingle modifier:kCTUnderlinePatternSolid range:result.range]; } if (self.attributesForLinks.count > 0) { [attributedString addAttributes:self.attributesForLinks range:result.range]; } if (self.attributesForHighlightedLink.count > 0 && NSEqualRanges(result.range, self.touchedLink.range)) { [attributedString addAttributes:self.attributesForHighlightedLink range:result.range]; } } } // We apply the additional styles immediately before we render the attributed string. This // composites the styles with the existing styles without losing any information. This // makes it possible to turn off links or remove them altogether without losing the existing // style information. - (NSMutableAttributedString *)mutableAttributedStringWithAdditions { NSMutableAttributedString* attributedString = [self.mutableAttributedString mutableCopy]; if (self.autoDetectLinks) { [self _applyLinkStyleWithResults:self.detectedlinkLocations toAttributedString:attributedString]; } [self _applyLinkStyleWithResults:self.explicitLinkLocations toAttributedString:attributedString]; if (self.images.count > 0) { // Sort the label images in reverse order by index so that when we add them the string's indices // remain relatively accurate to the original string. This is necessary because we're inserting // spaces into the string. [self.images sortUsingComparator:^NSComparisonResult(NIAttributedLabelImage* obj1, NIAttributedLabelImage* obj2) { if (obj1.index < obj2.index) { return NSOrderedDescending; } else if (obj1.index > obj2.index) { return NSOrderedAscending; } else { return NSOrderedSame; } }]; for (NIAttributedLabelImage *labelImage in self.images) { CTRunDelegateCallbacks callbacks; memset(&callbacks, 0, sizeof(CTRunDelegateCallbacks)); callbacks.version = kCTRunDelegateVersion1; callbacks.getAscent = NIImageDelegateGetAscentCallback; callbacks.getDescent = NIImageDelegateGetDescentCallback; callbacks.getWidth = NIImageDelegateGetWidthCallback; NSUInteger index = labelImage.index; if (index >= attributedString.length) { index = attributedString.length - 1; } NSDictionary *attributes = [attributedString attributesAtIndex:index effectiveRange:NULL]; CTFontRef font = (__bridge CTFontRef)[attributes valueForKey:(__bridge id)kCTFontAttributeName]; if (font != NULL) { labelImage.fontAscent = CTFontGetAscent(font); labelImage.fontDescent = CTFontGetDescent(font); } CTRunDelegateRef delegate = CTRunDelegateCreate(&callbacks, (__bridge void *)labelImage); // Character to use as recommended by kCTRunDelegateAttributeName documentation. unichar objectReplacementChar = 0xFFFC; NSString *objectReplacementString = [NSString stringWithCharacters:&objectReplacementChar length:1]; NSMutableAttributedString* space = [[NSMutableAttributedString alloc] initWithString:objectReplacementString]; CFRange range = CFRangeMake(0, 1); CFMutableAttributedStringRef spaceString = (__bridge_retained CFMutableAttributedStringRef)space; CFAttributedStringSetAttribute(spaceString, range, kCTRunDelegateAttributeName, delegate); // Explicitly set the writing direction of this string to LTR, because in 'drawImages' we draw // for LTR by drawing at offset to offset + width vs to offset - width as you would for RTL. CFAttributedStringSetAttribute(spaceString, range, kCTWritingDirectionAttributeName, (__bridge CFArrayRef)@[@(kCTWritingDirectionLeftToRight)]); CFRelease(delegate); CFRelease(spaceString); [attributedString insertAttributedString:space atIndex:labelImage.index]; } } if (self.isHighlighted) { [attributedString nimbuskit_setTextColor:self.highlightedTextColor]; } return attributedString; } - (NSInteger)numberOfDisplayedLines { CFArrayRef lines = CTFrameGetLines(self.textFrame); return self.numberOfLines > 0 ? MIN(self.numberOfLines, CFArrayGetCount(lines)) : CFArrayGetCount(lines); } - (void)drawImages { if (0 == self.images.count) { return; } CGContextRef ctx = UIGraphicsGetCurrentContext(); CFArrayRef lines = CTFrameGetLines(self.textFrame); CFIndex lineCount = CFArrayGetCount(lines); CGPoint lineOrigins[lineCount]; CTFrameGetLineOrigins(self.textFrame, CFRangeMake(0, 0), lineOrigins); NSInteger numberOfLines = [self numberOfDisplayedLines]; for (CFIndex i = 0; i < numberOfLines; i++) { CTLineRef line = CFArrayGetValueAtIndex(lines, i); CFArrayRef runs = CTLineGetGlyphRuns(line); CFIndex runCount = CFArrayGetCount(runs); CGPoint lineOrigin = lineOrigins[i]; CGFloat lineAscent; CGFloat lineDescent; CTLineGetTypographicBounds(line, &lineAscent, &lineDescent, NULL); CGFloat lineHeight = lineAscent + lineDescent; CGFloat lineBottomY = lineOrigin.y - lineDescent; // Iterate through each of the "runs" (i.e. a chunk of text) and find the runs that // intersect with the range. for (CFIndex k = 0; k < runCount; k++) { CTRunRef run = CFArrayGetValueAtIndex(runs, k); NSDictionary *runAttributes = (__bridge NSDictionary *)CTRunGetAttributes(run); CTRunDelegateRef delegate = (__bridge CTRunDelegateRef)[runAttributes valueForKey:(__bridge id)kCTRunDelegateAttributeName]; if (nil == delegate) { continue; } NIAttributedLabelImage* labelImage = (__bridge NIAttributedLabelImage *)CTRunDelegateGetRefCon(delegate); CGFloat ascent = 0.0f; CGFloat descent = 0.0f; CGFloat width = (CGFloat)CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, NULL); CGFloat imageBoxHeight = labelImage.boxSize.height; CGFloat xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, nil); CGFloat imageBoxOriginY = 0.0f; switch (labelImage.verticalTextAlignment) { case NIVerticalTextAlignmentTop: imageBoxOriginY = lineBottomY + (lineHeight - imageBoxHeight); break; case NIVerticalTextAlignmentMiddle: imageBoxOriginY = lineBottomY + (lineHeight - imageBoxHeight) / 2.f; break; case NIVerticalTextAlignmentBottom: imageBoxOriginY = lineBottomY; break; } CGRect rect = CGRectMake(lineOrigin.x + xOffset, imageBoxOriginY, width, imageBoxHeight); UIEdgeInsets flippedMargins = labelImage.margins; CGFloat top = flippedMargins.top; flippedMargins.top = flippedMargins.bottom; flippedMargins.bottom = top; CGRect imageRect = UIEdgeInsetsInsetRect(rect, flippedMargins); imageRect = CGRectOffset(imageRect, 0, -[self _verticalOffsetForBounds:self.bounds]); CGContextDrawImage(ctx, imageRect, labelImage.image.CGImage); } } } - (void)drawHighlightWithRect:(CGRect)rect { if ((nil == self.touchedLink && nil == self.actionSheetLink) || nil == self.highlightedLinkBackgroundColor) { return; } [self.highlightedLinkBackgroundColor setFill]; NSRange linkRange = nil != self.touchedLink ? self.touchedLink.range : self.actionSheetLink.range; CFArrayRef lines = CTFrameGetLines(self.textFrame); CFIndex count = CFArrayGetCount(lines); CGPoint lineOrigins[count]; CTFrameGetLineOrigins(self.textFrame, CFRangeMake(0, 0), lineOrigins); NSInteger numberOfLines = [self numberOfDisplayedLines]; CGContextRef ctx = UIGraphicsGetCurrentContext(); for (CFIndex i = 0; i < numberOfLines; i++) { CTLineRef line = CFArrayGetValueAtIndex(lines, i); CFRange stringRange = CTLineGetStringRange(line); NSRange lineRange = NSMakeRange(stringRange.location, stringRange.length); NSRange intersectedRange = NSIntersectionRange(lineRange, linkRange); if (intersectedRange.length == 0) { continue; } CGRect highlightRect = [self _rectForRange:linkRange inLine:line lineOrigin:lineOrigins[i]]; highlightRect = CGRectOffset(highlightRect, 0, -rect.origin.y); if (!CGRectIsEmpty(highlightRect)) { CGFloat pi = (CGFloat)M_PI; CGFloat radius = 1.0f; CGContextMoveToPoint(ctx, highlightRect.origin.x, highlightRect.origin.y + radius); CGContextAddLineToPoint(ctx, highlightRect.origin.x, highlightRect.origin.y + highlightRect.size.height - radius); CGContextAddArc(ctx, highlightRect.origin.x + radius, highlightRect.origin.y + highlightRect.size.height - radius, radius, pi, pi / 2.0f, 1.0f); CGContextAddLineToPoint(ctx, highlightRect.origin.x + highlightRect.size.width - radius, highlightRect.origin.y + highlightRect.size.height); CGContextAddArc(ctx, highlightRect.origin.x + highlightRect.size.width - radius, highlightRect.origin.y + highlightRect.size.height - radius, radius, pi / 2, 0.0f, 1.0f); CGContextAddLineToPoint(ctx, highlightRect.origin.x + highlightRect.size.width, highlightRect.origin.y + radius); CGContextAddArc(ctx, highlightRect.origin.x + highlightRect.size.width - radius, highlightRect.origin.y + radius, radius, 0.0f, -pi / 2.0f, 1.0f); CGContextAddLineToPoint(ctx, highlightRect.origin.x + radius, highlightRect.origin.y); CGContextAddArc(ctx, highlightRect.origin.x + radius, highlightRect.origin.y + radius, radius, -pi / 2, pi, 1); CGContextFillPath(ctx); } } } - (void)drawAttributedString:(NSAttributedString *)attributedString rect:(CGRect)rect { CGContextRef ctx = UIGraphicsGetCurrentContext(); // This logic adapted from @mattt's TTTAttributedLabel // https://github.com/mattt/TTTAttributedLabel CFArrayRef lines = CTFrameGetLines(self.textFrame); NSInteger numberOfLines = [self numberOfDisplayedLines]; BOOL truncatesLastLine = (self.lineBreakMode == NSLineBreakByTruncatingTail); CGPoint lineOrigins[numberOfLines]; CTFrameGetLineOrigins(self.textFrame, CFRangeMake(0, numberOfLines), lineOrigins); for (CFIndex lineIndex = 0; lineIndex < numberOfLines; lineIndex++) { CGPoint lineOrigin = lineOrigins[lineIndex]; lineOrigin.y -= rect.origin.y; // adjust for verticalTextAlignment CGContextSetTextPosition(ctx, lineOrigin.x, lineOrigin.y); CTLineRef line = CFArrayGetValueAtIndex(lines, lineIndex); BOOL shouldDrawLine = YES; if (truncatesLastLine && lineIndex == numberOfLines - 1) { // Does the last line need truncation? CFRange lastLineRange = CTLineGetStringRange(line); if (lastLineRange.location + lastLineRange.length < (CFIndex)attributedString.length) { CTLineTruncationType truncationType = kCTLineTruncationEnd; NSUInteger truncationAttributePosition = lastLineRange.location + lastLineRange.length - 1; NSAttributedString* tokenAttributedString; { NSDictionary *tokenAttributes = [attributedString attributesAtIndex:truncationAttributePosition effectiveRange:NULL]; NSString* tokenString = ((nil == self.tailTruncationString) ? kEllipsesCharacter : self.tailTruncationString); tokenAttributedString = [[NSAttributedString alloc] initWithString:tokenString attributes:tokenAttributes]; } CTLineRef truncationToken = CTLineCreateWithAttributedString((__bridge CFAttributedStringRef)tokenAttributedString); NSMutableAttributedString *truncationString = [[attributedString attributedSubstringFromRange:NSMakeRange(lastLineRange.location, lastLineRange.length)] mutableCopy]; if (lastLineRange.length > 0) { // Remove any whitespace at the end of the line. unichar lastCharacter = [[truncationString string] characterAtIndex:lastLineRange.length - 1]; if ([[NSCharacterSet whitespaceAndNewlineCharacterSet] characterIsMember:lastCharacter]) { [truncationString deleteCharactersInRange:NSMakeRange(lastLineRange.length - 1, 1)]; } } [truncationString appendAttributedString:tokenAttributedString]; CTLineRef truncationLine = CTLineCreateWithAttributedString((__bridge CFAttributedStringRef)truncationString); CTLineRef truncatedLine = CTLineCreateTruncatedLine(truncationLine, rect.size.width, truncationType, truncationToken); if (!truncatedLine) { // If the line is not as wide as the truncationToken, truncatedLine is NULL truncatedLine = CFRetain(truncationToken); } CFRelease(truncationLine); CFRelease(truncationToken); CTLineDraw(truncatedLine, ctx); CFRelease(truncatedLine); shouldDrawLine = NO; } } if (shouldDrawLine) { CTLineDraw(line, ctx); } } } - (void)drawTextInRect:(CGRect)rect { if (NIVerticalTextAlignmentTop != self.verticalTextAlignment) { rect.origin.y = [self _verticalOffsetForBounds:rect]; } if (self.autoDetectLinks) { [self detectLinks]; } NSMutableAttributedString* attributedStringWithLinks = [self mutableAttributedStringWithAdditions]; if (self.detectedlinkLocations.count > 0 || self.explicitLinkLocations.count > 0) { self.userInteractionEnabled = YES; } if (nil != attributedStringWithLinks) { CGContextRef ctx = UIGraphicsGetCurrentContext(); CGContextSaveGState(ctx); CGAffineTransform transform = [self _transformForCoreText]; CGContextConcatCTM(ctx, transform); [self drawImages]; [self drawHighlightWithRect:rect]; if (nil != self.shadowColor) { CGContextSetShadowWithColor(ctx, self.shadowOffset, self.shadowBlur, self.shadowColor.CGColor); } [self drawAttributedString:attributedStringWithLinks rect:rect]; CGContextRestoreGState(ctx); } else { [super drawTextInRect:rect]; } } #pragma mark - Accessibility - (void)invalidateAccessibleElements { self.accessibleElements = nil; } - (NSArray *)accessibleElements { if (nil != _accessibleElements) { return _accessibleElements; } NSMutableArray* accessibleElements = [NSMutableArray array]; // NSArray arrayWithArray:self.detectedlinkLocations ensures that we're not working with a nil // array. NSArray* allLinks = [[NSArray arrayWithArray:self.detectedlinkLocations] arrayByAddingObjectsFromArray:self.explicitLinkLocations]; for (NSTextCheckingResult* result in allLinks) { NSArray* rectsForLink = [self _rectsForLink:result]; if (0 == rectsForLink.count) { continue; } NSString* label = [self.mutableAttributedString.string substringWithRange:result.range]; for (NSValue* rectValue in rectsForLink) { UIAccessibilityElement* element = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self]; element.accessibilityLabel = label; element.accessibilityFrame = [self convertRect:rectValue.CGRectValue toView:self.window]; element.accessibilityTraits = UIAccessibilityTraitLink; [accessibleElements addObject:element]; } } UIAccessibilityElement* element = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self]; element.accessibilityLabel = self.attributedText.string; element.accessibilityFrame = [self convertRect:self.bounds toView:self.window]; element.accessibilityTraits = UIAccessibilityTraitNone; if (_shouldSortLinksLast) { [accessibleElements insertObject:element atIndex:0]; } else { [accessibleElements addObject:element]; } _accessibleElements = [accessibleElements copy]; return _accessibleElements; } - (BOOL)isAccessibilityElement { return NO; // We handle accessibility for this element in -accessibleElements. } - (NSInteger)accessibilityElementCount { return self.accessibleElements.count; } - (id)accessibilityElementAtIndex:(NSInteger)index { return [self.accessibleElements objectAtIndex:index]; } - (NSInteger)indexOfAccessibilityElement:(id)element { return [self.accessibleElements indexOfObject:element]; } #pragma mark - UIActionSheetDelegate - (void)actionSheet:(UIActionSheet*)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex { if (NSTextCheckingTypeLink == self.actionSheetLink.resultType) { if (buttonIndex == 0) { [[UIApplication sharedApplication] openURL:self.actionSheetLink.URL]; } else if (buttonIndex == 1) { if ([self.actionSheetLink.URL.scheme isEqualToString:@"mailto"]) { [[UIPasteboard generalPasteboard] setString:self.actionSheetLink.URL.resourceSpecifier]; } else { [[UIPasteboard generalPasteboard] setURL:self.actionSheetLink.URL]; } } } else if (NSTextCheckingTypePhoneNumber == self.actionSheetLink.resultType) { if (buttonIndex == 0) { [[UIApplication sharedApplication] openURL:[NSURL URLWithString:[@"tel:" stringByAppendingString:self.actionSheetLink.phoneNumber]]]; } else if (buttonIndex == 1) { [[UIPasteboard generalPasteboard] setString:self.actionSheetLink.phoneNumber]; } } else if (NSTextCheckingTypeAddress == self.actionSheetLink.resultType) { NSString* address = [self.mutableAttributedString.string substringWithRange:self.actionSheetLink.range]; if (buttonIndex == 0) { [[UIApplication sharedApplication] openURL:[NSURL URLWithString:[[@"http://maps.google.com/maps?q=" stringByAppendingString:address] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]]; } else if (buttonIndex == 1) { [[UIPasteboard generalPasteboard] setString:address]; } } else { // Unsupported data type only allows the user to copy. if (buttonIndex == 0) { NSString* text = [self.mutableAttributedString.string substringWithRange:self.actionSheetLink.range]; [[UIPasteboard generalPasteboard] setString:text]; } } self.actionSheetLink = nil; [self setNeedsDisplay]; } - (void)actionSheetCancel:(UIActionSheet *)actionSheet { self.actionSheetLink = nil; [self setNeedsDisplay]; } #pragma mark - Inline Image Support CGFloat NIImageDelegateGetAscentCallback(void* refCon) { NIAttributedLabelImage *labelImage = (__bridge NIAttributedLabelImage *)refCon; switch (labelImage.verticalTextAlignment) { case NIVerticalTextAlignmentMiddle: { CGFloat ascent = labelImage.fontAscent; CGFloat descent = labelImage.fontDescent; CGFloat baselineFromMid = (ascent + descent) / 2 - descent; return labelImage.boxSize.height / 2 + baselineFromMid; } case NIVerticalTextAlignmentTop: return labelImage.fontAscent; case NIVerticalTextAlignmentBottom: default: return labelImage.boxSize.height - labelImage.fontDescent; } } CGFloat NIImageDelegateGetDescentCallback(void* refCon) { NIAttributedLabelImage *labelImage = (__bridge NIAttributedLabelImage *)refCon; switch (labelImage.verticalTextAlignment) { case NIVerticalTextAlignmentMiddle: { CGFloat ascent = labelImage.fontAscent; CGFloat descent = labelImage.fontDescent; CGFloat baselineFromMid = (ascent + descent) / 2 - descent; return labelImage.boxSize.height / 2 - baselineFromMid; } case NIVerticalTextAlignmentTop: return labelImage.boxSize.height - labelImage.fontAscent; case NIVerticalTextAlignmentBottom: default: return labelImage.fontDescent; } } CGFloat NIImageDelegateGetWidthCallback(void* refCon) { NIAttributedLabelImage *labelImage = (__bridge NIAttributedLabelImage *)refCon; return labelImage.image.size.width + labelImage.margins.left + labelImage.margins.right; } - (void)insertImage:(UIImage *)image atIndex:(NSInteger)index { [self insertImage:image atIndex:index margins:UIEdgeInsetsZero verticalTextAlignment:NIVerticalTextAlignmentBottom]; } - (void)insertImage:(UIImage *)image atIndex:(NSInteger)index margins:(UIEdgeInsets)margins { [self insertImage:image atIndex:index margins:margins verticalTextAlignment:NIVerticalTextAlignmentBottom]; } - (void)insertImage:(UIImage *)image atIndex:(NSInteger)index margins:(UIEdgeInsets)margins verticalTextAlignment:(NIVerticalTextAlignment)verticalTextAlignment { NIAttributedLabelImage* labelImage = [[NIAttributedLabelImage alloc] init]; labelImage.index = index; labelImage.image = image; labelImage.margins = margins; labelImage.verticalTextAlignment = verticalTextAlignment; if (nil == self.images) { self.images = [NSMutableArray array]; } [self.images addObject:labelImage]; } @end @implementation NIAttributedLabel (ConversionUtilities) + (CTTextAlignment)alignmentFromUITextAlignment:(NSTextAlignment)alignment { switch (alignment) { case NSTextAlignmentLeft: return kCTLeftTextAlignment; case NSTextAlignmentCenter: return kCTCenterTextAlignment; case NSTextAlignmentRight: return kCTRightTextAlignment; case NSTextAlignmentJustified: return kCTJustifiedTextAlignment; default: return kCTNaturalTextAlignment; } } + (CTLineBreakMode)lineBreakModeFromUILineBreakMode:(NSLineBreakMode)lineBreakMode { switch (lineBreakMode) { case NSLineBreakByWordWrapping: return kCTLineBreakByWordWrapping; case NSLineBreakByCharWrapping: return kCTLineBreakByCharWrapping; case NSLineBreakByClipping: return kCTLineBreakByClipping; case NSLineBreakByTruncatingHead: return kCTLineBreakByTruncatingHead; case NSLineBreakByTruncatingTail: return kCTLineBreakByWordWrapping; // We handle truncation ourself. case NSLineBreakByTruncatingMiddle: return kCTLineBreakByTruncatingMiddle; default: return 0; } } + (NSMutableAttributedString *)mutableAttributedStringFromLabel:(UILabel *)label { NSMutableAttributedString* attributedString = nil; if (label.text.length > 0) { attributedString = [[NSMutableAttributedString alloc] initWithString:label.text]; [attributedString nimbuskit_setFont:label.font]; [attributedString nimbuskit_setTextColor:label.textColor]; CTTextAlignment textAlignment = [self alignmentFromUITextAlignment:label.textAlignment]; CTLineBreakMode lineBreak = [self.class lineBreakModeFromUILineBreakMode:label.lineBreakMode]; CGFloat lineHeight = 0; if ([label isKindOfClass:[NIAttributedLabel class]]) { lineHeight = [(NIAttributedLabel *)label lineHeight]; } [attributedString nimbuskit_setTextAlignment:textAlignment lineBreakMode:lineBreak lineHeight:lineHeight]; } return attributedString; } @end ================================================ FILE: src/NSMutableAttributedString+NimbusKitAttributedLabel.h ================================================ /* Copyright (c) 2011-present, NimbusKit. All rights reserved. Originally created by Roger Chapman This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree and at the http://nimbuskit.info/license url. An additional grant of patent rights can be found in the PATENTS file in the same directory and url. */ #import #import #import /** * This NimbusKit extension of the NSMutableAttributedString class provides a number of convenience * methods for setting styles on attributed strings. * * All methods will remove the attribute from the modification range before applying the changed * attribute. * * @ingroup NimbusKitAttributedLabel */ @interface NSMutableAttributedString (NimbusKitAttributedLabel) - (void)nimbuskit_setFont:(UIFont *)font; - (void)nimbuskit_setTextAlignment:(CTTextAlignment)textAlignment lineBreakMode:(CTLineBreakMode)lineBreakMode lineHeight:(CGFloat)lineHeight; - (void)nimbuskit_setTextColor:(UIColor *)color; - (void)nimbuskit_setBackgroundColor:(UIColor *)color; - (void)nimbuskit_setLigaturesEnabled:(BOOL)enabled; - (void)nimbuskit_setKern:(CGFloat)kern; - (void)nimbuskit_setUnderlineStyle:(CTUnderlineStyle)style modifier:(CTUnderlineStyleModifiers)modifier; - (void)nimbuskit_setStrokeWidth:(CGFloat)width; - (void)nimbuskit_setStrokeColor:(UIColor *)color; - (void)nimbuskit_setLetterpressEnabled:(BOOL)enabled; - (void)nimbuskit_setFont:(UIFont *)font range:(NSRange)range; - (void)nimbuskit_setTextAlignment:(CTTextAlignment)textAlignment lineBreakMode:(CTLineBreakMode)lineBreakMode lineHeight:(CGFloat)lineHeight range:(NSRange)range; - (void)nimbuskit_setTextColor:(UIColor *)color range:(NSRange)range; - (void)nimbuskit_setBackgroundColor:(UIColor *)color range:(NSRange)range; - (void)nimbuskit_setLigaturesEnabled:(BOOL)enabled range:(NSRange)range; - (void)nimbuskit_setKern:(CGFloat)kern range:(NSRange)range; - (void)nimbuskit_setUnderlineStyle:(CTUnderlineStyle)style modifier:(CTUnderlineStyleModifiers)modifier range:(NSRange)range; - (void)nimbuskit_setStrokeWidth:(CGFloat)width range:(NSRange)range; - (void)nimbuskit_setStrokeColor:(UIColor *)color range:(NSRange)range; - (void)nimbuskit_setLetterpressEnabled:(BOOL)enabled range:(NSRange)range; @end /** @name Modifying Styles for the Entire String */ /** * Sets the font for the entire string. * * @sa nimbuskit_setFont:range: * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setFont: */ /** * Sets the text alignment and the line break mode for the entire string. * * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setTextAlignment:lineBreakMode:lineHeight: */ /** * Sets the text color for the entire string. * * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setTextColor: */ /** * Sets the background color for the entire string. * * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setBackgroundColor: */ /** * Sets whether or not ligatures are enabled for the entire string. * * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setLigaturesEnabled: */ /** * Sets the text kern for the entire string. * * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setKern: */ /** * Sets the underline style and modifier for the entire string. * * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setUnderlineStyle:modifier: */ /** * Sets the stroke width for the entire string. * * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setStrokeWidth: */ /** * Sets the stroke color for the entire string. * * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setStrokeColor: */ /** * Sets whether or not the letterpress text style is enabled for the entire string. * * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setLetterpressEnabled: */ /** @name Modifying Styles for Ranges of the String */ /** * Sets the font for a given range. * * @sa nimbuskit_setFont: * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setFont:range: */ /** * Sets the text alignment and line break mode for a given range. * * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setTextAlignment:lineBreakMode:lineHeight:range: */ /** * Sets the text color for a given range. * * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setTextColor:range: */ /** * Sets the background color for a given range. * * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setBackgroundColor:range: */ /** * Sets whether or not ligatures are enabled for a given range. * * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setLigaturesEnabled:range: */ /** * Sets the text kern for a given range. * * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setKern:range: */ /** * Sets the underline style and modifier for a given range. * * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setUnderlineStyle:modifier:range: */ /** * Sets the stroke width for a given range. * * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setStrokeWidth:range: */ /** * Sets the stroke color for a given range. * * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setStrokeColor:range: */ /** * Sets whether or not the letterpress text style is enabled for a given range. * * Does nothing on pre-iOS 7 devices. * * @fn NSMutableAttributedString(NimbusKitAttributedLabel)::nimbuskit_setLetterpressEnabled:range: */ ================================================ FILE: src/NSMutableAttributedString+NimbusKitAttributedLabel.m ================================================ /* Copyright (c) 2011-present, NimbusKit. All rights reserved. Originally created by Roger Chapman This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree and at the http://nimbuskit.info/license url. An additional grant of patent rights can be found in the PATENTS file in the same directory and url. */ #import "NSMutableAttributedString+NimbusKitAttributedLabel.h" #import "NimbusKitBasics.h" #if !defined(__has_feature) || !__has_feature(objc_arc) #error "NimbusKit requires ARC support." #endif #if __IPHONE_OS_VERSION_MIN_REQUIRED < NI_IOS_6_0 #error "NIAttributedLabel requires iOS 6 or higher." #endif NI_FIX_CATEGORY_BUG(NSMutableAttributedStringNimbusKitAttributedLabel) @implementation NSMutableAttributedString (NimbusKitAttributedLabel) + (NSLineBreakMode)nimbuskit_lineBreakModeFromCTLineBreakMode:(CTLineBreakMode)mode { switch (mode) { case kCTLineBreakByWordWrapping: return NSLineBreakByWordWrapping; case kCTLineBreakByCharWrapping: return NSLineBreakByCharWrapping; case kCTLineBreakByClipping: return NSLineBreakByClipping; case kCTLineBreakByTruncatingHead: return NSLineBreakByTruncatingHead; case kCTLineBreakByTruncatingTail: return NSLineBreakByTruncatingTail; case kCTLineBreakByTruncatingMiddle: return NSLineBreakByTruncatingMiddle; } } - (NSRange)nimbuskit_rangeOfEntireString { return NSMakeRange(0, self.length); } - (void)nimbuskit_setFont:(UIFont*)font { [self nimbuskit_setFont:font range:self.nimbuskit_rangeOfEntireString]; } - (void)nimbuskit_setTextAlignment:(CTTextAlignment)textAlignment lineBreakMode:(CTLineBreakMode)lineBreakMode lineHeight:(CGFloat)lineHeight { [self nimbuskit_setTextAlignment:textAlignment lineBreakMode:lineBreakMode lineHeight:lineHeight range:self.nimbuskit_rangeOfEntireString]; } - (void)nimbuskit_setTextColor:(UIColor *)color { [self nimbuskit_setTextColor:color range:self.nimbuskit_rangeOfEntireString]; } - (void)nimbuskit_setBackgroundColor:(UIColor *)color { [self nimbuskit_setBackgroundColor:color range:self.nimbuskit_rangeOfEntireString]; } - (void)nimbuskit_setLigaturesEnabled:(BOOL)enabled { [self nimbuskit_setLigaturesEnabled:enabled range:self.nimbuskit_rangeOfEntireString]; } - (void)nimbuskit_setKern:(CGFloat)kern { [self nimbuskit_setKern:kern range:self.nimbuskit_rangeOfEntireString]; } - (void)nimbuskit_setUnderlineStyle:(CTUnderlineStyle)style modifier:(CTUnderlineStyleModifiers)modifier { [self nimbuskit_setUnderlineStyle:style modifier:modifier range:self.nimbuskit_rangeOfEntireString]; } - (void)nimbuskit_setStrokeWidth:(CGFloat)width { [self nimbuskit_setStrokeWidth:width range:self.nimbuskit_rangeOfEntireString]; } - (void)nimbuskit_setStrokeColor:(UIColor *)color { [self nimbuskit_setStrokeColor:color range:self.nimbuskit_rangeOfEntireString]; } - (void)nimbuskit_setLetterpressEnabled:(BOOL)enabled { [self nimbuskit_setLetterpressEnabled:enabled range:self.nimbuskit_rangeOfEntireString]; } - (void)nimbuskit_setFont:(UIFont *)font range:(NSRange)range { [self removeAttribute:NSFontAttributeName range:range]; if (nil != font) { [self addAttribute:NSFontAttributeName value:font range:range]; } } - (void)nimbuskit_setTextAlignment:(CTTextAlignment)textAlignment lineBreakMode:(CTLineBreakMode)lineBreakMode lineHeight:(CGFloat)lineHeight range:(NSRange)range { NSMutableParagraphStyle* paragraphStyle = [[NSMutableParagraphStyle alloc] init]; paragraphStyle.alignment = NSTextAlignmentFromCTTextAlignment(textAlignment); paragraphStyle.lineBreakMode = [[self class] nimbuskit_lineBreakModeFromCTLineBreakMode:lineBreakMode]; paragraphStyle.minimumLineHeight = lineHeight; paragraphStyle.maximumLineHeight = lineHeight; [self addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:range]; } - (void)nimbuskit_setTextColor:(UIColor *)color range:(NSRange)range { [self removeAttribute:NSForegroundColorAttributeName range:range]; if (nil != color) { [self addAttribute:NSForegroundColorAttributeName value:color range:range]; } } - (void)nimbuskit_setBackgroundColor:(UIColor *)color range:(NSRange)range { [self removeAttribute:NSBackgroundColorAttributeName range:range]; if (nil != color) { [self addAttribute:NSBackgroundColorAttributeName value:color range:range]; } } - (void)nimbuskit_setLigaturesEnabled:(BOOL)enabled range:(NSRange)range { [self removeAttribute:NSLigatureAttributeName range:range]; [self addAttribute:NSLigatureAttributeName value:@((enabled ? TRUE : FALSE)) range:range]; } - (void)nimbuskit_setKern:(CGFloat)kern range:(NSRange)range { [self removeAttribute:NSKernAttributeName range:range]; [self addAttribute:NSKernAttributeName value:@(kern) range:range]; } - (void)nimbuskit_setUnderlineStyle:(CTUnderlineStyle)style modifier:(CTUnderlineStyleModifiers)modifier range:(NSRange)range { [self removeAttribute:NSUnderlineStyleAttributeName range:range]; [self addAttribute:NSUnderlineStyleAttributeName value:@(style|modifier) range:range]; } - (void)nimbuskit_setStrokeWidth:(CGFloat)width range:(NSRange)range { [self removeAttribute:NSStrokeWidthAttributeName range:range]; [self addAttribute:NSStrokeWidthAttributeName value:@(width) range:range]; } - (void)nimbuskit_setStrokeColor:(UIColor *)color range:(NSRange)range { [self removeAttribute:NSStrokeColorAttributeName range:range]; if (nil != color.CGColor) { [self addAttribute:NSStrokeColorAttributeName value:color range:range]; } } - (void)nimbuskit_setLetterpressEnabled:(BOOL)enabled range:(NSRange)range { // Introduced in iOS 7 - avoid crashing on older OSes. if (nil == NSTextEffectLetterpressStyle) { return; } [self removeAttribute:NSTextEffectAttributeName range:range]; if (enabled) { [self addAttribute:NSTextEffectAttributeName value:NSTextEffectLetterpressStyle range:range]; } } @end ================================================ FILE: src/NimbusKitAttributedLabel.h ================================================ /* Copyright (c) 2011-present, NimbusKit. All rights reserved. Originally created by Roger Chapman This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree and at the http://nimbuskit.info/license url. An additional grant of patent rights can be found in the PATENTS file in the same directory and url. */ #ifndef _NIMBUSKIT_ATTRIBUTEDLABEL_H_ #define _NIMBUSKIT_ATTRIBUTEDLABEL_H_ #import "NIAttributedLabel.h" #import #import #import #pragma mark Current Version #ifndef NIMBUSKIT_ATTRIBUTEDLABEL_VERSION #define NIMBUSKIT_ATTRIBUTEDLABEL_VERSION NIMBUSKIT_ATTRIBUTEDLABEL_1_0_0 #endif #endif // _NIMBUSKIT_ATTRIBUTEDLABEL_H_ #pragma mark All Known Versions #ifndef NIMBUSKIT_ATTRIBUTEDLABEL_1_0_0 #define NIMBUSKIT_ATTRIBUTEDLABEL_1_0_0 10000 #endif #pragma mark Version Check #ifndef NI_SUPPRESS_VERSION_WARNINGS #if NIMBUSKIT_ATTRIBUTEDLABEL_VERSION < NIMBUSKIT_ATTRIBUTEDLABEL_1_0_0 // These macros allow us to inline C-strings with macro values. #ifndef NI_MACRO_DEFER #define NI_MACRO_DEFER(M,...) M(__VA_ARGS__) #endif #ifndef NI_MACRO_STR #define NI_MACRO_STR(X) #X #endif #ifndef NI_MACRO_INLINE_STR #define NI_MACRO_INLINE_STR(str) NI_MACRO_DEFER(NI_MACRO_STR, str) #endif #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" #endif // NIMBUSKIT_ATTRIBUTEDLABEL_VERSION check #endif // #ifndef NI_SUPPRESS_VERSION_WARNINGS ================================================ FILE: src/NimbusKitBasics.h ================================================ /* Copyright (c) 2011-present, NimbusKit. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree and at the http://nimbuskit.info/license url. An additional grant of patent rights can be found in the PATENTS file in the same directory and url. */ #import #import // All macros #ifndef'd so that they can be individually overwritten if necessary. #ifndef _NIMBUSKIT_BASICS_H_ #define _NIMBUSKIT_BASICS_H_ #pragma mark Compiler Features #ifndef NI_DEPRECATED_METHOD #if __has_feature(attribute_deprecated_with_message) #define NI_DEPRECATED_METHOD(_msg) __attribute__((deprecated(_msg))) // Example: // - (void)yourDeprecatedMethod:(id)arg NI_DESIGNATED_INITIALIZER; #else #define NI_DEPRECATED_METHOD(_msg) __attribute__((deprecated)) #endif // #if __has_feature #endif // #ifndef NI_DEPRECATED_METHOD #ifndef NI_DESIGNATED_INITIALIZER #if __has_attribute(objc_designated_initializer) #define NI_DESIGNATED_INITIALIZER __attribute((objc_designated_initializer)) // Example: // - (instancetype)initWithArg:(id)arg NI_DESIGNATED_INITIALIZER; #else #define NI_DESIGNATED_INITIALIZER #endif // #if __has_feature #endif // #ifndef NI_DESIGNATED_INITIALIZER // For use in sources which contain only categories. Removes need for -force_load -all_load when building libraries. // Use once per source (.m) file (not per category). // name must be globally unique. Generally a good idea to prefix it. #ifndef NI_FIX_CATEGORY_BUG #define NI_FIX_CATEGORY_BUG(name) @interface NI_FIX_CATEGORY_BUG_##name : NSObject @end \ @implementation NI_FIX_CATEGORY_BUG_##name @end // Example: // NI_FIX_CATEGORY_BUG(NSMutableAttributedStringNimbusAttributedLabel) // @implementation NSMutableAttributedString (NimbusAttributedLabel) #endif #ifndef NI_IS_FLAG_SET #define NI_IS_FLAG_SET(value, flag) (((value) & (flag)) == (flag)) // Example: // if (NI_IS_FLAG_SET(autoresizingMask, UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight) // YES only if BOTH width and height are specified on the mask. #endif #pragma mark UIColor Generators #ifndef NI_RGBCOLOR #define NI_RGBCOLOR(r,g,b) [UIColor colorWithRed:(r)/255.0f green:(g)/255.0f blue:(b)/255.0f alpha:1] // Example: // NI_RGBCOLOR(255, 0, 255) for a vibrant debugging color #endif // `a` is a floating point value [0...1]. #ifndef NI_RGBACOLOR #define NI_RGBACOLOR(r,g,b,a) [UIColor colorWithRed:(r)/255.0f green:(g)/255.0f blue:(b)/255.0f alpha:(a)] // Example: // NI_RGBACOLOR(255, 0, 255, 0.5) for a semi-translucently vibrant debugging color #endif #ifndef NI_HEXCOLOR #define NI_HEXCOLOR(hex) RGBCOLOR(((hex >> 16) & 0xFF), ((hex >> 8) & 0xFF), ((hex) & 0xFF)) // Example: // NI_HEXCOLOR(0xFF00FF) for colors pasted from DigitalColor Meter (handy tool, use it!) #endif // `a` is a floating point value [0...1]. #ifndef NI_HEXACOLOR #define NI_HEXACOLOR(hex,a) RGBACOLOR(((hex >> 16) & 0xFF), ((hex >> 8) & 0xFF), ((hex) & 0xFF), (a)) // Example: // NI_HEXACOLOR(0xFF00FF, 0.5) for colors pasted from DigitalColor Meter, but with alpha #endif #pragma mark Tools for Debugging #if defined(DEBUG) && !defined(NI_DISABLE_DASSERT) #import #import #import // From: http://developer.apple.com/mac/library/qa/qa2004/qa1361.html CG_INLINE int NIIsInDebugger(void) { int mib[4]; struct kinfo_proc info; size_t size; // Initialize the flags so that, if sysctl fails for some bizarre reason, we get a predictable result. info.kp_proc.p_flag = 0; // Initialize mib, which tells sysctl the info we want, in this case we're looking for information // about a specific process ID. mib[0] = CTL_KERN; mib[1] = KERN_PROC; mib[2] = KERN_PROC_PID; mib[3] = getpid(); size = sizeof(info); sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0); // We're being debugged if the P_TRACED flag is set. return (info.kp_proc.p_flag & P_TRACED) != 0; } CG_INLINE BOOL NIIsRunningTests(void) { NSString* injectBundle = [[NSProcessInfo processInfo] environment][@"XCInjectBundle"]; NSString* pathExtension = [injectBundle pathExtension]; return ([pathExtension isEqualToString:@"octest"] || [pathExtension isEqualToString:@"xctest"]); } #if TARGET_IPHONE_SIMULATOR // We use the __asm__ in this macro so that when a break occurs, we don't have to step out of // a "breakInDebugger" function. #define NI_DASSERT(xx) { if (!(xx)) { NI_DPRINT(@"NI_DASSERT failed: %s", #xx); \ if (NIIsInDebugger() && !NIIsRunningTests()) { __asm__("int $3\n" : : ); } } \ } ((void)0) #else #define NI_DASSERT(xx) { if (!(xx)) { NI_DPRINT(@"NI_DASSERT failed: %s", #xx); \ if (NIIsInDebugger() && !NIIsRunningTests()) { raise(SIGTRAP); } } \ } ((void)0) #endif // #if TARGET_IPHONE_SIMULATOR #else // The ((void)0) syntax allows us force macros to be terminated with a `;` as though they were functions. #define NI_DASSERT(xx) ((void)0) #endif // #if defined(DEBUG) && !defined(NI_DISABLE_DASSERT) #if defined(DEBUG) #define NI_DPRINT(xx, ...) NSLog(@"%s(%d): " xx, __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__) #else #define NI_DPRINT(xx, ...) ((void)0) #endif #if defined(DEBUG) #define NI_DCONDITIONLOG(condition, xx, ...) { if ((condition)) { NI_DPRINT(xx, ##__VA_ARGS__); } } ((void)0) #else #define NI_DCONDITIONLOG(condition, xx, ...) ((void)0) #endif #define NI_DPRINTMETHODNAME() NI_DPRINT(@"%s", __PRETTY_FUNCTION__) #pragma mark Short-hand runtime checks. CG_INLINE BOOL NIIsPad(void) { return [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad; } CG_INLINE BOOL NIIsPhone(void) { return [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone; } CG_INLINE CGFloat NIScreenScale(void) { return [[UIScreen mainScreen] scale]; } CG_INLINE BOOL NIIsRetina(void) { return [[UIScreen mainScreen] scale] == 2.f; } // Pre-iOS 7-safe mechanism for accessing UIView's tintColor. CG_INLINE UIColor* NITintColorForViewWithFallback(UIView* view, UIColor* fallbackColor) { return [view respondsToSelector:@selector(tintColor)] ? view.tintColor : fallbackColor; } CG_INLINE BOOL NIDeviceOSVersionIsAtLeast(double versionNumber) { return kCFCoreFoundationVersionNumber >= versionNumber; } #pragma mark iOS Version Numbers /** Released on July 11, 2008 */ #define NI_IOS_2_0 20000 /** Released on September 9, 2008 */ #define NI_IOS_2_1 20100 /** Released on November 21, 2008 */ #define NI_IOS_2_2 20200 /** Released on June 17, 2009 */ #define NI_IOS_3_0 30000 /** Released on September 9, 2009 */ #define NI_IOS_3_1 30100 /** Released on April 3, 2010 */ #define NI_IOS_3_2 30200 /** Released on June 21, 2010 */ #define NI_IOS_4_0 40000 /** Released on September 8, 2010 */ #define NI_IOS_4_1 40100 /** Released on November 22, 2010 */ #define NI_IOS_4_2 40200 /** Released on March 9, 2011 */ #define NI_IOS_4_3 40300 /** Released on October 12, 2011. */ #define NI_IOS_5_0 50000 /** Released on March 7, 2012. */ #define NI_IOS_5_1 50100 /** Released on September 19, 2012. */ #define NI_IOS_6_0 60000 /** Released on January 28, 2013. */ #define NI_IOS_6_1 60100 /** Released on September 18, 2013 */ #define NI_IOS_7_0 70000 #ifndef kCFCoreFoundationVersionNumber_iPhoneOS_2_0 #define kCFCoreFoundationVersionNumber_iPhoneOS_2_0 478.23 #endif #ifndef kCFCoreFoundationVersionNumber_iPhoneOS_2_1 #define kCFCoreFoundationVersionNumber_iPhoneOS_2_1 478.26 #endif #ifndef kCFCoreFoundationVersionNumber_iPhoneOS_2_2 #define kCFCoreFoundationVersionNumber_iPhoneOS_2_2 478.29 #endif #ifndef kCFCoreFoundationVersionNumber_iPhoneOS_3_0 #define kCFCoreFoundationVersionNumber_iPhoneOS_3_0 478.47 #endif #ifndef kCFCoreFoundationVersionNumber_iPhoneOS_3_1 #define kCFCoreFoundationVersionNumber_iPhoneOS_3_1 478.52 #endif #ifndef kCFCoreFoundationVersionNumber_iPhoneOS_3_2 #define kCFCoreFoundationVersionNumber_iPhoneOS_3_2 478.61 #endif #ifndef kCFCoreFoundationVersionNumber_iOS_4_0 #define kCFCoreFoundationVersionNumber_iOS_4_0 550.32 #endif #ifndef kCFCoreFoundationVersionNumber_iOS_4_1 #define kCFCoreFoundationVersionNumber_iOS_4_1 550.38 #endif #ifndef kCFCoreFoundationVersionNumber_iOS_4_2 #define kCFCoreFoundationVersionNumber_iOS_4_2 550.52 #endif #ifndef kCFCoreFoundationVersionNumber_iOS_4_3 #define kCFCoreFoundationVersionNumber_iOS_4_3 550.52 #endif #ifndef kCFCoreFoundationVersionNumber_iOS_5_0 #define kCFCoreFoundationVersionNumber_iOS_5_0 675.00 #endif #ifndef kCFCoreFoundationVersionNumber_iOS_5_1 #define kCFCoreFoundationVersionNumber_iOS_5_1 690.10 #endif #ifndef kCFCoreFoundationVersionNumber_iOS_6_0 #define kCFCoreFoundationVersionNumber_iOS_6_0 793.00 #endif #ifndef kCFCoreFoundationVersionNumber_iOS_6_1 #define kCFCoreFoundationVersionNumber_iOS_6_1 793.00 #endif #pragma mark 32/64 Bit Support #if CGFLOAT_IS_DOUBLE #define NI_CGFLOAT_EPSILON DBL_EPSILON #else #define NI_CGFLOAT_EPSILON FLT_EPSILON #endif #ifndef NI_DISABLE_GENERIC_MATH #import // Until tgmath.h is able to work with modules enabled, the following explicit remappings of the // common math functions are provided. // http://stackoverflow.com/questions/23333287/tgmath-h-doesnt-work-if-modules-are-enabled // http://www.openradar.me/16744288 #undef acos #define acos(__x) __tg_acos(__tg_promote1((__x))(__x)) #undef asin #define asin(__x) __tg_asin(__tg_promote1((__x))(__x)) #undef atan #define atan(__x) __tg_atan(__tg_promote1((__x))(__x)) #undef atan2 #define atan2(__x, __y) __tg_atan2(__tg_promote2((__x), (__y))(__x), __tg_promote2((__x), (__y))(__y)) #undef cos #define cos(__x) __tg_cos(__tg_promote1((__x))(__x)) #undef sin #define sin(__x) __tg_sin(__tg_promote1((__x))(__x)) #undef tan #define tan(__x) __tg_tan(__tg_promote1((__x))(__x)) #undef acosh #define acosh(__x) __tg_acosh(__tg_promote1((__x))(__x)) #undef asinh #define asinh(__x) __tg_asinh(__tg_promote1((__x))(__x)) #undef atanh #define atanh(__x) __tg_atanh(__tg_promote1((__x))(__x)) #undef cosh #define cosh(__x) __tg_cosh(__tg_promote1((__x))(__x)) #undef sinh #define sinh(__x) __tg_sinh(__tg_promote1((__x))(__x)) #undef tanh #define tanh(__x) __tg_tanh(__tg_promote1((__x))(__x)) #undef exp #define exp(__x) __tg_exp(__tg_promote1((__x))(__x)) #undef exp2 #define exp2(__x) __tg_exp2(__tg_promote1((__x))(__x)) #undef expm1 #define expm1(__x) __tg_expm1(__tg_promote1((__x))(__x)) #undef log #define log(__x) __tg_log(__tg_promote1((__x))(__x)) #undef log10 #define log10(__x) __tg_log10(__tg_promote1((__x))(__x)) #undef log2 #define log2(__x) __tg_log2(__tg_promote1((__x))(__x)) #undef log1p #define log1p(__x) __tg_log1p(__tg_promote1((__x))(__x)) #undef logb #define logb(__x) __tg_logb(__tg_promote1((__x))(__x)) #undef fabs #define fabs(__x) __tg_fabs(__tg_promote1((__x))(__x)) #undef cbrt #define cbrt(__x) __tg_cbrt(__tg_promote1((__x))(__x)) #undef hypot #define hypot(__x, __y) __tg_hypot(__tg_promote2((__x), (__y))(__x), __tg_promote2((__x), (__y))(__y)) #undef pow #define pow(__x, __y) __tg_pow(__tg_promote2((__x), (__y))(__x), __tg_promote2((__x), (__y))(__y)) #undef sqrt #define sqrt(__x) __tg_sqrt(__tg_promote1((__x))(__x)) #undef erf #define erf(__x) __tg_erf(__tg_promote1((__x))(__x)) #undef erfc #define erfc(__x) __tg_erfc(__tg_promote1((__x))(__x)) #undef lgamma #define lgamma(__x) __tg_lgamma(__tg_promote1((__x))(__x)) #undef tgamma #define tgamma(__x) __tg_tgamma(__tg_promote1((__x))(__x)) #undef ceil #define ceil(__x) __tg_ceil(__tg_promote1((__x))(__x)) #undef floor #define floor(__x) __tg_floor(__tg_promote1((__x))(__x)) #undef nearbyint #define nearbyint(__x) __tg_nearbyint(__tg_promote1((__x))(__x)) #undef rint #define rint(__x) __tg_rint(__tg_promote1((__x))(__x)) #undef round #define round(__x) __tg_round(__tg_promote1((__x))(__x)) #undef trunc #define trunc(__x) __tg_trunc(__tg_promote1((__x))(__x)) #undef fmod #define fmod(__x, __y) __tg_fmod(__tg_promote2((__x), (__y))(__x), __tg_promote2((__x), (__y))(__y)) #undef remainder #define remainder(__x, __y) __tg_remainder(__tg_promote2((__x), (__y))(__x), __tg_promote2((__x), (__y))(__y)) #undef copysign #define copysign(__x, __y) __tg_copysign(__tg_promote2((__x), (__y))(__x), __tg_promote2((__x), (__y))(__y)) #undef nextafter #define nextafter(__x, __y) __tg_nextafter(__tg_promote2((__x), (__y))(__x), __tg_promote2((__x), (__y))(__y)) #undef fdim #define fdim(__x, __y) __tg_fdim(__tg_promote2((__x), (__y))(__x), __tg_promote2((__x), (__y))(__y)) #undef fmax #define fmax(__x, __y) __tg_fmax(__tg_promote2((__x), (__y))(__x), __tg_promote2((__x), (__y))(__y)) #undef fmin #define fmin(__x, __y) __tg_fmin(__tg_promote2((__x), (__y))(__x), __tg_promote2((__x), (__y))(__y)) #endif // #ifndef NI_DISABLE_GENERIC_MATH #pragma mark Current Version #ifndef NIMBUSKIT_BASICS_VERSION #define NIMBUSKIT_BASICS_VERSION NIMBUSKIT_BASICS_1_2_1 #endif #endif // #ifndef _NIMBUSKIT_BASICS_H_ #pragma mark All Known Versions #ifndef NIMBUSKIT_BASICS_1_0_0 #define NIMBUSKIT_BASICS_1_0_0 10000 #endif #ifndef NIMBUSKIT_BASICS_1_1_0 #define NIMBUSKIT_BASICS_1_1_0 10100 #endif #ifndef NIMBUSKIT_BASICS_1_2_0 #define NIMBUSKIT_BASICS_1_2_0 10200 #endif #ifndef NIMBUSKIT_BASICS_1_2_1 #define NIMBUSKIT_BASICS_1_2_1 10201 #endif #pragma mark Version Check #ifndef NI_SUPPRESS_VERSION_WARNINGS #if NIMBUSKIT_BASICS_VERSION < NIMBUSKIT_BASICS_1_2_1 // These macros allow us to inline C-strings with macro values. #ifndef NI_MACRO_DEFER #define NI_MACRO_DEFER(M,...) M(__VA_ARGS__) #endif #ifndef NI_MACRO_STR #define NI_MACRO_STR(X) #X #endif #ifndef NI_MACRO_INLINE_STR #define NI_MACRO_INLINE_STR(str) NI_MACRO_DEFER(NI_MACRO_STR, str) #endif #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" #endif // NIMBUSKIT_BASICS_VERSION check #endif // #ifndef NI_SUPPRESS_VERSION_WARNINGS #pragma mark - ~~~ Docs ~~~ /** @name Macros */ /** * Marks a method or property as deprecated to the compiler. * * To be used like so: * * - (void)someMethod NI_DEPRECATED_METHOD("use someOtherMethod instead"); * * Note that the macro expects a C-string (no @-prefix), not an Objective-C NSString. * * Any use of a deprecated method or property will flag a warning when compiling. * * @param msg A C-string explaining the deprecation. Used in the message * " is deprecated: %s". * @fn #NI_DEPRECATED_METHOD(msg) * @ingroup NimbusKitBasics */ /** * Marks an initializer method as the designated initializer for a class. * * Causes Xcode to throw warnings if the initializer chain is not implemented correctly. * This macro can only be specified on a single initializer. * * @fn #NI_DESIGNATED_INITIALIZER * @ingroup NimbusKitBasics */ /** * Force a category to be loaded when an app starts up. * * Add this macro in every source file that only contains a category implementation. * * When linking to a library, Xcode will NOT link symbols from .m source that only contain * categories. In order to force Xcode to do this you must use the -all_load or -force_load linker * flags. * * By placing this macro in any category .m source you generate an empty class that will cause Xcode * to link all of the contents of the .m without requiring the -all_load or -force_load flags. * * See http://developer.apple.com/library/mac/#qa/qa2006/qa1490.html for more info. * * @fn #NI_FIX_CATEGORY_BUG(name) * @ingroup NimbusKitBasics */ /** * Checks whether a \p flag is set on \p value. * * This macro may be used to correctly check if a mask has a complex flag (more than one bit in the * flag) enabled. * * It is a common error to check for a flag by simply using the & operator, but this only checks if * ANY subset of the flag's bits are set, not that ALL of them are set. * * By using the & operator and then comparing the result to the original \p flag, we ensure that all * bits in \p flag are set on \p value. This macro simplifies that check. * * @fn #NI_IS_FLAG_SET(value, flag) * @ingroup NimbusKitBasics */ /** * Creates an opaque UIColor object from a byte-value color definition. * * @fn #NI_RGBCOLOR(r,g,b) * @ingroup NimbusKitBasics */ /** * Creates a UIColor object from a byte-value color definition and alpha transparency. * * @fn #NI_RGBACOLOR(r,g,b,a) * @ingroup NimbusKitBasics */ /** * Creates an opaque UIColor object from a hex color definition of the form 0xRRGGBB. * * @fn #NI_HEXCOLOR(hex) * @ingroup NimbusKitBasics */ /** * Creates a UIColor object from a hex color definition of the form 0xRRGGBB with alpha * transparency. * * @fn #NI_HEXACOLOR(hex,a) * @ingroup NimbusKitBasics */ /** @name Querying the Debugger State */ /** * Returns a Boolean value indicating whether or not a debugger is attached to the process. * * @fn NIIsInDebugger() * @ingroup NimbusKitBasics */ /** @name Debug Assertions */ /** * If this assertion fails then this macro mimics a breakpoint when a debugger is attached. * * This macro may be used to safely pause execution of a program before it enters crashland. * * The source for this macro is only compiler when the DEBUG flag is defined. * If you wish to explicitly disable NI_DASSERT from being compiled, define NI_DISABLE_DASSERT in * your target's preprocessor macros. * * @fn #NI_DASSERT(xx) * @ingroup NimbusKitBasics */ /** @name Debug Logging */ /** * Only writes to the log when `DEBUG` is defined. * * When `DEBUG` is defined, this log method will always write to the log, regardless of log levels. * It is used by all of the other logging methods in Nimbus' debugging library. * * @fn #NI_DPRINT(xx, ...) * @ingroup NimbusKitBasics */ /** * Write the containing method's name to the log using NI_DPRINT. * * @fn #NI_DPRINTMETHODNAME() * @ingroup NimbusKitBasics */ /** * Only writes to the log if \p condition is satisified. * * This macro powers the level-based loggers. It can also be used for conditionally enabling * families of logs. * * @fn #NI_DCONDITIONLOG(condition, xx, ...) * @ingroup NimbusKitBasics */ /** @name Querying the Hardware */ /** * Checks whether the device the app is currently running on is an iPad or not. * * @returns YES if the device is an iPad. * @fn NIIsPad() * @ingroup NimbusKitBasics */ /** * Checks whether the device the app is currently running on is an * iPhone/iPod touch or not. * * @returns YES if the device is an iPhone or iPod touch. * @fn NIIsPhone() * @ingroup NimbusKitBasics */ /** * Returns the screen's scale. * * @fn NIScreenScale() * @ingroup NimbusKitBasics */ /** * Returns YES if the screen is a retina display, NO otherwise. * * @fn NIIsRetina() * @ingroup NimbusKitBasics */ /** @name Determining Feature Support */ /** * An SDK-agnostic mechanism for getting the tint color of a view. * * On pre-iOS 7 devices, will always return \p fallbackColor. * On devices that support -tintColor on UIView, returns `view.tintColor`. * * tintColor was introduced in iOS 7 as a global mechanism for changing tint color in an app. * * @fn NITintColorForViewWithFallback(UIView* view, UIColor* fallbackColor) * @ingroup NimbusKitBasics */ /** * Checks whether the device's OS version is at least the given version number. * * Useful for runtime checks of the device's version number. * * @attention Apple recommends using respondsToSelector where possible to check for * feature support. Use this method as a last resort. * * @param versionNumber Any value of kCFCoreFoundationVersionNumber. * @fn NIDeviceOSVersionIsAtLeast(double versionNumber) * @ingroup NimbusKitBasics */