├── .gitignore ├── .travis.yml ├── Assets ├── demo.png ├── swifty-text-logo.png └── swifty-text-logo.sketch ├── Docs └── README-en.md ├── LICENSE ├── README.md ├── SwiftyText.podspec ├── SwiftyText.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ └── SwiftyText.xcscheme ├── SwiftyText ├── Info.plist ├── SwiftyLabel │ ├── Extensions │ │ ├── NSAttributedString+SwiftyText.swift │ │ ├── NSLayoutManager+GlyphRects.swift │ │ ├── NSMutableAttributedString+SwiftyText.swift │ │ └── UIBezierPath+Rects.swift │ ├── GestureRecognizers │ │ ├── SwiftyTextGestureRecognizer.swift │ │ ├── SwiftyTextLongPressRecognizer.swift │ │ ├── SwiftyTextTapRecognizer.swift │ │ └── SwiftyTextTouchRecognizer.swift │ ├── SwiftyLabel.swift │ ├── SwiftyTextAttachment.swift │ ├── SwiftyTextDetector.swift │ ├── SwiftyTextLink.swift │ └── SwiftyTextParser.swift └── SwiftyText.h ├── SwiftyTextDemo ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── SwiftyText.imageset │ │ ├── Contents.json │ │ └── swifty-text.png │ └── swift.imageset │ │ ├── Contents.json │ │ └── swift.png ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── BasicViewController.swift ├── Info.plist ├── ListViewController.swift ├── SwiftyLabelCell.swift └── SwiftyTextDemo-Bridging-Header.h └── SwiftyTextTests ├── Info.plist └── SwiftyTextTests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | *.xccheckout 14 | *.moved-aside 15 | DerivedData 16 | *.hmap 17 | *.ipa 18 | *.xcuserstate 19 | 20 | # CocoaPods 21 | # 22 | # We recommend against adding the Pods directory to your .gitignore. However 23 | # you should judge for yourself, the pros and cons are mentioned at: 24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 25 | # 26 | # Pods/ 27 | 28 | # Carthage 29 | # 30 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 31 | # Carthage/Checkouts 32 | 33 | Carthage/Build 34 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode7.1 3 | xcode_sdk: iphonesimulator9.1 4 | xcode_project: SwiftyText.xcodeproj 5 | xcode_scheme: SwiftyText -------------------------------------------------------------------------------- /Assets/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kejinlu/SwiftyText/a3f83bdda84a36253fac3692ecb64f941602b619/Assets/demo.png -------------------------------------------------------------------------------- /Assets/swifty-text-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kejinlu/SwiftyText/a3f83bdda84a36253fac3692ecb64f941602b619/Assets/swifty-text-logo.png -------------------------------------------------------------------------------- /Assets/swifty-text-logo.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kejinlu/SwiftyText/a3f83bdda84a36253fac3692ecb64f941602b619/Assets/swifty-text-logo.sketch -------------------------------------------------------------------------------- /Docs/README-en.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ---- 4 | 5 | [![Build Status](https://travis-ci.org/kejinlu/SwiftyText.svg?branch=master)](https://travis-ci.org/kejinlu/SwiftyText) 6 | 7 | ### Goals & Features 8 | - **Link Attribute** Gestures support, delegate methods 9 | - **Attachment Enhancements** Vertical alignment, view based attachment 10 | - **Text Parser** 11 | - **Asynchronous Text Rendering** Render text in background thread 12 | - **Accessibility** Voiceover supported for links 13 | 14 | ### Requirements 15 | - iOS 8.0+ 16 | - XCode 7.1+ 17 | 18 | ### Integration 19 | 20 | You can use [Carthage](https://github.com/Carthage/Carthage) to install SwiftyText by adding it to your Cartfile: 21 | 22 | ``` 23 | github "kejinlu/SwiftyText" 24 | ``` 25 | 26 | Of course, you can use [Cocoapods](https://github.com/CocoaPods/CocoaPods). 27 | 28 | ``` 29 | platform :ios, '8.0' 30 | use_frameworks! 31 | pod 'SwiftyText' 32 | ``` 33 | 34 | ### Usage 35 | 36 | #### Basic properties 37 | - **text** Assigning a new value to this property also replaces the value of the attributedText property with the same text, albeit without any inherent style attributes. Instead the label styles the new string using the font, textColor, and other style-related properties of the class. 38 | - **attributedText** Assigning a new value to this property also replaces the value of the text property with the same string data, albeit without any formatting information. In addition, assigning a new a value updates the values in the font, textColor, and other style-related properties so that they reflect the style information starting at location 0 in the attributed string. 39 | - **numberOfLines** This property controls the maximum number of lines to use in order to fit the label’s text into its bounding rectangle. The default value for this property is 0 which means using as many lines as needed(By now found a bug with Text Kit,numberOfLines will not work when lineBreakMode set to NSLineBreakByCharWrapping) 40 | 41 | 42 | 43 | Here gives an example of creating a SwiftyLabel: 44 | 45 | 46 | ```objc 47 | let label = SwiftyLabel(frame: CGRectMake(0, 0, 300, 400)) 48 | label.center = self.view.center 49 | label.delegate = self 50 | label.backgroundColor = UIColor(red: 243/255.0, green: 1, blue: 236/255.0, alpha: 1) 51 | label.text = "Swift is a powerful and intuitive programming language for iOS, OS X, tvOS, and watchOS. https://developer.apple.com/swift/resources/ . Writing Swift code is interactive and fun, the syntax is concise yet expressive, and apps run lightning-fast. Swift is ready for your next project — or addition into your current app — because Swift code works side-by-side with Objective-C. " 52 | label.textContainerInset = UIEdgeInsetsMake(6, 6, 6, 6) 53 | label.font = UIFont.systemFontOfSize(14) 54 | label.textColor = UIColor.blackColor() 55 | label.firstLineHeadIndent = 24 56 | label.drawsTextAsynchronously = true 57 | ``` 58 | 59 | #### Link 60 | The **Link** Attribute convert a range of text to clickable item. 61 | The **SwiftyTextLink** class is a model class designed to describe the link style and infomation. 62 | - **attributes**: Link attributes for the target text 63 | - **highlightedAttributes**: When highlighted, set the highlightedAttributes to the target text 64 | - **highlightedMaskRadius**, **highlightedMaskColor**: Stylish for mask when highlighted 65 | - **URL** ,**date**,**timeZone**,**phoneNumber**,**addressComponents**: These properties used for specialized link such as URL links, phoneNumber links 66 | 67 | Example: 68 | 69 | ```objc 70 | let link = SwiftyTextLink() 71 | link.URL = NSURL(string: "https://developer.apple.com/swift/") 72 | link.attributes = [NSForegroundColorAttributeName:UIColor(red: 0, green: 122/255.0, blue: 1.0, alpha: 1.0),NSUnderlineStyleAttributeName:NSUnderlineStyle.StyleSingle.rawValue] 73 | label.textStorage.setLink(link, range: NSMakeRange(0, 5)) 74 | ``` 75 | 76 | 77 | #### TextDetector 78 | 79 | The properties of **TBTextDetector** class: 80 | 81 | - **name**: The name of the detector 82 | - **linkable**: Should make the matched text to be clickable 83 | - **regularExpression**: The instance of NSRegularExpression class or subclass 84 | - **attributes**: Text attributes for matched text 85 | - **highlightedAttributes**: for the link attribute when linable is YES 86 | - **replacementBlock**: When **attributes** is not statisfy the requirements use this block to return the attributed text for replacement 87 | 88 | Example: 89 | 90 | ```objc 91 | let detector = SwiftyTextDetector.detectorWithType([.URL,.Address]) 92 | if detector != nil { 93 | label.addTextDetector(detector!) 94 | } 95 | ``` 96 | 97 | #### Attachment 98 | SwiftyTextAttachment makes some enhancements to vertical alignment and attachment type. With SwiftyTextAttachment you can treat a view as attachement. 99 | 100 | Example: 101 | 102 | ```objc 103 | let imageAttachment = SwiftyTextAttachment() 104 | imageAttachment.image = UIImage(named: "logo") 105 | imageAttachment.attachmentTextVerticalAlignment = .Top 106 | label.textStorage.insertAttachment(imageAttachment, atIndex: label.textStorage.length) 107 | 108 | let sliderAttachment = SwiftyTextAttachment() 109 | let slider = UISlider() 110 | sliderAttachment.contentView = slider; 111 | sliderAttachment.contentViewPadding = 3.0 112 | sliderAttachment.attachmentTextVerticalAlignment = .Center 113 | label.textStorage.insertAttachment(sliderAttachment, atIndex: 8) 114 | ``` 115 | 116 | The screenshot of the demo: 117 | 118 | 119 | 120 | #### Other Text Kit Features 121 | Other features of Text Kit can be achieved by NSTextStorage,NSLayoutManager,and NSTextContainer through properties of SwiftyLabel. 122 | 123 | ### Licenese 124 | SwiftyText is released under the MIT license. See LICENSE for details. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 卢克 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ---- 4 | 5 | [![Build Status](https://travis-ci.org/kejinlu/SwiftyText.svg?branch=master)](https://travis-ci.org/kejinlu/SwiftyText) 6 | 7 | [English](Docs/README-en.md) 8 | 9 | ### 目标及特性 10 | - **链接属性(Link Attribute)** 设置后,对应区域的文本便支持点击效果,单击以及长按都有对应的delegate方法 11 | - **手势自定义** Link的手势支持点击(Tap)以及长按(LongPress), 手势的触发事件都可以通过delegate方法进行处理 12 | - **增强的Attachment** 支持基于View的附件,支持和文本垂直方向的各种对其方式:靠底对齐,居中对齐,靠顶对齐,根据文本高度进行缩放 13 | - **Text Parser** 设置后完成文本模式的识别和文本属性的自动设置,支持自动将识别结果加上link属性,比如识别文本中的所有URL链接,变成可以点击的 14 | - **文本异步渲染**,提升用户体验 15 | - **Accessibility**,支持Voice Over便于盲人使用 16 | 17 | ### 知识点 18 | 如果你想通过阅读源码来学习iOS相关的编程知识,那么通过本项目你可以学到如下一些知识点: 19 | 20 | - 如何直接通过TextKit来渲染界面 21 | - 通过CAShapeLayer来实现一些特殊形状的layer 22 | - 如何实现自定义的手势识别器(Gesture Recognizer),以及多个手势识别器之间如何协同 23 | - 如果通过继承NSTextAttachment并覆盖attachmentBoundsForTextContainer方法来实现attachment的自定义的垂直对齐方式 24 | - 自定义界面控件如何实现Voice Over特性 25 | 26 | ### 要求 27 | - iOS 8.0+ 28 | - Xcode 7.1+ 29 | 30 | ### 集成 31 | 32 | 你可以使用 [Carthage](https://github.com/Carthage/Carthage) 来集成 SwiftyText, 将以下依赖添加到你的Cartfile中: 33 | 34 | ``` 35 | github "kejinlu/SwiftyText" 36 | ``` 37 | 38 | 你也可以使用CocoaPods来进行集成 39 | 40 | ``` 41 | platform :ios, '8.0' 42 | use_frameworks! 43 | pod 'SwiftyText' 44 | ``` 45 | 46 | ### 使用 47 | 48 | #### 基本设置 49 | - text 设置此属性 会替换掉原先设置的attributedText的文本,新文本使用textColor,font,textAlignment,lineSpacing这些属性进行样式的设置 50 | - attributedText 设置此属性会替换原先的text文本以及所有的相关样式,但是之后重新设置textColor,font,textAlignment,lineSpacing,paragraphSpacing,firstLineHeadIndent这些属性的时候,会将样式重新设置到整个attributedText 51 | - numberOfLines将设置文本控件的最大行数,lineBreakMode设置文本的断行模式(目前发现一个Text Kit的bug,当lineBreakMode设置为NSLineBreakByCharWrapping的时候numberOfLines起不了约束的作用) 52 | 53 | 54 | SwiftyText 提供了一些NSAttributedString以及NSMutableAttributedString的扩展方法,方便使用者快捷设置自己的Attributed String 55 | 56 | ``` 57 | extension NSAttributedString { 58 | public func isValidRange(range: NSRange) -> Bool 59 | public func entireRange() -> NSRange 60 | public func proposedSizeWithConstrainedSize(constrainedSize: CGSize, exclusionPaths: [UIBezierPath]?, lineBreakMode: NSLineBreakMode?, maximumNumberOfLines: Int?) -> CGSize //计算最佳Size 61 | public func neighbourFontDescenderWithRange(range: NSRange) -> CGFloat 62 | } 63 | 64 | extension NSMutableAttributedString { 65 | public var font: UIFont? 66 | public var foregroundColor: UIColor? 67 | public func setFont(font: UIFont?, range: NSRange) 68 | public func setForegroundColor(foregroundColor: UIColor?, range: NSRange) 69 | public func setLink(link: SwiftyText.SwiftyTextLink?, range: NSRange) 70 | public func insertAttachment(attachment: SwiftyText.SwiftyTextAttachment, atIndex loc: Int) 71 | } 72 | ``` 73 | 74 | 这里创建一个SwiftyLabel, 代码如下: 75 | 76 | 77 | ```objc 78 | let label = SwiftyLabel(frame: CGRectMake(0, 0, 300, 400)) 79 | label.center = self.view.center 80 | label.delegate = self 81 | label.backgroundColor = UIColor(red: 243/255.0, green: 1, blue: 236/255.0, alpha: 1) 82 | label.text = "Swift is a powerful and intuitive programming language for iOS, OS X, tvOS, and watchOS. https://developer.apple.com/swift/resources/ . Writing Swift code is interactive and fun, the syntax is concise yet expressive, and apps run lightning-fast. Swift is ready for your next project — or addition into your current app — because Swift code works side-by-side with Objective-C. " 83 | label.textContainerInset = UIEdgeInsetsMake(6, 6, 6, 6) 84 | label.font = UIFont.systemFontOfSize(14) 85 | label.textColor = UIColor.blackColor() 86 | label.firstLineHeadIndent = 24 87 | label.drawsTextAsynchronously = true 88 | ``` 89 | 90 | #### Link 91 | 这里的链接指的是文本中可以点击的内容。 92 | 链接属性在SwiftyText中使用 SwiftyTextLink来表示,SwiftyTextLink中包含如下的一些属性: 93 | - attributes 设置链接属性对应的文本的式样,使用标准的NSAttributedString的attributes 94 | - highlightedAttributes 点击高亮时 对应文本的样式属性,非高亮的时候会恢复到高亮之前的样式 95 | - highlightedMaskRadius,highlightedMaskColor 分别设置点击时 文本上的蒙层边角半径以及颜色 96 | - URL,date,timeZone,phoneNumber,addressComponents 这些是可能出现的常见的链接类型的相关值,如果不是这些特定链接,可以使用userInfo自己进行设置 97 | 98 | 代码示例: 99 | 100 | ```objc 101 | let link = SwiftyTextLink() 102 | link.URL = NSURL(string: "https://developer.apple.com/swift/") 103 | link.attributes = [NSForegroundColorAttributeName:UIColor(red: 0, green: 122/255.0, blue: 1.0, alpha: 1.0),NSUnderlineStyleAttributeName:NSUnderlineStyle.StyleSingle.rawValue] 104 | label.setLink(link, range: NSMakeRange(0, 5)) 105 | ``` 106 | 107 | 108 | #### TextParser 109 | 有很多时候我们需要对特定的模式的文本做特殊处理,诸如文本替换,或者特定的文本设置特定的属性,诸如颜色,字体等,这个时候我们便可以通过实现SwiftyTextParser protocol来实现自己的Text Parser,设置到SwiftyLabel中。Text Parser存在的好处在于处理逻辑的复用。 110 | 在SwiftyText中定义了一种叫做Detector的特殊Text Parser,可以通过设置正则以及对应的属性的方式来创建一个Parser。 111 | 还有一个特殊的Text Parser叫做 SwiftyTextSuperParser,它其实就是一个parser container, 是一个Text Parser的容器,这样就可以将多个 Text Parser合并成一个。 112 | 113 | 下面主要讲解下Detector 114 | - name detector的名字 115 | - linkable 是否是链接,如果是链接的话会对匹配的文本设置上Link属性,支持点击, 你还可以通过linkGestures来设置链接支持的手势 116 | - regularExpression 匹配的模式,类型为NSRegularExpression的实例或者其子类的对象 117 | - attributes 匹配的文本需要设置的属性,比如特定颜色,字体,下划线等 118 | - highlightedAttributes 当linkable为YES时,此属性用来决定匹配的文本的点击时的高亮属性 119 | - replacementBlock 当匹配的文本的属性的设置比较复杂的时候,没法通过简单的attributes来实现的时候,可以通过此block返回替换的attributedText 120 | 121 | ```objc 122 | let detector = SwiftyTextDetector.detectorWithType([.URL,.Address]) 123 | if detector != nil { 124 | label.parser = detector 125 | } 126 | ``` 127 | 128 | #### Attachment 129 | SwiftyTextAttachment在NSTextAttachment上做了增强,同时支持基于图片以及基于UIView的Attachment。 130 | 图片,UIView类型的附件都支持和文本在纵向上的各种对齐方式:靠顶对齐,居中,靠底对齐,缩放以适配文本高度,都支持通过设置padding来控制前后的padding。 131 | 图片Attachment还支持通过设置imageSize来控制图片的大小(当垂直对齐为 靠顶对齐,居中,靠底对齐时起作用) 132 | 133 | 134 | ```objc 135 | let imageAttachment = SwiftyTextAttachment() 136 | imageAttachment.image = UIImage(named: "logo") 137 | imageAttachment.attachmentTextVerticalAlignment = .Top 138 | label.insertAttachment(imageAttachment, atIndex: label.textStorage.length) 139 | 140 | let sliderAttachment = SwiftyTextAttachment() 141 | let slider = UISlider() 142 | sliderAttachment.contentView = slider; 143 | sliderAttachment.padding = 3.0 144 | sliderAttachment.attachmentTextVerticalAlignment = .Center 145 | label.insertAttachment(sliderAttachment, atIndex: 8) 146 | ``` 147 | 148 | 下图为demo的效果截图: 149 | 150 | 151 | 152 | #### 其余Text Kit的特性 153 | 其余的Text Kit的特性比如exclusionPaths,可以通过SwiftyLabel的exclusionPaths的属性进行设置。 154 | 155 | ### 许可证 156 | SwiftyText 基于 MIT license进行开源。 具体详情请查看根目录下的LICENSE文件。 157 | -------------------------------------------------------------------------------- /SwiftyText.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'SwiftyText' 3 | s.version = '0.0.3' 4 | s.license = 'MIT' 5 | s.summary = 'SwiftyText in Swift based on Text Kit' 6 | s.homepage = 'https://github.com/kejinlu/SwiftyText' 7 | s.social_media_url = 'http://weibo.com/kejinlu' 8 | s.authors = { 'Luke' => 'kejinlu@gmail.com' } 9 | s.source = { :git => 'https://github.com/kejinlu/SwiftyText.git', :tag => s.version } 10 | 11 | s.ios.deployment_target = '8.0' 12 | 13 | s.source_files = 'SwiftyText/**/*.swift' 14 | 15 | s.requires_arc = true 16 | end -------------------------------------------------------------------------------- /SwiftyText.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | A330DAB11C4DF9FF0016F396 /* SwiftyTextGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A330DAAD1C4DF9FF0016F396 /* SwiftyTextGestureRecognizer.swift */; }; 11 | A330DAB21C4DF9FF0016F396 /* SwiftyTextLongPressRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A330DAAE1C4DF9FF0016F396 /* SwiftyTextLongPressRecognizer.swift */; }; 12 | A330DAB31C4DF9FF0016F396 /* SwiftyTextTapRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A330DAAF1C4DF9FF0016F396 /* SwiftyTextTapRecognizer.swift */; }; 13 | A330DAB41C4DF9FF0016F396 /* SwiftyTextTouchRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = A330DAB01C4DF9FF0016F396 /* SwiftyTextTouchRecognizer.swift */; }; 14 | A35BAC191C1AC705004FFABC /* SwiftyText.h in Headers */ = {isa = PBXBuildFile; fileRef = A35BAC181C1AC705004FFABC /* SwiftyText.h */; settings = {ATTRIBUTES = (Public, ); }; }; 15 | A35BAC201C1AC705004FFABC /* SwiftyText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A35BAC151C1AC705004FFABC /* SwiftyText.framework */; }; 16 | A35BAC251C1AC705004FFABC /* SwiftyTextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A35BAC241C1AC705004FFABC /* SwiftyTextTests.swift */; }; 17 | A35BAC4E1C1AC76A004FFABC /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A35BAC4D1C1AC76A004FFABC /* AppDelegate.swift */; }; 18 | A35BAC501C1AC76A004FFABC /* BasicViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A35BAC4F1C1AC76A004FFABC /* BasicViewController.swift */; }; 19 | A35BAC531C1AC76A004FFABC /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A35BAC511C1AC76A004FFABC /* Main.storyboard */; }; 20 | A35BAC551C1AC76A004FFABC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A35BAC541C1AC76A004FFABC /* Assets.xcassets */; }; 21 | A35BAC581C1AC76A004FFABC /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A35BAC561C1AC76A004FFABC /* LaunchScreen.storyboard */; }; 22 | A35BAC661C1AC808004FFABC /* SwiftyLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A35BAC5F1C1AC808004FFABC /* SwiftyLabel.swift */; }; 23 | A35BAC681C1AC808004FFABC /* SwiftyTextAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = A35BAC611C1AC808004FFABC /* SwiftyTextAttachment.swift */; }; 24 | A35BAC691C1AC808004FFABC /* SwiftyTextDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = A35BAC621C1AC808004FFABC /* SwiftyTextDetector.swift */; }; 25 | A37B90F11C2AC72500F37FA1 /* SwiftyTextParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = A37B90F01C2AC72500F37FA1 /* SwiftyTextParser.swift */; }; 26 | A396DB9E1C3B971C00155664 /* SwiftyText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A35BAC151C1AC705004FFABC /* SwiftyText.framework */; }; 27 | A396DB9F1C3B971C00155664 /* SwiftyText.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A35BAC151C1AC705004FFABC /* SwiftyText.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 28 | A396DBA41C3BE6C100155664 /* ListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A396DBA31C3BE6C100155664 /* ListViewController.swift */; }; 29 | A396DBA61C3C015E00155664 /* SwiftyLabelCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A396DBA51C3C015E00155664 /* SwiftyLabelCell.swift */; }; 30 | A396DBAE1C3C06A000155664 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A396DBAD1C3C06A000155664 /* QuartzCore.framework */; }; 31 | A396DBB01C3C06B100155664 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A396DBAF1C3C06B100155664 /* AudioToolbox.framework */; }; 32 | A396DBB21C3C06B700155664 /* SpriteKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A396DBB11C3C06B700155664 /* SpriteKit.framework */; }; 33 | A3987DE91C22964600CF5202 /* SwiftyTextLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3987DE81C22964600CF5202 /* SwiftyTextLink.swift */; }; 34 | A3BF75AB1C474A00007E1C89 /* NSAttributedString+SwiftyText.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3BF75A71C474A00007E1C89 /* NSAttributedString+SwiftyText.swift */; }; 35 | A3BF75AC1C474A00007E1C89 /* NSLayoutManager+GlyphRects.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3BF75A81C474A00007E1C89 /* NSLayoutManager+GlyphRects.swift */; }; 36 | A3BF75AD1C474A00007E1C89 /* NSMutableAttributedString+SwiftyText.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3BF75A91C474A00007E1C89 /* NSMutableAttributedString+SwiftyText.swift */; }; 37 | A3BF75AE1C474A00007E1C89 /* UIBezierPath+Rects.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3BF75AA1C474A00007E1C89 /* UIBezierPath+Rects.swift */; }; 38 | /* End PBXBuildFile section */ 39 | 40 | /* Begin PBXContainerItemProxy section */ 41 | A35BAC211C1AC705004FFABC /* PBXContainerItemProxy */ = { 42 | isa = PBXContainerItemProxy; 43 | containerPortal = A35BAC0C1C1AC705004FFABC /* Project object */; 44 | proxyType = 1; 45 | remoteGlobalIDString = A35BAC141C1AC705004FFABC; 46 | remoteInfo = SwiftyText; 47 | }; 48 | A396DBA01C3B971C00155664 /* PBXContainerItemProxy */ = { 49 | isa = PBXContainerItemProxy; 50 | containerPortal = A35BAC0C1C1AC705004FFABC /* Project object */; 51 | proxyType = 1; 52 | remoteGlobalIDString = A35BAC141C1AC705004FFABC; 53 | remoteInfo = SwiftyText; 54 | }; 55 | /* End PBXContainerItemProxy section */ 56 | 57 | /* Begin PBXCopyFilesBuildPhase section */ 58 | A396DBA21C3B971C00155664 /* Embed Frameworks */ = { 59 | isa = PBXCopyFilesBuildPhase; 60 | buildActionMask = 2147483647; 61 | dstPath = ""; 62 | dstSubfolderSpec = 10; 63 | files = ( 64 | A396DB9F1C3B971C00155664 /* SwiftyText.framework in Embed Frameworks */, 65 | ); 66 | name = "Embed Frameworks"; 67 | runOnlyForDeploymentPostprocessing = 0; 68 | }; 69 | /* End PBXCopyFilesBuildPhase section */ 70 | 71 | /* Begin PBXFileReference section */ 72 | A330DAAD1C4DF9FF0016F396 /* SwiftyTextGestureRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftyTextGestureRecognizer.swift; sourceTree = ""; }; 73 | A330DAAE1C4DF9FF0016F396 /* SwiftyTextLongPressRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftyTextLongPressRecognizer.swift; sourceTree = ""; }; 74 | A330DAAF1C4DF9FF0016F396 /* SwiftyTextTapRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftyTextTapRecognizer.swift; sourceTree = ""; }; 75 | A330DAB01C4DF9FF0016F396 /* SwiftyTextTouchRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftyTextTouchRecognizer.swift; sourceTree = ""; }; 76 | A35BAC151C1AC705004FFABC /* SwiftyText.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftyText.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 77 | A35BAC181C1AC705004FFABC /* SwiftyText.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftyText.h; sourceTree = ""; }; 78 | A35BAC1A1C1AC705004FFABC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = ../Info.plist; sourceTree = ""; }; 79 | A35BAC1F1C1AC705004FFABC /* SwiftyTextTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftyTextTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 80 | A35BAC241C1AC705004FFABC /* SwiftyTextTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftyTextTests.swift; sourceTree = ""; }; 81 | A35BAC261C1AC705004FFABC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 82 | A35BAC4B1C1AC76A004FFABC /* SwiftyTextDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftyTextDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 83 | A35BAC4D1C1AC76A004FFABC /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 84 | A35BAC4F1C1AC76A004FFABC /* BasicViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicViewController.swift; sourceTree = ""; }; 85 | A35BAC521C1AC76A004FFABC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 86 | A35BAC541C1AC76A004FFABC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 87 | A35BAC571C1AC76A004FFABC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 88 | A35BAC591C1AC76A004FFABC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 89 | A35BAC5F1C1AC808004FFABC /* SwiftyLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftyLabel.swift; sourceTree = ""; }; 90 | A35BAC611C1AC808004FFABC /* SwiftyTextAttachment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftyTextAttachment.swift; sourceTree = ""; }; 91 | A35BAC621C1AC808004FFABC /* SwiftyTextDetector.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftyTextDetector.swift; sourceTree = ""; }; 92 | A37B90F01C2AC72500F37FA1 /* SwiftyTextParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftyTextParser.swift; sourceTree = ""; }; 93 | A396DBA31C3BE6C100155664 /* ListViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewController.swift; sourceTree = ""; }; 94 | A396DBA51C3C015E00155664 /* SwiftyLabelCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftyLabelCell.swift; sourceTree = ""; }; 95 | A396DBAD1C3C06A000155664 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 96 | A396DBAF1C3C06B100155664 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; 97 | A396DBB11C3C06B700155664 /* SpriteKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SpriteKit.framework; path = System/Library/Frameworks/SpriteKit.framework; sourceTree = SDKROOT; }; 98 | A396DBB61C3C08B700155664 /* SwiftyTextDemo-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SwiftyTextDemo-Bridging-Header.h"; sourceTree = ""; }; 99 | A3987DE81C22964600CF5202 /* SwiftyTextLink.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftyTextLink.swift; sourceTree = ""; }; 100 | A3BF75A71C474A00007E1C89 /* NSAttributedString+SwiftyText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+SwiftyText.swift"; sourceTree = ""; }; 101 | A3BF75A81C474A00007E1C89 /* NSLayoutManager+GlyphRects.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSLayoutManager+GlyphRects.swift"; sourceTree = ""; }; 102 | A3BF75A91C474A00007E1C89 /* NSMutableAttributedString+SwiftyText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSMutableAttributedString+SwiftyText.swift"; sourceTree = ""; }; 103 | A3BF75AA1C474A00007E1C89 /* UIBezierPath+Rects.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIBezierPath+Rects.swift"; sourceTree = ""; }; 104 | /* End PBXFileReference section */ 105 | 106 | /* Begin PBXFrameworksBuildPhase section */ 107 | A35BAC111C1AC705004FFABC /* Frameworks */ = { 108 | isa = PBXFrameworksBuildPhase; 109 | buildActionMask = 2147483647; 110 | files = ( 111 | ); 112 | runOnlyForDeploymentPostprocessing = 0; 113 | }; 114 | A35BAC1C1C1AC705004FFABC /* Frameworks */ = { 115 | isa = PBXFrameworksBuildPhase; 116 | buildActionMask = 2147483647; 117 | files = ( 118 | A35BAC201C1AC705004FFABC /* SwiftyText.framework in Frameworks */, 119 | ); 120 | runOnlyForDeploymentPostprocessing = 0; 121 | }; 122 | A35BAC481C1AC76A004FFABC /* Frameworks */ = { 123 | isa = PBXFrameworksBuildPhase; 124 | buildActionMask = 2147483647; 125 | files = ( 126 | A396DBB21C3C06B700155664 /* SpriteKit.framework in Frameworks */, 127 | A396DBB01C3C06B100155664 /* AudioToolbox.framework in Frameworks */, 128 | A396DBAE1C3C06A000155664 /* QuartzCore.framework in Frameworks */, 129 | A396DB9E1C3B971C00155664 /* SwiftyText.framework in Frameworks */, 130 | ); 131 | runOnlyForDeploymentPostprocessing = 0; 132 | }; 133 | /* End PBXFrameworksBuildPhase section */ 134 | 135 | /* Begin PBXGroup section */ 136 | A330DAAC1C4DF9FF0016F396 /* GestureRecognizers */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | A330DAAD1C4DF9FF0016F396 /* SwiftyTextGestureRecognizer.swift */, 140 | A330DAAE1C4DF9FF0016F396 /* SwiftyTextLongPressRecognizer.swift */, 141 | A330DAAF1C4DF9FF0016F396 /* SwiftyTextTapRecognizer.swift */, 142 | A330DAB01C4DF9FF0016F396 /* SwiftyTextTouchRecognizer.swift */, 143 | ); 144 | path = GestureRecognizers; 145 | sourceTree = ""; 146 | }; 147 | A35BAC0B1C1AC705004FFABC = { 148 | isa = PBXGroup; 149 | children = ( 150 | A35BAC171C1AC705004FFABC /* SwiftyText */, 151 | A35BAC231C1AC705004FFABC /* SwiftyTextTests */, 152 | A35BAC4C1C1AC76A004FFABC /* SwiftyTextDemo */, 153 | A35BAC161C1AC705004FFABC /* Products */, 154 | ); 155 | sourceTree = ""; 156 | }; 157 | A35BAC161C1AC705004FFABC /* Products */ = { 158 | isa = PBXGroup; 159 | children = ( 160 | A35BAC151C1AC705004FFABC /* SwiftyText.framework */, 161 | A35BAC1F1C1AC705004FFABC /* SwiftyTextTests.xctest */, 162 | A35BAC4B1C1AC76A004FFABC /* SwiftyTextDemo.app */, 163 | ); 164 | name = Products; 165 | sourceTree = ""; 166 | }; 167 | A35BAC171C1AC705004FFABC /* SwiftyText */ = { 168 | isa = PBXGroup; 169 | children = ( 170 | A35BAC5D1C1AC808004FFABC /* SwiftyLabel */, 171 | A35BAC181C1AC705004FFABC /* SwiftyText.h */, 172 | ); 173 | path = SwiftyText; 174 | sourceTree = ""; 175 | }; 176 | A35BAC231C1AC705004FFABC /* SwiftyTextTests */ = { 177 | isa = PBXGroup; 178 | children = ( 179 | A35BAC241C1AC705004FFABC /* SwiftyTextTests.swift */, 180 | A35BAC261C1AC705004FFABC /* Info.plist */, 181 | ); 182 | path = SwiftyTextTests; 183 | sourceTree = ""; 184 | }; 185 | A35BAC4C1C1AC76A004FFABC /* SwiftyTextDemo */ = { 186 | isa = PBXGroup; 187 | children = ( 188 | A396DBB11C3C06B700155664 /* SpriteKit.framework */, 189 | A396DBAF1C3C06B100155664 /* AudioToolbox.framework */, 190 | A396DBAD1C3C06A000155664 /* QuartzCore.framework */, 191 | A396DBB61C3C08B700155664 /* SwiftyTextDemo-Bridging-Header.h */, 192 | A35BAC4D1C1AC76A004FFABC /* AppDelegate.swift */, 193 | A35BAC4F1C1AC76A004FFABC /* BasicViewController.swift */, 194 | A396DBA31C3BE6C100155664 /* ListViewController.swift */, 195 | A396DBA51C3C015E00155664 /* SwiftyLabelCell.swift */, 196 | A35BAC511C1AC76A004FFABC /* Main.storyboard */, 197 | A35BAC541C1AC76A004FFABC /* Assets.xcassets */, 198 | A35BAC561C1AC76A004FFABC /* LaunchScreen.storyboard */, 199 | A35BAC591C1AC76A004FFABC /* Info.plist */, 200 | ); 201 | path = SwiftyTextDemo; 202 | sourceTree = ""; 203 | }; 204 | A35BAC5D1C1AC808004FFABC /* SwiftyLabel */ = { 205 | isa = PBXGroup; 206 | children = ( 207 | A330DAAC1C4DF9FF0016F396 /* GestureRecognizers */, 208 | A3BF75A61C474A00007E1C89 /* Extensions */, 209 | A35BAC5F1C1AC808004FFABC /* SwiftyLabel.swift */, 210 | A3987DE81C22964600CF5202 /* SwiftyTextLink.swift */, 211 | A35BAC611C1AC808004FFABC /* SwiftyTextAttachment.swift */, 212 | A37B90F01C2AC72500F37FA1 /* SwiftyTextParser.swift */, 213 | A35BAC621C1AC808004FFABC /* SwiftyTextDetector.swift */, 214 | A35BAC1A1C1AC705004FFABC /* Info.plist */, 215 | ); 216 | path = SwiftyLabel; 217 | sourceTree = ""; 218 | }; 219 | A3BF75A61C474A00007E1C89 /* Extensions */ = { 220 | isa = PBXGroup; 221 | children = ( 222 | A3BF75A71C474A00007E1C89 /* NSAttributedString+SwiftyText.swift */, 223 | A3BF75A91C474A00007E1C89 /* NSMutableAttributedString+SwiftyText.swift */, 224 | A3BF75A81C474A00007E1C89 /* NSLayoutManager+GlyphRects.swift */, 225 | A3BF75AA1C474A00007E1C89 /* UIBezierPath+Rects.swift */, 226 | ); 227 | path = Extensions; 228 | sourceTree = ""; 229 | }; 230 | /* End PBXGroup section */ 231 | 232 | /* Begin PBXHeadersBuildPhase section */ 233 | A35BAC121C1AC705004FFABC /* Headers */ = { 234 | isa = PBXHeadersBuildPhase; 235 | buildActionMask = 2147483647; 236 | files = ( 237 | A35BAC191C1AC705004FFABC /* SwiftyText.h in Headers */, 238 | ); 239 | runOnlyForDeploymentPostprocessing = 0; 240 | }; 241 | /* End PBXHeadersBuildPhase section */ 242 | 243 | /* Begin PBXNativeTarget section */ 244 | A35BAC141C1AC705004FFABC /* SwiftyText */ = { 245 | isa = PBXNativeTarget; 246 | buildConfigurationList = A35BAC291C1AC705004FFABC /* Build configuration list for PBXNativeTarget "SwiftyText" */; 247 | buildPhases = ( 248 | A35BAC101C1AC705004FFABC /* Sources */, 249 | A35BAC111C1AC705004FFABC /* Frameworks */, 250 | A35BAC121C1AC705004FFABC /* Headers */, 251 | A35BAC131C1AC705004FFABC /* Resources */, 252 | ); 253 | buildRules = ( 254 | ); 255 | dependencies = ( 256 | ); 257 | name = SwiftyText; 258 | productName = SwiftyText; 259 | productReference = A35BAC151C1AC705004FFABC /* SwiftyText.framework */; 260 | productType = "com.apple.product-type.framework"; 261 | }; 262 | A35BAC1E1C1AC705004FFABC /* SwiftyTextTests */ = { 263 | isa = PBXNativeTarget; 264 | buildConfigurationList = A35BAC2C1C1AC705004FFABC /* Build configuration list for PBXNativeTarget "SwiftyTextTests" */; 265 | buildPhases = ( 266 | A35BAC1B1C1AC705004FFABC /* Sources */, 267 | A35BAC1C1C1AC705004FFABC /* Frameworks */, 268 | A35BAC1D1C1AC705004FFABC /* Resources */, 269 | ); 270 | buildRules = ( 271 | ); 272 | dependencies = ( 273 | A35BAC221C1AC705004FFABC /* PBXTargetDependency */, 274 | ); 275 | name = SwiftyTextTests; 276 | productName = SwiftyTextTests; 277 | productReference = A35BAC1F1C1AC705004FFABC /* SwiftyTextTests.xctest */; 278 | productType = "com.apple.product-type.bundle.unit-test"; 279 | }; 280 | A35BAC4A1C1AC76A004FFABC /* SwiftyTextDemo */ = { 281 | isa = PBXNativeTarget; 282 | buildConfigurationList = A35BAC5A1C1AC76A004FFABC /* Build configuration list for PBXNativeTarget "SwiftyTextDemo" */; 283 | buildPhases = ( 284 | A35BAC471C1AC76A004FFABC /* Sources */, 285 | A35BAC481C1AC76A004FFABC /* Frameworks */, 286 | A35BAC491C1AC76A004FFABC /* Resources */, 287 | A396DBA21C3B971C00155664 /* Embed Frameworks */, 288 | ); 289 | buildRules = ( 290 | ); 291 | dependencies = ( 292 | A396DBA11C3B971C00155664 /* PBXTargetDependency */, 293 | ); 294 | name = SwiftyTextDemo; 295 | productName = SwiftyTextDemo; 296 | productReference = A35BAC4B1C1AC76A004FFABC /* SwiftyTextDemo.app */; 297 | productType = "com.apple.product-type.application"; 298 | }; 299 | /* End PBXNativeTarget section */ 300 | 301 | /* Begin PBXProject section */ 302 | A35BAC0C1C1AC705004FFABC /* Project object */ = { 303 | isa = PBXProject; 304 | attributes = { 305 | LastUpgradeCheck = 0700; 306 | ORGANIZATIONNAME = geeklu.com; 307 | TargetAttributes = { 308 | A35BAC141C1AC705004FFABC = { 309 | CreatedOnToolsVersion = 7.0.1; 310 | }; 311 | A35BAC1E1C1AC705004FFABC = { 312 | CreatedOnToolsVersion = 7.0.1; 313 | }; 314 | A35BAC4A1C1AC76A004FFABC = { 315 | CreatedOnToolsVersion = 7.0.1; 316 | }; 317 | }; 318 | }; 319 | buildConfigurationList = A35BAC0F1C1AC705004FFABC /* Build configuration list for PBXProject "SwiftyText" */; 320 | compatibilityVersion = "Xcode 3.2"; 321 | developmentRegion = English; 322 | hasScannedForEncodings = 0; 323 | knownRegions = ( 324 | en, 325 | Base, 326 | ); 327 | mainGroup = A35BAC0B1C1AC705004FFABC; 328 | productRefGroup = A35BAC161C1AC705004FFABC /* Products */; 329 | projectDirPath = ""; 330 | projectRoot = ""; 331 | targets = ( 332 | A35BAC141C1AC705004FFABC /* SwiftyText */, 333 | A35BAC1E1C1AC705004FFABC /* SwiftyTextTests */, 334 | A35BAC4A1C1AC76A004FFABC /* SwiftyTextDemo */, 335 | ); 336 | }; 337 | /* End PBXProject section */ 338 | 339 | /* Begin PBXResourcesBuildPhase section */ 340 | A35BAC131C1AC705004FFABC /* Resources */ = { 341 | isa = PBXResourcesBuildPhase; 342 | buildActionMask = 2147483647; 343 | files = ( 344 | ); 345 | runOnlyForDeploymentPostprocessing = 0; 346 | }; 347 | A35BAC1D1C1AC705004FFABC /* Resources */ = { 348 | isa = PBXResourcesBuildPhase; 349 | buildActionMask = 2147483647; 350 | files = ( 351 | ); 352 | runOnlyForDeploymentPostprocessing = 0; 353 | }; 354 | A35BAC491C1AC76A004FFABC /* Resources */ = { 355 | isa = PBXResourcesBuildPhase; 356 | buildActionMask = 2147483647; 357 | files = ( 358 | A35BAC581C1AC76A004FFABC /* LaunchScreen.storyboard in Resources */, 359 | A35BAC551C1AC76A004FFABC /* Assets.xcassets in Resources */, 360 | A35BAC531C1AC76A004FFABC /* Main.storyboard in Resources */, 361 | ); 362 | runOnlyForDeploymentPostprocessing = 0; 363 | }; 364 | /* End PBXResourcesBuildPhase section */ 365 | 366 | /* Begin PBXSourcesBuildPhase section */ 367 | A35BAC101C1AC705004FFABC /* Sources */ = { 368 | isa = PBXSourcesBuildPhase; 369 | buildActionMask = 2147483647; 370 | files = ( 371 | A330DAB41C4DF9FF0016F396 /* SwiftyTextTouchRecognizer.swift in Sources */, 372 | A3BF75AB1C474A00007E1C89 /* NSAttributedString+SwiftyText.swift in Sources */, 373 | A3987DE91C22964600CF5202 /* SwiftyTextLink.swift in Sources */, 374 | A35BAC661C1AC808004FFABC /* SwiftyLabel.swift in Sources */, 375 | A35BAC691C1AC808004FFABC /* SwiftyTextDetector.swift in Sources */, 376 | A37B90F11C2AC72500F37FA1 /* SwiftyTextParser.swift in Sources */, 377 | A3BF75AC1C474A00007E1C89 /* NSLayoutManager+GlyphRects.swift in Sources */, 378 | A330DAB11C4DF9FF0016F396 /* SwiftyTextGestureRecognizer.swift in Sources */, 379 | A330DAB21C4DF9FF0016F396 /* SwiftyTextLongPressRecognizer.swift in Sources */, 380 | A330DAB31C4DF9FF0016F396 /* SwiftyTextTapRecognizer.swift in Sources */, 381 | A3BF75AE1C474A00007E1C89 /* UIBezierPath+Rects.swift in Sources */, 382 | A3BF75AD1C474A00007E1C89 /* NSMutableAttributedString+SwiftyText.swift in Sources */, 383 | A35BAC681C1AC808004FFABC /* SwiftyTextAttachment.swift in Sources */, 384 | ); 385 | runOnlyForDeploymentPostprocessing = 0; 386 | }; 387 | A35BAC1B1C1AC705004FFABC /* Sources */ = { 388 | isa = PBXSourcesBuildPhase; 389 | buildActionMask = 2147483647; 390 | files = ( 391 | A35BAC251C1AC705004FFABC /* SwiftyTextTests.swift in Sources */, 392 | ); 393 | runOnlyForDeploymentPostprocessing = 0; 394 | }; 395 | A35BAC471C1AC76A004FFABC /* Sources */ = { 396 | isa = PBXSourcesBuildPhase; 397 | buildActionMask = 2147483647; 398 | files = ( 399 | A35BAC501C1AC76A004FFABC /* BasicViewController.swift in Sources */, 400 | A35BAC4E1C1AC76A004FFABC /* AppDelegate.swift in Sources */, 401 | A396DBA61C3C015E00155664 /* SwiftyLabelCell.swift in Sources */, 402 | A396DBA41C3BE6C100155664 /* ListViewController.swift in Sources */, 403 | ); 404 | runOnlyForDeploymentPostprocessing = 0; 405 | }; 406 | /* End PBXSourcesBuildPhase section */ 407 | 408 | /* Begin PBXTargetDependency section */ 409 | A35BAC221C1AC705004FFABC /* PBXTargetDependency */ = { 410 | isa = PBXTargetDependency; 411 | target = A35BAC141C1AC705004FFABC /* SwiftyText */; 412 | targetProxy = A35BAC211C1AC705004FFABC /* PBXContainerItemProxy */; 413 | }; 414 | A396DBA11C3B971C00155664 /* PBXTargetDependency */ = { 415 | isa = PBXTargetDependency; 416 | target = A35BAC141C1AC705004FFABC /* SwiftyText */; 417 | targetProxy = A396DBA01C3B971C00155664 /* PBXContainerItemProxy */; 418 | }; 419 | /* End PBXTargetDependency section */ 420 | 421 | /* Begin PBXVariantGroup section */ 422 | A35BAC511C1AC76A004FFABC /* Main.storyboard */ = { 423 | isa = PBXVariantGroup; 424 | children = ( 425 | A35BAC521C1AC76A004FFABC /* Base */, 426 | ); 427 | name = Main.storyboard; 428 | sourceTree = ""; 429 | }; 430 | A35BAC561C1AC76A004FFABC /* LaunchScreen.storyboard */ = { 431 | isa = PBXVariantGroup; 432 | children = ( 433 | A35BAC571C1AC76A004FFABC /* Base */, 434 | ); 435 | name = LaunchScreen.storyboard; 436 | sourceTree = ""; 437 | }; 438 | /* End PBXVariantGroup section */ 439 | 440 | /* Begin XCBuildConfiguration section */ 441 | A35BAC271C1AC705004FFABC /* Debug */ = { 442 | isa = XCBuildConfiguration; 443 | buildSettings = { 444 | ALWAYS_SEARCH_USER_PATHS = NO; 445 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 446 | CLANG_CXX_LIBRARY = "libc++"; 447 | CLANG_ENABLE_MODULES = YES; 448 | CLANG_ENABLE_OBJC_ARC = YES; 449 | CLANG_WARN_BOOL_CONVERSION = YES; 450 | CLANG_WARN_CONSTANT_CONVERSION = YES; 451 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 452 | CLANG_WARN_EMPTY_BODY = YES; 453 | CLANG_WARN_ENUM_CONVERSION = YES; 454 | CLANG_WARN_INT_CONVERSION = YES; 455 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 456 | CLANG_WARN_UNREACHABLE_CODE = YES; 457 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 458 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 459 | COPY_PHASE_STRIP = NO; 460 | CURRENT_PROJECT_VERSION = 1; 461 | DEBUG_INFORMATION_FORMAT = dwarf; 462 | ENABLE_STRICT_OBJC_MSGSEND = YES; 463 | ENABLE_TESTABILITY = YES; 464 | GCC_C_LANGUAGE_STANDARD = gnu99; 465 | GCC_DYNAMIC_NO_PIC = NO; 466 | GCC_NO_COMMON_BLOCKS = YES; 467 | GCC_OPTIMIZATION_LEVEL = 0; 468 | GCC_PREPROCESSOR_DEFINITIONS = ( 469 | "DEBUG=1", 470 | "$(inherited)", 471 | ); 472 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 473 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 474 | GCC_WARN_UNDECLARED_SELECTOR = YES; 475 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 476 | GCC_WARN_UNUSED_FUNCTION = YES; 477 | GCC_WARN_UNUSED_VARIABLE = YES; 478 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 479 | MTL_ENABLE_DEBUG_INFO = YES; 480 | ONLY_ACTIVE_ARCH = YES; 481 | SDKROOT = iphoneos; 482 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 483 | TARGETED_DEVICE_FAMILY = "1,2"; 484 | VERSIONING_SYSTEM = "apple-generic"; 485 | VERSION_INFO_PREFIX = ""; 486 | }; 487 | name = Debug; 488 | }; 489 | A35BAC281C1AC705004FFABC /* Release */ = { 490 | isa = XCBuildConfiguration; 491 | buildSettings = { 492 | ALWAYS_SEARCH_USER_PATHS = NO; 493 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 494 | CLANG_CXX_LIBRARY = "libc++"; 495 | CLANG_ENABLE_MODULES = YES; 496 | CLANG_ENABLE_OBJC_ARC = YES; 497 | CLANG_WARN_BOOL_CONVERSION = YES; 498 | CLANG_WARN_CONSTANT_CONVERSION = YES; 499 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 500 | CLANG_WARN_EMPTY_BODY = YES; 501 | CLANG_WARN_ENUM_CONVERSION = YES; 502 | CLANG_WARN_INT_CONVERSION = YES; 503 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 504 | CLANG_WARN_UNREACHABLE_CODE = YES; 505 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 506 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 507 | COPY_PHASE_STRIP = NO; 508 | CURRENT_PROJECT_VERSION = 1; 509 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 510 | ENABLE_NS_ASSERTIONS = NO; 511 | ENABLE_STRICT_OBJC_MSGSEND = YES; 512 | GCC_C_LANGUAGE_STANDARD = gnu99; 513 | GCC_NO_COMMON_BLOCKS = YES; 514 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 515 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 516 | GCC_WARN_UNDECLARED_SELECTOR = YES; 517 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 518 | GCC_WARN_UNUSED_FUNCTION = YES; 519 | GCC_WARN_UNUSED_VARIABLE = YES; 520 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 521 | MTL_ENABLE_DEBUG_INFO = NO; 522 | SDKROOT = iphoneos; 523 | TARGETED_DEVICE_FAMILY = "1,2"; 524 | VALIDATE_PRODUCT = YES; 525 | VERSIONING_SYSTEM = "apple-generic"; 526 | VERSION_INFO_PREFIX = ""; 527 | }; 528 | name = Release; 529 | }; 530 | A35BAC2A1C1AC705004FFABC /* Debug */ = { 531 | isa = XCBuildConfiguration; 532 | buildSettings = { 533 | DEFINES_MODULE = YES; 534 | DYLIB_COMPATIBILITY_VERSION = 1; 535 | DYLIB_CURRENT_VERSION = 1; 536 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 537 | INFOPLIST_FILE = SwiftyText/Info.plist; 538 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 539 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 540 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 541 | PRODUCT_BUNDLE_IDENTIFIER = com.geeklu.SwiftyText; 542 | PRODUCT_NAME = "$(TARGET_NAME)"; 543 | SKIP_INSTALL = YES; 544 | }; 545 | name = Debug; 546 | }; 547 | A35BAC2B1C1AC705004FFABC /* Release */ = { 548 | isa = XCBuildConfiguration; 549 | buildSettings = { 550 | DEFINES_MODULE = YES; 551 | DYLIB_COMPATIBILITY_VERSION = 1; 552 | DYLIB_CURRENT_VERSION = 1; 553 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 554 | INFOPLIST_FILE = SwiftyText/Info.plist; 555 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 556 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 557 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 558 | PRODUCT_BUNDLE_IDENTIFIER = com.geeklu.SwiftyText; 559 | PRODUCT_NAME = "$(TARGET_NAME)"; 560 | SKIP_INSTALL = YES; 561 | }; 562 | name = Release; 563 | }; 564 | A35BAC2D1C1AC705004FFABC /* Debug */ = { 565 | isa = XCBuildConfiguration; 566 | buildSettings = { 567 | INFOPLIST_FILE = SwiftyTextTests/Info.plist; 568 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 569 | PRODUCT_BUNDLE_IDENTIFIER = com.geeklu.SwiftyTextTests; 570 | PRODUCT_NAME = "$(TARGET_NAME)"; 571 | }; 572 | name = Debug; 573 | }; 574 | A35BAC2E1C1AC705004FFABC /* Release */ = { 575 | isa = XCBuildConfiguration; 576 | buildSettings = { 577 | INFOPLIST_FILE = SwiftyTextTests/Info.plist; 578 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 579 | PRODUCT_BUNDLE_IDENTIFIER = com.geeklu.SwiftyTextTests; 580 | PRODUCT_NAME = "$(TARGET_NAME)"; 581 | }; 582 | name = Release; 583 | }; 584 | A35BAC5B1C1AC76A004FFABC /* Debug */ = { 585 | isa = XCBuildConfiguration; 586 | buildSettings = { 587 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 588 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 589 | INFOPLIST_FILE = SwiftyTextDemo/Info.plist; 590 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 591 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 592 | PRODUCT_BUNDLE_IDENTIFIER = com.geeklu.SwiftyTextDemo; 593 | PRODUCT_NAME = "$(TARGET_NAME)"; 594 | SWIFT_OBJC_BRIDGING_HEADER = "SwiftyTextDemo/SwiftyTextDemo-Bridging-Header.h"; 595 | }; 596 | name = Debug; 597 | }; 598 | A35BAC5C1C1AC76A004FFABC /* Release */ = { 599 | isa = XCBuildConfiguration; 600 | buildSettings = { 601 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 602 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; 603 | INFOPLIST_FILE = SwiftyTextDemo/Info.plist; 604 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 605 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 606 | PRODUCT_BUNDLE_IDENTIFIER = com.geeklu.SwiftyTextDemo; 607 | PRODUCT_NAME = "$(TARGET_NAME)"; 608 | SWIFT_OBJC_BRIDGING_HEADER = "SwiftyTextDemo/SwiftyTextDemo-Bridging-Header.h"; 609 | }; 610 | name = Release; 611 | }; 612 | /* End XCBuildConfiguration section */ 613 | 614 | /* Begin XCConfigurationList section */ 615 | A35BAC0F1C1AC705004FFABC /* Build configuration list for PBXProject "SwiftyText" */ = { 616 | isa = XCConfigurationList; 617 | buildConfigurations = ( 618 | A35BAC271C1AC705004FFABC /* Debug */, 619 | A35BAC281C1AC705004FFABC /* Release */, 620 | ); 621 | defaultConfigurationIsVisible = 0; 622 | defaultConfigurationName = Release; 623 | }; 624 | A35BAC291C1AC705004FFABC /* Build configuration list for PBXNativeTarget "SwiftyText" */ = { 625 | isa = XCConfigurationList; 626 | buildConfigurations = ( 627 | A35BAC2A1C1AC705004FFABC /* Debug */, 628 | A35BAC2B1C1AC705004FFABC /* Release */, 629 | ); 630 | defaultConfigurationIsVisible = 0; 631 | defaultConfigurationName = Release; 632 | }; 633 | A35BAC2C1C1AC705004FFABC /* Build configuration list for PBXNativeTarget "SwiftyTextTests" */ = { 634 | isa = XCConfigurationList; 635 | buildConfigurations = ( 636 | A35BAC2D1C1AC705004FFABC /* Debug */, 637 | A35BAC2E1C1AC705004FFABC /* Release */, 638 | ); 639 | defaultConfigurationIsVisible = 0; 640 | defaultConfigurationName = Release; 641 | }; 642 | A35BAC5A1C1AC76A004FFABC /* Build configuration list for PBXNativeTarget "SwiftyTextDemo" */ = { 643 | isa = XCConfigurationList; 644 | buildConfigurations = ( 645 | A35BAC5B1C1AC76A004FFABC /* Debug */, 646 | A35BAC5C1C1AC76A004FFABC /* Release */, 647 | ); 648 | defaultConfigurationIsVisible = 0; 649 | defaultConfigurationName = Release; 650 | }; 651 | /* End XCConfigurationList section */ 652 | }; 653 | rootObject = A35BAC0C1C1AC705004FFABC /* Project object */; 654 | } 655 | -------------------------------------------------------------------------------- /SwiftyText.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftyText.xcodeproj/xcshareddata/xcschemes/SwiftyText.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /SwiftyText/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /SwiftyText/SwiftyLabel/Extensions/NSAttributedString+SwiftyText.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributedString+SwiftyText.swift 3 | // SwiftyText 4 | // 5 | // Created by Luke on 1/14/16. 6 | // Copyright © 2016 geeklu.com. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension NSAttributedString { 12 | public func containsRange(range: NSRange) -> Bool { 13 | var contains = false 14 | let entireRange = self.entireRange() 15 | if range.location >= entireRange.location && NSMaxRange(range) <= NSMaxRange(entireRange) { 16 | contains = true 17 | } 18 | return contains 19 | } 20 | 21 | public func entireRange() -> NSRange { 22 | let range = NSMakeRange(0, self.length) 23 | return range 24 | } 25 | 26 | public func proposedSizeWithConstrainedSize(constrainedSize: CGSize, exclusionPaths: [UIBezierPath]?, lineBreakMode: NSLineBreakMode?, maximumNumberOfLines: Int?) -> CGSize { 27 | let textContainer = NSTextContainer(size: constrainedSize) 28 | textContainer.lineFragmentPadding = 0.0 29 | if exclusionPaths != nil { 30 | textContainer.exclusionPaths = exclusionPaths! 31 | } 32 | if lineBreakMode != nil { 33 | textContainer.lineBreakMode = lineBreakMode! 34 | } 35 | if maximumNumberOfLines != nil { 36 | textContainer.maximumNumberOfLines = maximumNumberOfLines! 37 | } 38 | 39 | let layoutManager = NSLayoutManager() 40 | layoutManager.addTextContainer(textContainer) 41 | 42 | let textStorage = NSTextStorage(attributedString: self) 43 | textStorage.addLayoutManager(layoutManager) 44 | 45 | layoutManager.glyphRangeForTextContainer(textContainer) 46 | var proposedSize = layoutManager.usedRectForTextContainer(textContainer).size 47 | 48 | proposedSize.width = ceil(proposedSize.width) 49 | proposedSize.height = ceil(proposedSize.height) 50 | return proposedSize 51 | } 52 | 53 | public func neighbourFontDescenderWithRange(range: NSRange) -> CGFloat { 54 | var fontDescender: CGFloat = 0.0; 55 | var neighbourAttributs: [String: AnyObject]? = nil; 56 | if range.location >= 1 { 57 | neighbourAttributs = self.attributesAtIndex(range.location - 1, effectiveRange: nil) 58 | } else if (NSMaxRange(range) < self.length) { 59 | neighbourAttributs = self.attributesAtIndex(NSMaxRange(range) , effectiveRange: nil) 60 | } 61 | 62 | if neighbourAttributs != nil { 63 | if let neighbourAttachment = neighbourAttributs![NSAttachmentAttributeName] as? SwiftyTextAttachment { 64 | fontDescender = neighbourAttachment.fontDescender; 65 | } else if neighbourAttributs![NSFontAttributeName] != nil { 66 | if let neighbourFont = neighbourAttributs![NSFontAttributeName] as? UIFont{ 67 | fontDescender = neighbourFont.descender; 68 | } 69 | } 70 | } 71 | return fontDescender; 72 | } 73 | 74 | internal func viewAttachments() -> [SwiftyTextAttachment] { 75 | var attachments = [SwiftyTextAttachment]() 76 | self.enumerateAttribute(NSAttachmentAttributeName, inRange: self.entireRange(), options:[]) { (value: AnyObject?, range: NSRange, stop: UnsafeMutablePointer) -> Void in 77 | if value != nil && value is SwiftyTextAttachment { 78 | let attachment = value as! SwiftyTextAttachment 79 | if attachment.contentView != nil { 80 | attachments.append(attachment) 81 | } 82 | } 83 | } 84 | return attachments 85 | } 86 | 87 | internal func attributesRangeMapInRange(textRange: NSRange) -> [String: [String: AnyObject]]? { 88 | 89 | var attributesRangeMap:[String: [String: AnyObject]]? = [String: [String: AnyObject]]() 90 | self.enumerateAttributesInRange(textRange, options: NSAttributedStringEnumerationOptions()) { (attrs, range, stop) -> Void in 91 | let rangeString = NSStringFromRange(range) 92 | attributesRangeMap![rangeString] = attrs 93 | } 94 | return attributesRangeMap 95 | } 96 | } -------------------------------------------------------------------------------- /SwiftyText/SwiftyLabel/Extensions/NSLayoutManager+GlyphRects.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSLayoutExtensions.swift 3 | // SwiftyText 4 | // 5 | // Created by Luke on 12/3/15. 6 | // Copyright © 2015 geeklu.com. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension NSLayoutManager { 12 | func glyphRectsWithCharacterRange(range: NSRange, containerInset inset:UIEdgeInsets) -> [CGRect]? { 13 | 14 | let glyphRangeForCharacters = self.glyphRangeForCharacterRange(range, actualCharacterRange: nil) 15 | var glyphRects:[CGRect] = [] 16 | 17 | self.enumerateLineFragmentsForGlyphRange(glyphRangeForCharacters, usingBlock: { (lineRect: CGRect, usedRect: CGRect, textContainer: NSTextContainer, glyphRange: NSRange, stop: UnsafeMutablePointer) -> Void in 18 | 19 | let currentLineGlyphRange = NSIntersectionRange(glyphRangeForCharacters, glyphRange) 20 | var glyphRectInContainerView = self.boundingRectForGlyphRange(currentLineGlyphRange, inTextContainer: textContainer) 21 | glyphRectInContainerView.origin.x += inset.left 22 | glyphRectInContainerView.origin.y += inset.top 23 | 24 | glyphRectInContainerView = CGRectInset(glyphRectInContainerView, -2, 0) 25 | glyphRects.append(glyphRectInContainerView) 26 | }) 27 | 28 | if glyphRects.count > 0 { 29 | return glyphRects 30 | } 31 | return nil 32 | } 33 | } -------------------------------------------------------------------------------- /SwiftyText/SwiftyLabel/Extensions/NSMutableAttributedString+SwiftyText.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableAttributedString+SwiftyText.swift 3 | // SwiftyText 4 | // 5 | // Created by Luke on 1/14/16. 6 | // Copyright © 2016 geeklu.com. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension NSMutableAttributedString { 12 | 13 | public var font: UIFont? { 14 | get { 15 | var effectiveRange = NSMakeRange(NSNotFound, 0) 16 | let attribute = self.attribute(NSFontAttributeName, atIndex: 0, longestEffectiveRange: &effectiveRange, inRange: self.entireRange()) 17 | if NSEqualRanges(self.entireRange(), effectiveRange) { 18 | let fontAttribute = attribute as? UIFont 19 | return fontAttribute 20 | } else { 21 | return nil 22 | } 23 | 24 | } 25 | set { 26 | self.setFont(newValue, range: self.entireRange()) 27 | } 28 | } 29 | 30 | public var foregroundColor: UIColor? { 31 | get { 32 | var effectiveRange = NSMakeRange(NSNotFound, 0) 33 | let attribute = self.attribute(NSForegroundColorAttributeName, atIndex: 0, longestEffectiveRange: &effectiveRange, inRange: self.entireRange()) 34 | if NSEqualRanges(self.entireRange(), effectiveRange) { 35 | let foregroundColorAttribute = attribute as? UIColor 36 | return foregroundColorAttribute 37 | } else { 38 | return nil 39 | } 40 | } 41 | set { 42 | self.setForegroundColor(newValue, range: self.entireRange()) 43 | } 44 | } 45 | 46 | public func setFont(font: UIFont?, range:NSRange) { 47 | if self.containsRange(range) { 48 | if font != nil { 49 | self.addAttribute(NSFontAttributeName, value: font!, range: range) 50 | } else { 51 | self.removeAttribute(NSFontAttributeName, range: range) 52 | } 53 | } 54 | } 55 | 56 | public func setForegroundColor(foregroundColor: UIColor?, range:NSRange) { 57 | if self.containsRange(range) { 58 | if foregroundColor != nil { 59 | self.addAttribute(NSForegroundColorAttributeName, value: foregroundColor!, range: range) 60 | } else { 61 | self.removeAttribute(NSForegroundColorAttributeName, range: range) 62 | } 63 | } 64 | } 65 | 66 | public func setLink(link: SwiftyTextLink?, range:NSRange) { 67 | if self.containsRange(range) { 68 | if link != nil { 69 | self.addAttribute(SwiftyTextLinkAttributeName, value: link!, range: range) 70 | if link!.attributes != nil { 71 | self.addAttributes(link!.attributes!, range: range) 72 | } 73 | } else { 74 | self.removeAttribute(SwiftyTextLinkAttributeName, range: range) 75 | } 76 | } 77 | } 78 | 79 | public func insertAttachment(attachment:SwiftyTextAttachment, atIndex loc:Int) { 80 | if loc <= self.length { 81 | attachment.fontDescender = self.neighbourFontDescenderWithRange(NSMakeRange(loc, 0)) 82 | var attachmentAttributedString = NSAttributedString(attachment: attachment) 83 | //Use blank attachment for real image attachment padding 84 | if attachment.image != nil && 85 | attachment.contentView == nil && 86 | attachment.padding > 0 { 87 | let paddingAttachment = SwiftyTextBlankAttachment() 88 | paddingAttachment.width = attachment.padding 89 | let paddingAttributedString = NSAttributedString(attachment: paddingAttachment) 90 | 91 | let mutableAttributedString = NSMutableAttributedString() 92 | mutableAttributedString.appendAttributedString(paddingAttributedString) 93 | mutableAttributedString.appendAttributedString(attachmentAttributedString) 94 | mutableAttributedString.appendAttributedString(paddingAttributedString) 95 | attachmentAttributedString = mutableAttributedString.copy() as! NSAttributedString 96 | } 97 | self.insertAttributedString(attachmentAttributedString, atIndex: loc) 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /SwiftyText/SwiftyLabel/Extensions/UIBezierPath+Rects.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIBezierPathExtensions.swift 3 | // SwiftyText 4 | // 5 | // Created by Luke on 12/3/15. 6 | // Copyright © 2015 geeklu.com. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension UIBezierPath { 12 | class func bezierPathWithGlyphRects(rects: [CGRect], radius: CGFloat) -> UIBezierPath { 13 | let path = UIBezierPath() 14 | 15 | let rectCount = rects.count 16 | 17 | for var i = 0; i < rectCount; i++ { 18 | var previousRectValue:CGRect? = nil 19 | var nextRectValue:CGRect? = nil 20 | if i > 0 { 21 | previousRectValue = rects[i-1] 22 | } 23 | if i < rectCount - 1 { 24 | nextRectValue = rects[i+1] 25 | } 26 | 27 | var rectCorners = UIRectCorner() 28 | 29 | let currentRect = rects[i] 30 | 31 | let currentRectMinX = CGRectGetMinX(currentRect) 32 | let currentRectMaxX = CGRectGetMaxX(currentRect) 33 | 34 | if previousRectValue != nil { 35 | let previousRect = previousRectValue! 36 | let previousRectMinX = CGRectGetMinX(previousRect) 37 | let previousRectMaxX = CGRectGetMaxX(previousRect) 38 | 39 | if currentRectMinX < previousRectMinX || 40 | currentRectMinX > previousRectMaxX { 41 | rectCorners = rectCorners.union(.TopLeft) 42 | } 43 | if currentRectMaxX < previousRectMinX || 44 | currentRectMaxX > previousRectMaxX { 45 | rectCorners = rectCorners.union(.TopRight) 46 | } 47 | } else { 48 | rectCorners = rectCorners.union([.TopLeft, .TopRight]) 49 | } 50 | 51 | if nextRectValue != nil { 52 | let nextRect = nextRectValue! 53 | let nextRectMinX = CGRectGetMinX(nextRect) 54 | let nextRectMaxX = CGRectGetMaxX(nextRect) 55 | 56 | if currentRectMinX < nextRectMinX || 57 | currentRectMinX > nextRectMaxX{ 58 | rectCorners = rectCorners.union(.BottomLeft) 59 | } 60 | if currentRectMaxX < nextRectMinX || 61 | currentRectMaxX > nextRectMaxX { 62 | rectCorners = rectCorners.union(.BottomRight) 63 | } 64 | } else { 65 | rectCorners = rectCorners.union([.BottomLeft, .BottomRight]) 66 | } 67 | 68 | let currentRectPath = UIBezierPath(roundedRect: currentRect, byRoundingCorners: rectCorners, cornerRadii: CGSizeMake(radius, radius)) 69 | path.appendPath(currentRectPath) 70 | } 71 | return path 72 | } 73 | } -------------------------------------------------------------------------------- /SwiftyText/SwiftyLabel/GestureRecognizers/SwiftyTextGestureRecognizer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftyTextGestureRecognizer.swift 3 | // SwiftyText 4 | // 5 | // Created by Luke on 1/19/16. 6 | // Copyright © 2016 geeklu.com. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class SwiftyTextGestureRecognizer: UIGestureRecognizer { 13 | var link: SwiftyTextLink? 14 | var linkRange: NSRange? 15 | var linkGlyphRects: [CGRect]? 16 | } 17 | 18 | -------------------------------------------------------------------------------- /SwiftyText/SwiftyLabel/GestureRecognizers/SwiftyTextLongPressRecognizer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftyLabelLongPressRecognizer.swift 3 | // SwiftyText 4 | // 5 | // Created by Luke on 12/28/15. 6 | // Copyright © 2015 geeklu.com. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit.UIGestureRecognizerSubclass 11 | 12 | class SwiftyTextLongPressRecognizer: SwiftyTextGestureRecognizer { 13 | var minimumPressDuration: CFTimeInterval = 0.7 14 | var allowableMovement: CGFloat = 10.0 15 | var initialPoint: CGPoint = CGPointZero 16 | 17 | var longPressTimer: NSTimer? 18 | 19 | func isTouchCloseToInitialPoint(touch: UITouch) -> Bool { 20 | let point = touch.locationInView(self.view) 21 | let xDistance = self.initialPoint.x - point.x 22 | let yDistance = self.initialPoint.y - point.y 23 | 24 | let squaredDistance = xDistance * xDistance + yDistance * yDistance 25 | let isClose = squaredDistance <= self.allowableMovement * self.allowableMovement 26 | return isClose 27 | } 28 | 29 | func longPressed(timer: NSTimer) { 30 | timer.invalidate() 31 | self.state = .Ended 32 | } 33 | 34 | override func reset(){ 35 | super.reset() 36 | 37 | self.initialPoint = CGPointZero 38 | self.longPressTimer?.invalidate() 39 | self.longPressTimer = nil 40 | } 41 | 42 | override func touchesBegan(touches: Set, withEvent event: UIEvent) { 43 | super.touchesBegan(touches, withEvent: event) 44 | 45 | guard touches.count == 1 else { 46 | self.state = .Failed 47 | return 48 | } 49 | 50 | if let touch = touches.first { 51 | self.state = .Began 52 | self.initialPoint = touch.locationInView(self.view) 53 | self.longPressTimer = NSTimer.scheduledTimerWithTimeInterval(self.minimumPressDuration, target: self, selector:"longPressed:", userInfo: nil, repeats: false) 54 | 55 | } 56 | } 57 | 58 | override func touchesMoved(touches: Set, withEvent event: UIEvent) { 59 | if let touch = touches.first{ 60 | if !self.isTouchCloseToInitialPoint(touch){ 61 | self.longPressTimer?.invalidate() 62 | self.longPressTimer = nil 63 | self.state = .Failed 64 | } 65 | } 66 | super.touchesMoved(touches, withEvent: event) 67 | } 68 | 69 | override func touchesEnded(touches: Set, withEvent event: UIEvent) { 70 | super.touchesEnded(touches, withEvent: event) 71 | self.state = .Failed 72 | } 73 | 74 | override func touchesCancelled(touches: Set, withEvent event: UIEvent) { 75 | super.touchesCancelled(touches, withEvent: event) 76 | self.state = .Cancelled 77 | } 78 | } -------------------------------------------------------------------------------- /SwiftyText/SwiftyLabel/GestureRecognizers/SwiftyTextTapRecognizer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftyLabelTapRecognizer.swift 3 | // SwiftyText 4 | // 5 | // Created by Luke on 12/28/15. 6 | // Copyright © 2015 geeklu.com. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit.UIGestureRecognizerSubclass 11 | 12 | class SwiftyTextTapRecognizer: SwiftyTextGestureRecognizer { 13 | var numberOfTapsRequired: Int = 1 14 | var timeoutTimer: NSTimer? 15 | 16 | override func reset() { 17 | timeoutTimer?.invalidate() 18 | timeoutTimer = nil 19 | } 20 | 21 | func handleTimeout(timer: NSTimer) { 22 | timer.invalidate() 23 | self.state = .Cancelled 24 | } 25 | 26 | override func touchesBegan(touches: Set, withEvent event: UIEvent) { 27 | super.touchesBegan(touches, withEvent: event) 28 | guard touches.count == 1 else { 29 | self.state = .Failed 30 | return 31 | } 32 | self.state = .Began 33 | } 34 | 35 | override func touchesMoved(touches: Set, withEvent event: UIEvent) { 36 | super.touchesMoved(touches, withEvent: event) 37 | self.state = .Changed 38 | } 39 | 40 | override func touchesEnded(touches: Set, withEvent event: UIEvent) { 41 | super.touchesEnded(touches, withEvent: event) 42 | 43 | if let touch = touches.first { 44 | if self.numberOfTapsRequired == 1 { 45 | if touch.tapCount == 1 || touch.tapCount == 0 { 46 | self.state = .Ended 47 | } 48 | } else { 49 | if touch.tapCount < numberOfTapsRequired { 50 | if timeoutTimer != nil { 51 | timeoutTimer?.invalidate() 52 | } 53 | timeoutTimer = NSTimer.scheduledTimerWithTimeInterval(0.27, target: self, selector: "handleTimeout:", userInfo: nil, repeats: false) 54 | }else if touch.tapCount == numberOfTapsRequired { 55 | if timeoutTimer != nil { 56 | timeoutTimer?.invalidate() 57 | } 58 | self.state = .Ended 59 | } 60 | } 61 | } 62 | } 63 | 64 | override func touchesCancelled(touches: Set, withEvent event: UIEvent) { 65 | super.touchesCancelled(touches, withEvent: event) 66 | self.state = .Cancelled 67 | } 68 | } -------------------------------------------------------------------------------- /SwiftyText/SwiftyLabel/GestureRecognizers/SwiftyTextTouchRecognizer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftyTextTouchRecognizer.swift 3 | // SwiftyText 4 | // 5 | // Created by Luke on 1/15/16. 6 | // Copyright © 2016 geeklu.com. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit.UIGestureRecognizerSubclass 11 | 12 | class SwiftyTextTouchRecognizer: UIGestureRecognizer { 13 | override func touchesBegan(touches: Set, withEvent event: UIEvent) { 14 | self.state = .Began 15 | } 16 | 17 | override func touchesMoved(touches: Set, withEvent event: UIEvent) { 18 | self.state = .Changed 19 | } 20 | 21 | override func touchesEnded(touches: Set, withEvent event: UIEvent) { 22 | self.state = .Ended 23 | } 24 | 25 | override func touchesCancelled(touches: Set, withEvent event: UIEvent) { 26 | self.state = .Cancelled 27 | } 28 | } -------------------------------------------------------------------------------- /SwiftyText/SwiftyLabel/SwiftyLabel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftyLabel.swift 3 | // SwiftyLabel 4 | // 5 | // Created by Luke on 12/1/15. 6 | // Copyright © 2015 geeklu.com. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | public class SwiftyLabel: UIView, NSLayoutManagerDelegate, UIGestureRecognizerDelegate { 13 | 14 | // MARK:- Properties 15 | 16 | public var font: UIFont? { 17 | didSet { 18 | dispatch_sync(self.textQueue) { () -> Void in 19 | if self.text != nil { 20 | if self.font != nil { 21 | self.textStorage.addAttribute(NSFontAttributeName, value: self.font!, range: self.textStorage.entireRange()) 22 | } else { 23 | //if set as nil, use default 24 | self.textStorage.addAttribute(NSFontAttributeName, value: UIFont.systemFontOfSize(17), range: self.textStorage.entireRange()) 25 | } 26 | 27 | self.setNeedsDisplay() 28 | } 29 | } 30 | } 31 | } 32 | 33 | public var textColor: UIColor? { 34 | didSet { 35 | dispatch_sync(self.textQueue) { () -> Void in 36 | if self.text != nil { 37 | if self.textColor != nil { 38 | self.textStorage.addAttribute(NSForegroundColorAttributeName, value: self.textColor!, range: self.textStorage.entireRange()) 39 | } else { 40 | self.textStorage.addAttribute(NSForegroundColorAttributeName, value: UIColor.blackColor(), range: self.textStorage.entireRange()) 41 | } 42 | } 43 | self.setNeedsDisplay() 44 | } 45 | } 46 | } 47 | 48 | public var textAlignment: NSTextAlignment = .Left{ 49 | didSet { 50 | dispatch_sync(self.textQueue) { () -> Void in 51 | self.updateTextParaphStyleWithPropertyName("alignment", value: NSNumber(integer: self.textAlignment.rawValue)) 52 | } 53 | } 54 | } 55 | 56 | public var lineSpacing: CGFloat = 0.0 { 57 | didSet { 58 | dispatch_sync(self.textQueue) { () -> Void in 59 | self.updateTextParaphStyleWithPropertyName("lineSpacing", value: NSNumber(float: Float(self.lineSpacing))) 60 | } 61 | } 62 | } 63 | 64 | /** Default value: NSLineBreakMode.ByWordWrapping, The line break mode defines the behavior of the last line of the label. 65 | */ 66 | public var lineBreakMode: NSLineBreakMode { 67 | get { 68 | return self.textContainer.lineBreakMode 69 | } 70 | 71 | set { 72 | dispatch_sync(self.textQueue) { () -> Void in 73 | self.textContainer.lineBreakMode = newValue 74 | self.layoutManager.textContainerChangedGeometry(self.textContainer) 75 | self.setNeedsDisplay() 76 | } 77 | } 78 | } 79 | 80 | public var firstLineHeadIndent: CGFloat = 0.0 { 81 | didSet { 82 | dispatch_sync(self.textQueue) { () -> Void in 83 | self.updateTextParaphStyleWithPropertyName("firstLineHeadIndent", value: NSNumber(float: Float(self.firstLineHeadIndent))) 84 | } 85 | } 86 | } 87 | 88 | internal var defaultTextAttributes: [String: AnyObject]? { 89 | let attributes = NSMutableDictionary() 90 | attributes[NSFontAttributeName] = self.font ?? UIFont.systemFontOfSize(18) 91 | attributes[NSForegroundColorAttributeName] = self.textColor ?? UIColor.blackColor() 92 | let paragraphStyle = NSMutableParagraphStyle() 93 | if self.textAlignment != paragraphStyle.alignment { 94 | paragraphStyle.alignment = self.textAlignment 95 | } 96 | if self.lineSpacing != paragraphStyle.lineSpacing { 97 | paragraphStyle.lineSpacing = self.lineSpacing 98 | } 99 | if self.firstLineHeadIndent != paragraphStyle.firstLineHeadIndent { 100 | paragraphStyle.firstLineHeadIndent = self.firstLineHeadIndent 101 | } 102 | attributes[NSParagraphStyleAttributeName] = paragraphStyle.copy() 103 | if attributes.count > 0 { 104 | return attributes.copy() as? [String : AnyObject] 105 | } else { 106 | return nil; 107 | } 108 | } 109 | 110 | /** Hold the content for the text or attributedText property 111 | */ 112 | internal var content: AnyObject? 113 | 114 | public var text: String? { 115 | get { 116 | if self.content != nil { 117 | return self.textStorage.string 118 | } else { 119 | return nil 120 | } 121 | } 122 | 123 | set { 124 | dispatch_sync(self.textQueue) { () -> Void in 125 | let range = self.textStorage.entireRange() 126 | self.content = newValue 127 | if newValue != nil { 128 | self.textStorage.replaceCharactersInRange(range, withString: newValue!) 129 | let defaultAttributes = self.defaultTextAttributes 130 | if defaultAttributes != nil { 131 | self.textStorage.addAttributes(defaultAttributes!, range: self.textStorage.entireRange()) 132 | } 133 | 134 | self.needsParse = true 135 | } else { 136 | self.textStorage.replaceCharactersInRange(range, withString: "") 137 | } 138 | 139 | self.setNeedsDisplay() 140 | } 141 | } 142 | } 143 | 144 | /**the underlying attributed string drawn by the label, 145 | if set, the label ignores the properties above. 146 | In addition, assigning a new a value updates the values in the font, textColor, and other style-related properties so that they reflect the style information starting at location 0 in the attributed string. 147 | */ 148 | public var attributedText: NSAttributedString? { 149 | get { 150 | if self.content != nil { 151 | let range = self.textStorage.entireRange() 152 | return self.textStorage.attributedSubstringFromRange(range) 153 | } else { 154 | return nil 155 | } 156 | } 157 | 158 | set { 159 | dispatch_sync(self.textQueue) { () -> Void in 160 | self.content = newValue 161 | let range = self.textStorage.entireRange() 162 | if newValue != nil { 163 | self.textStorage.replaceCharactersInRange(range, withAttributedString: newValue!) 164 | self.needsParse = true 165 | } else { 166 | self.textStorage.replaceCharactersInRange(range, withString: "") 167 | } 168 | 169 | self.setNeedsDisplay() 170 | } 171 | } 172 | } 173 | 174 | public var numberOfLines: Int { 175 | get { 176 | return self.textContainer.maximumNumberOfLines 177 | } 178 | 179 | set { 180 | dispatch_sync(self.textQueue) { () -> Void in 181 | self.textContainer.maximumNumberOfLines = newValue 182 | self.layoutManager.textContainerChangedGeometry(self.textContainer) 183 | self.setNeedsDisplay() 184 | } 185 | } 186 | } 187 | 188 | internal var textContainer = NSTextContainer() 189 | public var textContainerInset = UIEdgeInsetsZero{ 190 | didSet { 191 | dispatch_sync(self.textQueue) { () -> Void in 192 | self.textContainer.size = UIEdgeInsetsInsetRect(self.bounds, self.textContainerInset).size 193 | self.layoutManager.textContainerChangedGeometry(self.textContainer) 194 | self.setNeedsDisplay() 195 | } 196 | } 197 | } 198 | 199 | /** The exclusionPaths for internal textContainer, so the exclusionPaths should be relatived to the text container's origin 200 | */ 201 | public var exclusionPaths: [UIBezierPath] { 202 | get { 203 | return self.textContainer.exclusionPaths 204 | } 205 | 206 | set { 207 | dispatch_sync(self.textQueue) { () -> Void in 208 | self.textContainer.exclusionPaths = self.exclusionPaths 209 | self.setNeedsDisplay() 210 | } 211 | } 212 | } 213 | 214 | internal var layoutManager = NSLayoutManager() 215 | internal var textStorage = NSTextStorage() 216 | 217 | public var parser: SwiftyTextParser? { 218 | didSet { 219 | self.needsParse = true 220 | self.setNeedsDisplay() 221 | } 222 | } 223 | internal var needsParse: Bool = false 224 | 225 | internal var asyncTextLayer: CALayer? 226 | internal var textQueue = dispatch_queue_create("com.geeklu.swiftylabel-text", DISPATCH_QUEUE_SERIAL) 227 | public var drawsTextAsynchronously: Bool = false { 228 | didSet { 229 | if drawsTextAsynchronously { 230 | if self.asyncTextLayer == nil { 231 | self.asyncTextLayer = CALayer() 232 | self.asyncTextLayer?.frame = self.layer.bounds 233 | } 234 | self.layer.addSublayer(self.asyncTextLayer!) 235 | } else { 236 | if self.asyncTextLayer != nil { 237 | self.asyncTextLayer!.removeFromSuperlayer() 238 | self.asyncTextLayer = nil 239 | } 240 | } 241 | self.setNeedsDisplay() 242 | } 243 | } 244 | 245 | /** 246 | When the touch is enabled on SwiftyLabel itself, you can set the highlightLayerColor for the label when touch. 247 | 248 | - important: highlightLayerColor should apply the alpha component. 249 | */ 250 | public var highlightLayerColor: UIColor?; 251 | 252 | internal var touchHighlightLayer: CALayer? 253 | internal var touchInfo: SwiftyLabelTouchInfo? 254 | 255 | //[rangeString: [attributeName: attribute]] 256 | internal var touchAttributesMap: [String: [String: AnyObject]]? 257 | 258 | internal var singleTapRecognizer = SwiftyTextTapRecognizer() 259 | internal var longPressRecognizer = SwiftyTextLongPressRecognizer() 260 | internal var touchRecognizer = SwiftyTextTouchRecognizer() 261 | 262 | weak public var delegate: SwiftyLabelDelegate? 263 | 264 | // MARK:- Init 265 | 266 | func commonInit() { 267 | self.textContainer.size = frame.size 268 | self.textContainer.lineFragmentPadding = 0.0 269 | self.layoutManager.addTextContainer(self.textContainer) 270 | self.textStorage.addLayoutManager(self.layoutManager) 271 | 272 | self.contentMode = .Redraw 273 | self.layoutManager.delegate = self 274 | 275 | self.singleTapRecognizer.numberOfTapsRequired = 1 276 | self.singleTapRecognizer.delegate = self 277 | self.singleTapRecognizer.addTarget(self, action: "handleSingleTap:") 278 | self.addGestureRecognizer(self.singleTapRecognizer) 279 | 280 | self.longPressRecognizer.delegate = self 281 | self.longPressRecognizer.addTarget(self, action: "handleLongPress:") 282 | self.addGestureRecognizer(self.longPressRecognizer) 283 | 284 | self.touchRecognizer.delegate = self 285 | self.touchRecognizer.addTarget(self, action: "handleTouch:") 286 | self.addGestureRecognizer(self.touchRecognizer) 287 | 288 | self.singleTapRecognizer.requireGestureRecognizerToFail(self.longPressRecognizer) 289 | 290 | NSNotificationCenter.defaultCenter().addObserver(self, selector: "voiceOverStatusChanged", name: UIAccessibilityVoiceOverStatusChanged, object: nil) 291 | } 292 | 293 | override public init(frame: CGRect) { 294 | super.init(frame: frame) 295 | self.commonInit() 296 | self.backgroundColor = UIColor.whiteColor() 297 | } 298 | 299 | required public init?(coder aDecoder: NSCoder) { 300 | super.init(coder: aDecoder) 301 | self.commonInit() 302 | } 303 | 304 | deinit { 305 | NSNotificationCenter.defaultCenter().removeObserver(self, name: UIAccessibilityVoiceOverStatusChanged, object: nil) 306 | } 307 | 308 | // MARK:- storage 309 | 310 | public func setLink(link: SwiftyTextLink?, range: NSRange) { 311 | dispatch_sync(self.textQueue) { () -> Void in 312 | self.textStorage.setLink(link, range: range) 313 | } 314 | } 315 | 316 | public func insertAttachment(attachment: SwiftyTextAttachment, atIndex loc: Int) { 317 | dispatch_sync(self.textQueue) { () -> Void in 318 | self.textStorage.insertAttachment(attachment, atIndex: loc) 319 | } 320 | } 321 | /** 322 | Returns the link attribute at a given position, and by reference the range and the glyph rects of the link. 323 | 324 | - parameter location: is relative to SwiftyLabel's bounds origin 325 | - parameter range: If non-NULL, upon return contains the range of the link returened 326 | - parameter rects: If non-NULL, upon return contains all the glyph rects of the link 327 | - returns: The link at location if existed, otherwise nil 328 | */ 329 | public func linkAtLocation(location: CGPoint, effectiveRange range: NSRangePointer, effectiveGlyphRects rects: UnsafeMutablePointer<[CGRect]>) -> SwiftyTextLink? { 330 | 331 | guard self.textStorage.length > 0 else { 332 | return nil 333 | } 334 | 335 | var locationInTextContainer = location 336 | locationInTextContainer.x -= self.textContainerInset.left 337 | locationInTextContainer.y -= self.textContainerInset.top 338 | 339 | let touchedIndex = self.layoutManager.characterIndexForPoint(locationInTextContainer, inTextContainer: self.textContainer, fractionOfDistanceBetweenInsertionPoints: nil) 340 | let link:SwiftyTextLink? = self.textStorage.attribute(SwiftyTextLinkAttributeName, atIndex: touchedIndex, longestEffectiveRange: range, inRange: NSMakeRange(0, self.textStorage.length)) as? SwiftyTextLink 341 | 342 | if link != nil { 343 | let glyphRects = self.layoutManager.glyphRectsWithCharacterRange(range.memory, containerInset: self.textContainerInset) 344 | if glyphRects != nil { 345 | if location.isInRects(glyphRects!) { 346 | rects.memory = glyphRects! 347 | return link 348 | } 349 | } 350 | } 351 | return nil; 352 | } 353 | 354 | // MARK:- Touch Info reset 355 | 356 | func resetTouchInfo(){ 357 | self.touchHighlightLayer?.removeFromSuperlayer() 358 | self.touchHighlightLayer = nil 359 | 360 | if self.touchInfo?.link?.highlightedAttributes != nil { 361 | self.setHighlight(false, withRange: self.touchInfo!.linkRange!, textLink: self.touchInfo!.link!) 362 | } 363 | 364 | self.touchAttributesMap = nil 365 | self.touchInfo = nil 366 | } 367 | 368 | // MARK:- GestureRecognizer Delegate 369 | public override func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool { 370 | if gestureRecognizer == self.singleTapRecognizer || gestureRecognizer == self.longPressRecognizer || gestureRecognizer == self.touchRecognizer { 371 | let link = self.touchInfo?.link 372 | if link != nil { 373 | if (link!.gestures.contains(.Tap) && gestureRecognizer == self.singleTapRecognizer) || (link!.gestures.contains(.LongPress) && gestureRecognizer == self.longPressRecognizer) || (link!.gestures != .None && gestureRecognizer == self.touchRecognizer) { 374 | return true 375 | } 376 | } 377 | return false 378 | } else { 379 | return true 380 | } 381 | } 382 | 383 | public func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool { 384 | if touch.view == self { 385 | var should = false 386 | if self.touchInfo?.link != nil && self.touchInfo?.touch != nil && self.touchInfo?.touch! == touch { 387 | should = true 388 | } else { 389 | self.touchInfo = SwiftyLabelTouchInfo(aTouch: touch) 390 | if self.touchInfo?.link != nil { 391 | should = true 392 | } 393 | } 394 | 395 | if should && gestureRecognizer is SwiftyTextGestureRecognizer { 396 | let textGestureRecognizer = gestureRecognizer as! SwiftyTextGestureRecognizer 397 | textGestureRecognizer.link = self.touchInfo!.link 398 | textGestureRecognizer.linkRange = self.touchInfo!.linkRange 399 | textGestureRecognizer.linkGlyphRects = self.touchInfo?.linkGlyphRects 400 | } 401 | 402 | return should 403 | } else { 404 | return false 405 | } 406 | } 407 | 408 | public func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool { 409 | return true 410 | } 411 | 412 | // MARK:- GestureRecognizer Actions 413 | internal func handleSingleTap(gestureRecognizer: SwiftyTextTapRecognizer) { 414 | if gestureRecognizer.state == .Ended { 415 | if gestureRecognizer.link != nil && gestureRecognizer.linkRange != nil && gestureRecognizer.linkGlyphRects != nil{ 416 | let location = gestureRecognizer.locationInView(self) 417 | if location.isInRects(gestureRecognizer.linkGlyphRects!) { 418 | self.delegate?.swiftyLabel(self, didTapWithTextLink: gestureRecognizer.link!, range: gestureRecognizer.linkRange!) 419 | } 420 | } 421 | } 422 | } 423 | 424 | internal func handleLongPress(gestureRecognizer: SwiftyTextLongPressRecognizer) { 425 | if gestureRecognizer.state == .Ended { 426 | 427 | if gestureRecognizer.link != nil && gestureRecognizer.linkRange != nil && gestureRecognizer.linkGlyphRects != nil{ 428 | let location = gestureRecognizer.locationInView(self) 429 | if location.isInRects(gestureRecognizer.linkGlyphRects!) { 430 | self.delegate?.swiftyLabel(self, didLongPressWithTextLink: gestureRecognizer.link!, range: gestureRecognizer.linkRange!) 431 | } 432 | } 433 | } 434 | } 435 | 436 | internal func handleTouch(gestureRecognizer: SwiftyTextTouchRecognizer) { 437 | if gestureRecognizer.state == .Began { 438 | if let link = self.touchInfo?.link { 439 | 440 | let shapeLayer = CAShapeLayer() 441 | shapeLayer.path = UIBezierPath.bezierPathWithGlyphRects(self.touchInfo!.linkGlyphRects!, radius: (link.highlightLayerRadius ?? 3.0)).CGPath 442 | shapeLayer.fillColor = link.highlightLayerColor?.CGColor ?? UIColor.grayColor().colorWithAlphaComponent(0.3).CGColor 443 | self.touchHighlightLayer = shapeLayer 444 | 445 | if link.highlightedAttributes != nil { 446 | self.setHighlight(true, withRange: self.touchInfo!.linkRange!, textLink: link) 447 | } 448 | } 449 | 450 | if let highlightLayer = self.touchHighlightLayer { 451 | self.layer.addSublayer(highlightLayer) 452 | } 453 | } 454 | 455 | if gestureRecognizer.state == .Changed { 456 | if self.touchInfo?.link != nil && self.touchHighlightLayer != nil { 457 | let location = gestureRecognizer.locationInView(self) 458 | if location.isInRects(self.touchInfo!.linkGlyphRects!) { 459 | if self.touchHighlightLayer?.superlayer != self.layer { 460 | self.layer.addSublayer(self.touchHighlightLayer!) 461 | if self.touchInfo!.link?.highlightedAttributes != nil { 462 | self.setHighlight(true, withRange: self.touchInfo!.linkRange!, textLink: self.touchInfo!.link!) 463 | } 464 | } 465 | 466 | } else { 467 | if self.touchHighlightLayer?.superlayer == self.layer { 468 | self.touchHighlightLayer!.removeFromSuperlayer() 469 | if self.touchInfo?.link?.highlightedAttributes != nil { 470 | self.setHighlight(false, withRange: self.touchInfo!.linkRange!, textLink: self.touchInfo!.link!) 471 | } 472 | } 473 | } 474 | } 475 | } 476 | 477 | if gestureRecognizer.state == .Ended || gestureRecognizer.state == .Cancelled || gestureRecognizer.state == .Failed{ 478 | self.resetTouchInfo() 479 | } 480 | } 481 | 482 | // MARK:- Internal attributed storage operation 483 | 484 | internal func updateTextParaphStyle() { 485 | self.updateTextParaphStyleWithPropertyName("alignment", value: NSNumber(integer: self.textAlignment.rawValue)) 486 | self.updateTextParaphStyleWithPropertyName("lineSpacing", value: NSNumber(float: Float(lineSpacing))) 487 | self.updateTextParaphStyleWithPropertyName("firstLineHeadIndent", value: NSNumber(float: Float(firstLineHeadIndent))) 488 | } 489 | 490 | internal func updateTextParaphStyleWithPropertyName(name: String, value: AnyObject) { 491 | if self.text != nil { 492 | let existedParagraphStyle = self.textStorage.attribute(NSParagraphStyleAttributeName, atIndex: 0, longestEffectiveRange: nil, inRange: NSMakeRange(0, self.textStorage.length)) 493 | var mutableParagraphStyle = existedParagraphStyle?.mutableCopy() as? NSMutableParagraphStyle 494 | if mutableParagraphStyle == nil { 495 | mutableParagraphStyle = NSMutableParagraphStyle() 496 | } 497 | 498 | mutableParagraphStyle?.setValue(value, forKey: name) 499 | self.textStorage.addAttribute(NSParagraphStyleAttributeName, value: mutableParagraphStyle!.copy(), range: self.textStorage.entireRange()) 500 | self.setNeedsDisplay() 501 | } 502 | } 503 | 504 | internal func setHighlight(highlighted: Bool, withRange range:NSRange, textLink link:SwiftyTextLink) { 505 | if link.highlightedAttributes != nil { 506 | if highlighted { 507 | if self.touchAttributesMap == nil { 508 | self.touchAttributesMap = self.textStorage.attributesRangeMapInRange(range) 509 | } 510 | self.textStorage.addAttributes(link.highlightedAttributes!, range: range) 511 | self.setNeedsDisplay() 512 | } else { 513 | if self.touchAttributesMap != nil { 514 | for attributeName in link.highlightedAttributes!.keys { 515 | self.textStorage.removeAttribute(attributeName, range: range) 516 | } 517 | 518 | for (rangeString, attributes) in self.touchAttributesMap! { 519 | let subRange = NSRangeFromString(rangeString) 520 | self.textStorage.addAttributes(attributes, range: subRange) 521 | } 522 | self.setNeedsDisplay() 523 | } 524 | } 525 | } 526 | } 527 | 528 | 529 | 530 | // MARK:- Draw 531 | 532 | override public func drawRect(rect: CGRect) { 533 | super.drawRect(rect) 534 | self.drawTextWithRect(rect, async: self.drawsTextAsynchronously) 535 | } 536 | 537 | func drawTextWithRect(rect: CGRect, async: Bool) { 538 | 539 | if async { 540 | dispatch_async(self.textQueue, { 541 | if self.needsParse { 542 | self.parser?.parseText(self.textStorage) 543 | self.needsParse = false 544 | } 545 | 546 | let constrainedSize = rect.size.insetsWith(self.textContainerInset) 547 | if !CGSizeEqualToSize(self.textContainer.size, constrainedSize) { 548 | self.textContainer.size = constrainedSize 549 | self.layoutManager.textContainerChangedGeometry(self.textContainer) 550 | } 551 | 552 | let textRect:CGRect = UIEdgeInsetsInsetRect(rect, self.textContainerInset) 553 | let range:NSRange = self.layoutManager.glyphRangeForTextContainer(self.textContainer) 554 | let textOrigin:CGPoint = textRect.origin 555 | UIGraphicsBeginImageContextWithOptions(rect.size, false, self.contentScaleFactor) 556 | 557 | self.layoutManager.drawBackgroundForGlyphRange(range, atPoint: textOrigin) 558 | self.layoutManager.drawGlyphsForGlyphRange(range, atPoint: textOrigin) 559 | 560 | let image:UIImage = UIGraphicsGetImageFromCurrentImageContext() 561 | UIGraphicsEndImageContext() 562 | 563 | dispatch_async(dispatch_get_main_queue(), { () -> Void in 564 | CATransaction.begin() 565 | CATransaction.setDisableActions(true) //disable implicit animation 566 | self.asyncTextLayer!.contents = image.CGImage 567 | CATransaction.commit() 568 | 569 | self.updateAccessibleElements() 570 | }) 571 | }) 572 | } else { 573 | if self.needsParse { 574 | self.parser?.parseText(self.textStorage) 575 | self.needsParse = false 576 | } 577 | 578 | let constrainedSize = rect.size.insetsWith(self.textContainerInset) 579 | if !CGSizeEqualToSize(self.textContainer.size, constrainedSize) { 580 | self.textContainer.size = constrainedSize 581 | self.layoutManager.textContainerChangedGeometry(self.textContainer) 582 | } 583 | 584 | let textRect:CGRect = UIEdgeInsetsInsetRect(rect, self.textContainerInset) 585 | let range:NSRange = self.layoutManager.glyphRangeForTextContainer(self.textContainer) 586 | let textOrigin:CGPoint = textRect.origin 587 | self.layoutManager.drawBackgroundForGlyphRange(range, atPoint: textOrigin) 588 | self.layoutManager.drawGlyphsForGlyphRange(range, atPoint: textOrigin) 589 | 590 | self.updateAccessibleElements() 591 | } 592 | } 593 | 594 | 595 | // MARK:- Size & Layout 596 | 597 | /// https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/TextLayout/Tasks/StringHeight.html 598 | public func proposedSizeWithConstrainedSize(constrainedSize: CGSize) -> CGSize { 599 | let constrainedSizeWithInsets = constrainedSize.insetsWith(self.textContainerInset) 600 | 601 | self.textContainer.size = constrainedSizeWithInsets 602 | self.layoutManager.glyphRangeForTextContainer(self.textContainer) 603 | var proposedSize = self.layoutManager.usedRectForTextContainer(self.textContainer).size 604 | proposedSize.width = ceil(proposedSize.width) 605 | proposedSize.height = ceil(proposedSize.height) 606 | proposedSize.width += (self.textContainerInset.left + self.textContainerInset.right) 607 | proposedSize.height += (self.textContainerInset.top + self.textContainerInset.bottom) 608 | proposedSize.width = ceil(proposedSize.width) 609 | proposedSize.height = ceil(proposedSize.height) 610 | return proposedSize; 611 | } 612 | 613 | public override func sizeThatFits(size: CGSize) -> CGSize { 614 | var constrainedSize = size 615 | constrainedSize.height = CGFloat.max 616 | return self.proposedSizeWithConstrainedSize(constrainedSize) 617 | } 618 | 619 | public override func layoutSubviews() { 620 | super.layoutSubviews() 621 | CATransaction.begin() 622 | CATransaction.setDisableActions(true) //disable implicit animation 623 | self.asyncTextLayer?.frame = self.layer.bounds 624 | CATransaction.commit() 625 | } 626 | 627 | // MARK:- Layout Manager Delegate 628 | public func layoutManager(layoutManager: NSLayoutManager, didCompleteLayoutForTextContainer textContainer: NSTextContainer?, atEnd layoutFinishedFlag: Bool) { 629 | if (layoutFinishedFlag) { 630 | for attachement in self.textStorage.viewAttachments() { 631 | if (attachement.contentView != nil) { 632 | var frame = attachement.contentViewFrameInTextContainer!; 633 | frame.origin.x += self.textContainerInset.left; 634 | frame.origin.y += self.textContainerInset.top; 635 | dispatch_async(dispatch_get_main_queue(), { () -> Void in 636 | attachement.contentView!.removeFromSuperview() 637 | attachement.contentView!.frame = frame; 638 | self.insertSubview(attachement.contentView!, atIndex: 0) 639 | }) 640 | } 641 | } 642 | } 643 | } 644 | 645 | 646 | // MARK:- Accessibility 647 | 648 | internal func updateAccessibleElements() { 649 | guard UIAccessibilityIsVoiceOverRunning() else { 650 | self.accessibilityElements = nil 651 | return 652 | } 653 | 654 | self.isAccessibilityElement = false 655 | var elements = [AnyObject]() 656 | 657 | 658 | // Text element itself 659 | let textElement = UIAccessibilityElement(accessibilityContainer: self) 660 | textElement.accessibilityLabel = self.text 661 | textElement.accessibilityTraits = UIAccessibilityTraitStaticText 662 | textElement.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(self.bounds,self) 663 | elements.append(textElement) 664 | 665 | // link element 666 | dispatch_sync(self.textQueue) { () -> Void in 667 | self.textStorage.enumerateAttribute(SwiftyTextLinkAttributeName, inRange: self.textStorage.entireRange(), options:[]) { (value: AnyObject?, range: NSRange, stop: UnsafeMutablePointer) -> Void in 668 | if value != nil && value is SwiftyTextLink { 669 | let linkElement = UIAccessibilityElement(accessibilityContainer: self) 670 | 671 | let glyphRects = self.layoutManager.glyphRectsWithCharacterRange(range, containerInset: self.textContainerInset) 672 | var screenRects = [CGRect]() 673 | if glyphRects != nil { 674 | for glyphRect in glyphRects! { 675 | screenRects.append(UIAccessibilityConvertFrameToScreenCoordinates(glyphRect, self)) 676 | } 677 | 678 | linkElement.accessibilityPath = UIBezierPath.bezierPathWithGlyphRects(screenRects, radius: 0) 679 | let firstScreenRect = screenRects[0] 680 | let firstCenterPoint = CGPointMake(firstScreenRect.midX, firstScreenRect.midY) 681 | linkElement.accessibilityActivationPoint = firstCenterPoint 682 | linkElement.accessibilityLabel = (self.text! as NSString).substringWithRange(range) 683 | linkElement.accessibilityTraits = UIAccessibilityTraitLink 684 | elements.append(linkElement) 685 | } 686 | } 687 | } 688 | } 689 | 690 | self.accessibilityElements = elements 691 | } 692 | 693 | internal func voiceOverStatusChanged() { 694 | self.updateAccessibleElements(); 695 | } 696 | } 697 | 698 | 699 | struct SwiftyLabelTouchInfo{ 700 | var touch: UITouch? 701 | 702 | var link: SwiftyTextLink? 703 | var linkRange: NSRange? 704 | var linkGlyphRects: [CGRect]? 705 | 706 | init(aTouch: UITouch) { 707 | self.touch = aTouch 708 | 709 | let label = aTouch.view as? SwiftyLabel 710 | let location = aTouch.locationInView(label) 711 | var effectiveRange = NSMakeRange(NSNotFound, 0) 712 | var effectiveGlyphRects = [CGRect]() 713 | 714 | let touchLink = label?.linkAtLocation(location, effectiveRange: &effectiveRange, effectiveGlyphRects: &effectiveGlyphRects) 715 | if touchLink != nil && effectiveRange.location != NSNotFound && effectiveGlyphRects.count > 0{ 716 | self.link = touchLink 717 | self.linkRange = effectiveRange 718 | self.linkGlyphRects = effectiveGlyphRects 719 | } 720 | } 721 | } 722 | 723 | 724 | public protocol SwiftyLabelDelegate: NSObjectProtocol { 725 | 726 | /// Delegate methods for the touch of link 727 | func swiftyLabel(swiftyLabel: SwiftyLabel, didTapWithTextLink link: SwiftyTextLink, range: NSRange) 728 | func swiftyLabel(swiftyLabel: SwiftyLabel, didLongPressWithTextLink link:SwiftyTextLink, range: NSRange) 729 | } 730 | 731 | 732 | //Default implementations for SwiftyLabelDelegate 733 | public extension SwiftyLabelDelegate { 734 | func swiftyLabel(swiftyLabel: SwiftyLabel, didTapWithTextLink link: SwiftyTextLink, range: NSRange){} 735 | func swiftyLabel(swiftyLabel: SwiftyLabel, didLongPressWithTextLink link:SwiftyTextLink, range: NSRange){} 736 | } 737 | 738 | 739 | extension CGSize { 740 | public func insetsWith(insets: UIEdgeInsets) -> CGSize{ 741 | return CGSizeMake(self.width - insets.left - insets.right, self.height - insets.top - insets.bottom) 742 | } 743 | } 744 | 745 | 746 | extension CGPoint { 747 | public func isInRects(rects: [CGRect]) -> Bool{ 748 | for rect in rects { 749 | if CGRectContainsPoint(rect, self) { 750 | return true 751 | } 752 | } 753 | return false 754 | } 755 | } 756 | -------------------------------------------------------------------------------- /SwiftyText/SwiftyLabel/SwiftyTextAttachment.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftyTextAttachment.swift 3 | // SwiftyLabel 4 | // 5 | // Created by Luke on 12/1/15. 6 | // Copyright © 2015 geeklu.com. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum TBTextAttachmentTextVerticalAlignment: Int { 12 | case Bottom = 0 13 | case Center 14 | case Top 15 | case Scale 16 | } 17 | 18 | public class SwiftyTextAttachment: NSTextAttachment { 19 | public var contentView: UIView? 20 | public private(set) var contentViewFrameInTextContainer:CGRect? 21 | 22 | /** The layout padding at the beginning and end of the view or image attachment 23 | */ 24 | public var padding: CGFloat = 0.0 25 | 26 | /** Image size setting for the image attachment when use Bottom, Center or Top Vertical Alignment 27 | */ 28 | public var imageSize: CGSize = CGSizeZero 29 | 30 | public var attachmentTextVerticalAlignment: TBTextAttachmentTextVerticalAlignment = .Bottom 31 | 32 | public var verticalOffset: CGFloat = 0 33 | public var fontDescender: CGFloat = 0 34 | 35 | public var userInfo:[String: AnyObject?]? 36 | 37 | public override func attachmentBoundsForTextContainer(textContainer: NSTextContainer?, proposedLineFragment lineFrag: CGRect, glyphPosition position: CGPoint, characterIndex charIndex: Int) -> CGRect { 38 | var attachmentBounds = CGRectZero 39 | 40 | if !CGRectEqualToRect(CGRectZero, self.bounds) { 41 | attachmentBounds = self.bounds 42 | 43 | if self.contentView != nil { 44 | var lineFragmentPadding:CGFloat = 0.0 45 | if textContainer != nil { 46 | lineFragmentPadding = textContainer!.lineFragmentPadding 47 | } 48 | contentViewFrameInTextContainer = CGRectMake(position.x + self.padding + lineFragmentPadding, position.y, attachmentBounds.size.width, attachmentBounds.size.height); 49 | attachmentBounds.size.width += (self.padding * 2); 50 | } 51 | return attachmentBounds 52 | } 53 | 54 | let totalOffset = self.verticalOffset + self.fontDescender 55 | 56 | if self.image != nil || self.contentView != nil { 57 | var attachmentSize = CGSizeZero 58 | if self.image != nil { 59 | if CGSizeEqualToSize(CGSizeZero, self.imageSize) { 60 | attachmentSize = self.image!.size 61 | } else { 62 | attachmentSize = self.imageSize 63 | } 64 | } else { 65 | attachmentSize = self.contentView!.frame.size 66 | } 67 | 68 | switch self.attachmentTextVerticalAlignment { 69 | case .Bottom : 70 | attachmentBounds = CGRectMake(0, 0, attachmentSize.width, attachmentSize.height) 71 | break 72 | case .Center : 73 | let y = (CGRectGetHeight(lineFrag) - attachmentSize.height)/2; 74 | attachmentBounds = CGRectMake(0, y + totalOffset, attachmentSize.width, attachmentSize.height) 75 | break 76 | case .Top : 77 | attachmentBounds = CGRectMake(0, CGRectGetHeight(lineFrag) - attachmentSize.height + totalOffset, attachmentSize.width, attachmentSize.height); 78 | break 79 | case .Scale : 80 | let scale = CGRectGetHeight(lineFrag)/attachmentSize.height; 81 | attachmentBounds = CGRectMake(0, totalOffset, attachmentSize.width * scale, attachmentSize.height * scale); 82 | if (self.contentView != nil) { 83 | var contnetViewFrame = self.contentView!.frame; 84 | contnetViewFrame.size = attachmentBounds.size; 85 | self.contentViewFrameInTextContainer = contnetViewFrame; 86 | } 87 | break 88 | } 89 | 90 | if (self.contentView != nil) { 91 | attachmentBounds.size.width += (self.padding * 2); 92 | var contentViewFrame = self.contentView!.frame; 93 | var lineFragmentPadding:CGFloat = 0.0 94 | if textContainer != nil { 95 | lineFragmentPadding = textContainer!.lineFragmentPadding 96 | } 97 | contentViewFrame.origin.x = position.x + self.padding + lineFragmentPadding 98 | contentViewFrame.origin.y = position.y; 99 | self.contentViewFrameInTextContainer = contentViewFrame; 100 | } 101 | } else { 102 | attachmentBounds = super.attachmentBoundsForTextContainer(textContainer, proposedLineFragment: lineFrag, glyphPosition: position, characterIndex: charIndex); 103 | } 104 | 105 | return attachmentBounds 106 | } 107 | } 108 | 109 | 110 | /** 111 | Blank placeholder usded for image padding 112 | */ 113 | public class SwiftyTextBlankAttachment: NSTextAttachment { 114 | public var width: CGFloat { 115 | get{ 116 | return CGRectGetWidth(bounds) 117 | } 118 | 119 | set { 120 | bounds.size.width = newValue 121 | bounds.size.height = 1 122 | } 123 | } 124 | } -------------------------------------------------------------------------------- /SwiftyText/SwiftyLabel/SwiftyTextDetector.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftyTextDataDetector.swift 3 | // SwiftyText 4 | // 5 | // Created by Luke on 12/3/15. 6 | // Copyright © 2015 geeklu.com. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | internal let SwiftyTextDetectorResultAttributeName: String = "SwiftyTextDetectorResult" 12 | 13 | public struct SwiftyTextDetectorType: OptionSetType { 14 | public let rawValue: UInt 15 | public init(rawValue: UInt){ self.rawValue = rawValue} 16 | 17 | public static let None = SwiftyTextDetectorType(rawValue: 0) 18 | public static let PhoneNumber = SwiftyTextDetectorType(rawValue: 1) 19 | public static let URL = SwiftyTextDetectorType(rawValue: 1 << 1) 20 | public static let Date = SwiftyTextDetectorType(rawValue: 1 << 2) 21 | public static let Address = SwiftyTextDetectorType(rawValue: 1 << 3) 22 | public static let All = SwiftyTextDetectorType(rawValue: UInt.max) 23 | } 24 | 25 | /** 26 | SwiftyTextDetector is a special kind of SwiftyTextParser which parse attributed string with regular expression 27 | */ 28 | public class SwiftyTextDetector: NSObject, SwiftyTextParser { 29 | public var name:String 30 | public var regularExpression: NSRegularExpression 31 | public var attributes: [String : AnyObject]? 32 | public var highlightedAttributes: [String : AnyObject]? 33 | 34 | public var replacementAttributedText:((checkingResult: NSTextCheckingResult, matchedAttributedText: NSAttributedString, sourceAttributedText: NSAttributedString) -> NSAttributedString?)? 35 | 36 | /// link attributes 37 | public var linkable: Bool = false 38 | public var linkGestures: SwiftyTextLinkGesture = [.Tap] 39 | 40 | public var highlightLayerRadius: CGFloat? 41 | public var highlightLayerColor: UIColor? 42 | 43 | public init(name:String, regularExpression: NSRegularExpression, attributes: [String : AnyObject]?) { 44 | self.name = name 45 | self.regularExpression = regularExpression 46 | self.attributes = attributes 47 | 48 | super.init() 49 | } 50 | 51 | public func parseText(attributedText: NSMutableAttributedString) { 52 | let text = attributedText.string 53 | let checkingResults = self.regularExpression.matchesInString(text, options: NSMatchingOptions(), range: NSMakeRange(0, text.characters.count)) 54 | 55 | for result in checkingResults.reverse() { 56 | let checkingRange = result.range 57 | var resultRange = checkingRange 58 | 59 | if checkingRange.location == NSNotFound { 60 | continue 61 | } 62 | 63 | let detectorResultAttribute = attributedText.attribute(SwiftyTextDetectorResultAttributeName, atIndex: checkingRange.location, longestEffectiveRange: nil, inRange: checkingRange) 64 | if detectorResultAttribute != nil { 65 | continue 66 | } 67 | 68 | if let attributes = self.attributes { 69 | attributedText.addAttributes(attributes, range: checkingRange) 70 | } 71 | 72 | if let replacementFunc = self.replacementAttributedText { 73 | let replacement = replacementFunc(checkingResult: result, matchedAttributedText: attributedText.attributedSubstringFromRange(checkingRange), sourceAttributedText: attributedText) 74 | if replacement != nil { 75 | attributedText.replaceCharactersInRange(checkingRange, withAttributedString: replacement!) 76 | resultRange.length = replacement!.length 77 | } 78 | } 79 | 80 | if self.linkable { 81 | let link = SwiftyTextLink() 82 | link.highlightedAttributes = self.highlightedAttributes 83 | 84 | if self.highlightLayerRadius != nil { 85 | link.highlightLayerRadius = self.highlightLayerRadius 86 | } 87 | if self.highlightLayerColor != nil { 88 | link.highlightLayerColor = self.highlightLayerColor 89 | } 90 | 91 | link.gestures = self.linkGestures 92 | 93 | if let URL = result.URL { 94 | link.URL = URL 95 | } 96 | 97 | if let phoneNumber = result.phoneNumber { 98 | link.phoneNumber = phoneNumber 99 | } 100 | 101 | if let date = result.date { 102 | link.date = date 103 | } 104 | 105 | if let timeZone = result.timeZone { 106 | link.timeZone = timeZone 107 | } 108 | 109 | if let addressComponents = result.addressComponents { 110 | link.addressComponents = addressComponents 111 | } 112 | 113 | attributedText.addAttribute(SwiftyTextLinkAttributeName, value: link, range: resultRange) 114 | } 115 | 116 | attributedText.addAttribute(SwiftyTextDetectorResultAttributeName, value: self, range: resultRange) 117 | } 118 | 119 | } 120 | 121 | public class func detectorWithType(type: SwiftyTextDetectorType) -> SwiftyTextDetector?{ 122 | var checkingTypes = NSTextCheckingType() 123 | 124 | if type.contains(.PhoneNumber) { 125 | checkingTypes = checkingTypes.union(.PhoneNumber) 126 | } 127 | if type.contains(.URL) { 128 | checkingTypes = checkingTypes.union(.Link) 129 | } 130 | if type.contains(.Date) { 131 | checkingTypes = checkingTypes.union(.Date) 132 | } 133 | if type.contains(.Address) { 134 | checkingTypes = checkingTypes.union(.Address) 135 | } 136 | do { 137 | let dataDetector = try NSDataDetector(types: checkingTypes.rawValue) 138 | let textDetector = SwiftyTextDetector(name: "Link", regularExpression: dataDetector, attributes: [NSForegroundColorAttributeName:UIColor(red: 0, green: 122/255.0, blue: 1.0, alpha: 1.0),NSUnderlineStyleAttributeName:NSUnderlineStyle.StyleSingle.rawValue]) 139 | textDetector.linkable = true 140 | return textDetector 141 | } catch { 142 | 143 | } 144 | return nil; 145 | } 146 | } -------------------------------------------------------------------------------- /SwiftyText/SwiftyLabel/SwiftyTextLink.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftyTextLink.swift 3 | // SwiftyText 4 | // 5 | // Created by Luke on 12/17/15. 6 | // Copyright © 2015 geeklu.com. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public let SwiftyTextLinkAttributeName: String = "SwiftyTextLink" 12 | 13 | /** 14 | SwiftyTextLink holds link style and identifier infomation. 15 | 16 | Some special link identifier has been list as property such as URL, phoneNumber etc.. 17 | You also can use userInfo to identify you link. 18 | 19 | **Properties**: 20 | - attributes: Attributes which will be applied to the target text 21 | - highlightedAttributes: Attributeds applied to the text when highlighted. 22 | 23 | - highlightLayerRadius,highlightLayerColor: When highlight, there can be a mask layer over the link, these two properties can be setted to style that layer 24 | 25 | */ 26 | public class SwiftyTextLink: NSObject { 27 | public var gestures: SwiftyTextLinkGesture = [.Tap] 28 | 29 | public var attributes: [String: AnyObject]? 30 | public var highlightedAttributes: [String: AnyObject]? 31 | 32 | public var highlightLayerRadius: CGFloat? 33 | public var highlightLayerColor: UIColor? 34 | 35 | public var URL: NSURL? 36 | public var date: NSDate? 37 | public var timeZone: NSTimeZone? 38 | public var phoneNumber: String? 39 | public var addressComponents: [String : String]? 40 | 41 | public var userInfo: [String: AnyObject]? 42 | } 43 | 44 | 45 | public struct SwiftyTextLinkGesture: OptionSetType { 46 | public let rawValue: UInt 47 | public init(rawValue: UInt){ self.rawValue = rawValue} 48 | 49 | public static let None = SwiftyTextLinkGesture(rawValue: 0) 50 | public static let Tap = SwiftyTextLinkGesture(rawValue: 1) 51 | public static let LongPress = SwiftyTextLinkGesture(rawValue: 1 << 1) 52 | } 53 | -------------------------------------------------------------------------------- /SwiftyText/SwiftyLabel/SwiftyTextParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftyTextParser.swift 3 | // SwiftyText 4 | // 5 | // Created by Luke on 12/23/15. 6 | // Copyright © 2015 geeklu.com. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol SwiftyTextParser { 12 | func parseText(attributedText: NSMutableAttributedString) 13 | } 14 | 15 | public class SwiftyTextSuperParser: NSObject, SwiftyTextParser { 16 | 17 | public init(parsers: [SwiftyTextParser]) { 18 | self.subParsers = parsers 19 | super.init() 20 | } 21 | 22 | public var subParsers: [SwiftyTextParser]? 23 | 24 | public func parseText(attributedText: NSMutableAttributedString) { 25 | if subParsers != nil { 26 | for subParser in self.subParsers! { 27 | subParser.parseText(attributedText) 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /SwiftyText/SwiftyText.h: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftyText.h 3 | // SwiftyText 4 | // 5 | // Created by Luke on 12/11/15. 6 | // Copyright © 2015 geeklu.com. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for SwiftyText. 12 | FOUNDATION_EXPORT double SwiftyTextVersionNumber; 13 | 14 | //! Project version string for SwiftyText. 15 | FOUNDATION_EXPORT const unsigned char SwiftyTextVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /SwiftyTextDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SwiftyTextDemo 4 | // 5 | // Created by Luke on 12/11/15. 6 | // Copyright © 2015 geeklu.com. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 18 | // Override point for customization after application launch. 19 | //KMCGeigerCounter.sharedGeigerCounter().enabled = true 20 | return true 21 | } 22 | 23 | func applicationWillResignActive(application: UIApplication) { 24 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 25 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 26 | } 27 | 28 | func applicationDidEnterBackground(application: UIApplication) { 29 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | func applicationWillEnterForeground(application: UIApplication) { 34 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 35 | } 36 | 37 | func applicationDidBecomeActive(application: UIApplication) { 38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 39 | } 40 | 41 | func applicationWillTerminate(application: UIApplication) { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | 46 | } 47 | 48 | -------------------------------------------------------------------------------- /SwiftyTextDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /SwiftyTextDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /SwiftyTextDemo/Assets.xcassets/SwiftyText.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "swifty-text.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SwiftyTextDemo/Assets.xcassets/SwiftyText.imageset/swifty-text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kejinlu/SwiftyText/a3f83bdda84a36253fac3692ecb64f941602b619/SwiftyTextDemo/Assets.xcassets/SwiftyText.imageset/swifty-text.png -------------------------------------------------------------------------------- /SwiftyTextDemo/Assets.xcassets/swift.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "filename" : "swift.png", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SwiftyTextDemo/Assets.xcassets/swift.imageset/swift.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kejinlu/SwiftyText/a3f83bdda84a36253fac3692ecb64f941602b619/SwiftyTextDemo/Assets.xcassets/swift.imageset/swift.png -------------------------------------------------------------------------------- /SwiftyTextDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /SwiftyTextDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /SwiftyTextDemo/BasicViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SwiftyTextDemo 4 | // 5 | // Created by Luke on 12/11/15. 6 | // Copyright © 2015 geeklu.com. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftyText 11 | 12 | class BasicViewController: UIViewController, SwiftyLabelDelegate { 13 | 14 | @IBOutlet weak var label: SwiftyLabel! 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | self.view.backgroundColor = UIColor(red: 229/255.0, green: 229/255.0, blue: 229/255.0, alpha: 1) 19 | 20 | label.delegate = self 21 | label.backgroundColor = UIColor(red: 243/255.0, green: 1, blue: 236/255.0, alpha: 1) 22 | label.text = "Swift is a powerful and intuitive programming language for iOS, OS X, tvOS, and watchOS. https://developer.apple.com/swift/resources/ . Writing Swift code is interactive and fun, the syntax is concise yet expressive, and apps run lightning-fast. Swift is ready for your next project — or addition into your current app — because Swift code works side-by-side with Objective-C. " 23 | label.textContainerInset = UIEdgeInsetsMake(6, 6, 6, 6) 24 | label.font = UIFont.systemFontOfSize(14) 25 | label.textColor = UIColor.blackColor() 26 | label.firstLineHeadIndent = 24 27 | label.drawsTextAsynchronously = true 28 | 29 | let link = SwiftyTextLink() 30 | link.URL = NSURL(string: "https://developer.apple.com/swift/") 31 | link.attributes = [NSForegroundColorAttributeName:UIColor(red: 0, green: 122/255.0, blue: 1.0, alpha: 1.0),NSUnderlineStyleAttributeName:NSUnderlineStyle.StyleSingle.rawValue] 32 | label.setLink(link, range: NSMakeRange(0, 5)) 33 | 34 | let imageAttachment = SwiftyTextAttachment() 35 | imageAttachment.image = UIImage(named: "swift") 36 | imageAttachment.attachmentTextVerticalAlignment = .Center 37 | imageAttachment.padding = 10.0 38 | label.insertAttachment(imageAttachment, atIndex: label.attributedText!.length - 9) 39 | 40 | let sliderAttachment = SwiftyTextAttachment() 41 | let slider = UISlider() 42 | sliderAttachment.contentView = slider; 43 | sliderAttachment.padding = 3.0 44 | sliderAttachment.attachmentTextVerticalAlignment = .Center 45 | label.insertAttachment(sliderAttachment, atIndex: 8) 46 | 47 | let detector = SwiftyTextDetector.detectorWithType([.URL,.Address]) 48 | detector!.linkGestures = [.Tap, .LongPress] 49 | if detector != nil { 50 | label.parser = detector 51 | } 52 | label.sizeToFit() 53 | } 54 | 55 | override func didReceiveMemoryWarning() { 56 | super.didReceiveMemoryWarning() 57 | // Dispose of any resources that can be recreated. 58 | } 59 | 60 | 61 | // MARK:- SwiftyLabelDelegate 62 | func swiftyLabel(swiftyLabel: SwiftyLabel, didTapWithTextLink link: SwiftyTextLink, range: NSRange){ 63 | if let URL = link.URL { 64 | let sheet = UIAlertController(title: "Link", message: URL.absoluteString , preferredStyle: .ActionSheet) 65 | let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel, handler: { (action) -> Void in 66 | self.dismissViewControllerAnimated(true, completion: nil) 67 | }) 68 | sheet.addAction(cancelAction) 69 | 70 | let openAction = UIAlertAction(title: "Open in Safari", style: .Default, handler: { (action) -> Void in 71 | UIApplication.sharedApplication().openURL(URL) 72 | }) 73 | sheet.addAction(openAction) 74 | 75 | self.presentViewController(sheet, animated: true, completion: nil) 76 | } 77 | } 78 | 79 | func swiftyLabel(swiftyLabel: SwiftyLabel, didLongPressWithTextLink link:SwiftyTextLink, range: NSRange){ 80 | 81 | } 82 | 83 | func swiftyLabelShoudTouch(swiftyLabel: SwiftyLabel) -> Bool { 84 | return true 85 | } 86 | } 87 | 88 | -------------------------------------------------------------------------------- /SwiftyTextDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UIRequiresFullScreen 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /SwiftyTextDemo/ListViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListViewController.swift 3 | // SwiftyText 4 | // 5 | // Created by Luke on 1/5/16. 6 | // Copyright © 2016 geeklu.com. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftyText 11 | 12 | class ListViewController: UITableViewController, SwiftyLabelDelegate { 13 | var attributedTexts = [NSAttributedString]() 14 | override func viewDidLoad() { 15 | //self.tableView.allowsSelection = false 16 | self.tableView.contentInset = UIEdgeInsetsMake(20.0, 0.0, 0.0, 0.0) 17 | var i = 0 18 | while i < 75 { 19 | let a = NSMutableAttributedString(string: "Writing Swift code is interactive and fun, the syntax is concise yet expressive, and apps run lightning-fast. Swift is ready for your next project — or addition into your current app — because Swift code works side-by-side with Objective-C.") 20 | let link = SwiftyTextLink() 21 | link.attributes = [NSForegroundColorAttributeName:UIColor(red: 0, green: 122/255.0, blue: 1.0, alpha: 1.0),NSUnderlineStyleAttributeName:NSUnderlineStyle.StyleSingle.rawValue] 22 | link.URL = NSURL(string: "https://developer.apple.com/swift/") 23 | a.setLink(link, range: NSMakeRange(8, 5)) 24 | 25 | let imageAttachment = SwiftyTextAttachment() 26 | imageAttachment.image = UIImage(named: "swift") 27 | imageAttachment.padding = 10.0 28 | imageAttachment.imageSize = CGSizeMake(40, 40) 29 | imageAttachment.attachmentTextVerticalAlignment = .Center 30 | a.insertAttachment(imageAttachment, atIndex: a.length - 9) 31 | 32 | self.attributedTexts.append(a) 33 | i += 1 34 | } 35 | } 36 | 37 | override func numberOfSectionsInTableView(tableView: UITableView) -> Int { 38 | return 1 39 | } 40 | 41 | override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 42 | return self.attributedTexts.count 43 | } 44 | 45 | override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 46 | let CellIdentifier = "SwiftyTextCell" 47 | let cell = tableView.dequeueReusableCellWithIdentifier(CellIdentifier) as! SwiftyLabelCell 48 | let attributedText = self.attributedTexts[indexPath.row] 49 | cell.swiftyLabel.drawsTextAsynchronously = true 50 | cell.swiftyLabel.attributedText = attributedText 51 | return cell 52 | } 53 | 54 | override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { 55 | print("row selected") 56 | } 57 | 58 | override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { 59 | let attributedText = self.attributedTexts[indexPath.row] 60 | let size = attributedText.proposedSizeWithConstrainedSize(CGSize(width: self.tableView.bounds.width, height: CGFloat.max), exclusionPaths: nil, lineBreakMode: nil, maximumNumberOfLines: nil) 61 | return size.height 62 | } 63 | 64 | override func scrollViewWillBeginDragging(scrollView: UIScrollView) { 65 | 66 | } 67 | } -------------------------------------------------------------------------------- /SwiftyTextDemo/SwiftyLabelCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftyLabelCell.swift 3 | // SwiftyText 4 | // 5 | // Created by Luke on 1/5/16. 6 | // Copyright © 2016 geeklu.com. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import SwiftyText 12 | 13 | class SwiftyLabelCell: UITableViewCell { 14 | @IBOutlet weak var swiftyLabel: SwiftyLabel! 15 | override func prepareForReuse() { 16 | super.prepareForReuse() 17 | self.swiftyLabel.drawsTextAsynchronously = true 18 | } 19 | override func layoutSubviews() { 20 | super.layoutSubviews() 21 | self.swiftyLabel.frame = self.contentView.bounds 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /SwiftyTextDemo/SwiftyTextDemo-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftyTextDemo-Bridging-Header.h.h 3 | // SwiftyText 4 | // 5 | // Created by Luke on 1/5/16. 6 | // Copyright © 2016 geeklu.com. All rights reserved. 7 | // 8 | 9 | #ifndef SwiftyTextDemo_Bridging_Header_h_h 10 | #define SwiftyTextDemo_Bridging_Header_h_h 11 | 12 | #endif /* SwiftyTextDemo_Bridging_Header_h_h */ 13 | -------------------------------------------------------------------------------- /SwiftyTextTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /SwiftyTextTests/SwiftyTextTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftyTextTests.swift 3 | // SwiftyTextTests 4 | // 5 | // Created by Luke on 12/11/15. 6 | // Copyright © 2015 geeklu.com. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import SwiftyText 11 | 12 | class SwiftyTextTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measureBlock { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | --------------------------------------------------------------------------------