├── .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 | [](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 | [](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 |
--------------------------------------------------------------------------------