├── .gitignore
├── LICENSE
├── README.md
├── Samples
├── FallingLabel
│ ├── .gitignore
│ ├── FallingCounterLabel.swift
│ ├── FallingLabel.xcodeproj
│ │ ├── project.pbxproj
│ │ └── project.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ ├── FallingLabel
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets
│ │ │ ├── AppIcon.appiconset
│ │ │ │ └── Contents.json
│ │ │ └── Contents.json
│ │ ├── Base.lproj
│ │ │ ├── LaunchScreen.storyboard
│ │ │ └── Main.storyboard
│ │ ├── Info.plist
│ │ ├── SceneDelegate.swift
│ │ └── ViewController.swift
│ └── Podfile
├── FlyingText
│ ├── .gitignore
│ ├── FlyingText.xcodeproj
│ │ ├── project.pbxproj
│ │ └── project.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ ├── FlyingText
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets
│ │ │ ├── AppIcon.appiconset
│ │ │ │ └── Contents.json
│ │ │ └── Contents.json
│ │ ├── Base.lproj
│ │ │ ├── LaunchScreen.storyboard
│ │ │ └── Main.storyboard
│ │ ├── Info.plist
│ │ ├── SceneDelegate.swift
│ │ └── ViewController.swift
│ └── Podfile
├── TextDecompose
│ ├── .gitignore
│ ├── Podfile
│ ├── TextDecompose.xcodeproj
│ │ ├── project.pbxproj
│ │ └── project.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── TextDecompose
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ └── Contents.json
│ │ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ │ ├── Info.plist
│ │ ├── SceneDelegate.swift
│ │ └── ViewController.swift
└── TextToSVG
│ ├── .gitignore
│ ├── Podfile
│ ├── TextToSVG.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
│ └── TextToSVG
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ └── Contents.json
│ ├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
│ ├── Info.plist
│ ├── SceneDelegate.swift
│ └── ViewController.swift
├── Source
└── NSAttributedString.swift
├── TextPaths.podspec
└── _Assets
├── textpaths-bounds-01.jpg
├── textpaths-bounds-02.jpg
├── textpaths-bounds-03.jpg
├── textpaths-bounds-04.jpg
├── textpaths-falling-uilabel-animation.gif
├── textpaths-fly-in-text-animation.gif
├── textpaths-nsattributedtext-decompose.jpg
├── textpaths-nsattributedtext-to-svg-01.jpg
└── textpaths-nsattributedtext-to-svg-02.jpg
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xcuserstate
23 |
24 | ## Obj-C/Swift specific
25 | *.hmap
26 | *.ipa
27 | *.dSYM.zip
28 | *.dSYM
29 |
30 | ## Playgrounds
31 | timeline.xctimeline
32 | playground.xcworkspace
33 |
34 | # Swift Package Manager
35 | #
36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
37 | # Packages/
38 | .build/
39 |
40 | # CocoaPods
41 | #
42 | # We recommend against adding the Pods directory to your .gitignore. However
43 | # you should judge for yourself, the pros and cons are mentioned at:
44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
45 | #
46 | # Pods/
47 |
48 | # Carthage
49 | #
50 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
51 | # Carthage/Checkouts
52 |
53 | Carthage/Build
54 |
55 | # fastlane
56 | #
57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
58 | # screenshots whenever they are needed.
59 | # For more information about the recommended setup visit:
60 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
61 |
62 | fastlane/report.xml
63 | fastlane/Preview.html
64 | fastlane/screenshots
65 | fastlane/test_output
66 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Matt
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TextPaths
2 |
3 | **TextPaths** is an utility for converting `NSAttributedText` to vector representation where each character of an input text is represented as a `CGPAth` glyph. **TextPaths** also returns typographic propeties for lines and entire text flow. For in-depth knowledge on how iOS handles text see [Using Text Kit to Draw and Manage Text](https://developer.apple.com/library/archive/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/CustomTextProcessing/CustomTextProcessing.html) chapter in documentation.
4 |
5 | Source `NSAttributedText` is converted into a tree like representation with char glyphs on leafs.
6 |
7 | ```
8 | TextPath
9 | ⎿ TextPathFrame[]
10 | ⎿ TextPathLine[]
11 | ⎿ TextPathGlyph[]
12 |
13 | ⎿ ComposedTextPath
14 | ```
15 |
16 | `TextPathFrame` - text frame representation
17 |
18 |
19 |
20 | `TextPathLine` - single text line representation
21 |
22 |
23 |
24 | There is also a text bounds rectangle available. Text bounds rectangle is equal or less then line bounds rectangle. On image below this metric is show in purple.
25 |
26 |
27 |
28 | `TextPathGlyph` - single character representation (glyph)
29 |
30 |
31 |
32 | `ComposedTextPath` - composed path of entire input text. This is illustrated on all images above in black.
33 |
34 | All illustrations were created with [`TextDecompose`](https://github.com/malczak/TextPaths/tree/master/Samples/TextDecompose) sample application.
35 |
36 | ## Important notes
37 |
38 | - `UITextView` has extra edge insets (padding) of apporx 8px
39 | - `UIKit` treats text different than `CoreText` when different font size is used to trailing line-break `\n` character. TextPaths instead of following `CoreText` is using the same approach as `UIKit`. See code comments in `TextPathLine` class for `effectiveDescent` property.
40 |
41 | ## Production use
42 |
43 | This repo is used in my apps
44 |
45 | - [Fabristic](https://fabristic.com)
46 | - [Last Day? Life progress and stats](https://apps.apple.com/us/app/last-day-track-events-life/id1193076940?ign-mpt=uo%3D4)
47 |
48 | ## Sample Applications
49 |
50 | ### [`TextDecompose`](https://github.com/malczak/TextPaths/tree/master/Samples/TextDecompose)
51 |
52 | A sample showing how NSAttributedString is decomposed into CGPaths keeping all attributes and typographic properties.
53 | See screenshots below
54 |
55 | ### [`TextToSVG`](https://github.com/malczak/TextPaths/tree/master/Samples/TextToSVG)
56 |
57 | NSAttributedString to SVG conversion. Entire NSAttributedString is converted in to one merged CGPath and then serialized as SVG path. Resulting SVG is presented in WKWebView for comparison.
58 |
59 | ### [`FallingLabel`](https://github.com/malczak/TextPaths/tree/master/Samples/FallingLabel)
60 |
61 | Falling letters counter application shows how to to use TextPaths for text animations. In this example TextPaths is used to animated UILabel value change.
62 |
63 | ### [`FlyingText`](https://github.com/malczak/TextPaths/tree/master/Samples/FlyingText)
64 |
65 | Another animation example showing how to animate multiline NSAttributedText.
66 |
67 | ## Screenshots
68 |
69 | 
70 |
71 |
75 |
76 |
80 |
81 |
85 |
86 |
90 |
91 |
95 |
--------------------------------------------------------------------------------
/Samples/FallingLabel/.gitignore:
--------------------------------------------------------------------------------
1 | Pods/
2 | Podfile.lock
3 | *.xcworkspace
4 | .DS_Store
5 |
6 | ### Xcode ###
7 | # Xcode
8 | #
9 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
10 |
11 | ## User settings
12 | xcuserdata/
13 |
14 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
15 | *.xcscmblueprint
16 | *.xccheckout
17 |
18 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
19 | build/
20 | DerivedData/
21 | *.moved-aside
22 | *.pbxuser
23 | !default.pbxuser
24 | *.mode1v3
25 | !default.mode1v3
26 | *.mode2v3
27 | !default.mode2v3
28 | *.perspectivev3
29 | !default.perspectivev3
30 |
31 | ## Xcode Patch
32 | *.xcodeproj/*
33 | !*.xcodeproj/project.pbxproj
34 | !*.xcodeproj/xcshareddata/
35 | !*.xcworkspace/contents.xcworkspacedata
36 | /*.gcno
37 |
38 | ### Xcode Patch ###
39 | **/xcshareddata/WorkspaceSettings.xcsettings
40 |
--------------------------------------------------------------------------------
/Samples/FallingLabel/FallingCounterLabel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FallingCounterLabel.swift
3 | // TextPaths
4 | //
5 | // Copyright © 2017 The Pirate Cat. All rights reserved.
6 | //
7 |
8 | import UIKit
9 | import TextPaths
10 |
11 | public class NumberPaths {
12 |
13 | public var font: UIFont
14 |
15 | public var alignment: NSTextAlignment
16 |
17 | public var textPath: TextPath
18 |
19 | public var unicodeMap: [UnicodeScalar:TextPathGlyph]
20 |
21 | public init?(WithFont font: UIFont, alignment: NSTextAlignment) {
22 | self.font = font
23 | self.alignment = alignment
24 |
25 | let par = NSMutableParagraphStyle()
26 | par.alignment = self.alignment
27 |
28 | let numbersString = NSMutableAttributedString(string: "0123456789", attributes: [
29 | NSAttributedString.Key.font: self.font
30 | ])
31 |
32 | let compositionSize = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
33 | guard let textPath = numbersString.getTextPath(InBounds: compositionSize) else {
34 | return nil
35 | }
36 |
37 | var paths = [UnicodeScalar:TextPathGlyph]()
38 | let unicodes = numbersString.string.unicodeScalars
39 | textPath.frames[0].enumerateGlyphs({ _, glyph in
40 | let unicode = unicodes[glyph.index]
41 | paths[unicode] = glyph
42 | })
43 |
44 | self.textPath = textPath
45 | self.unicodeMap = paths
46 | }
47 | }
48 |
49 | public class FallingCounterLabel {
50 | private struct TextDropInfo {
51 | var charPath: TextPathGlyph
52 | var modified: Bool
53 | init(charPath: TextPathGlyph, modified: Bool) {
54 | self.charPath = charPath
55 | self.modified = modified
56 | }
57 | }
58 |
59 | public weak var label: UILabel? {
60 | didSet {
61 | if let label = label {
62 | label.layer.borderColor = UIColor.black.cgColor
63 | label.layer.borderWidth = 1
64 | }
65 | }
66 | }
67 |
68 | public var numberPaths: NumberPaths?
69 |
70 | var layers = [CAShapeLayer]()
71 |
72 | var fieldLength = 2
73 |
74 | var duration = CGFloat(0.6)
75 |
76 | public var value = -1 {
77 | didSet {
78 | update(Animated: (oldValue != -1) && (oldValue != value))
79 | }
80 | }
81 |
82 | public init(WithLabel label: UILabel) {
83 | self.label = label
84 | }
85 |
86 | func update(Animated animated: Bool) {
87 | guard let numberPaths = numberPaths else {
88 | return
89 | }
90 |
91 | guard let label = label else {
92 | return
93 | }
94 | let attributes = [
95 | NSAttributedString.Key.font: numberPaths.font
96 | ]
97 | let formatString = String(format: "%%.0%dd", fieldLength)
98 | let numbersString = NSMutableAttributedString(string: String(format: formatString, value), attributes: attributes)
99 |
100 | if animated {
101 | let lastText = label.attributedText
102 | let transition = CATransition()
103 | transition.duration = 0.3
104 | transition.type = CATransitionType.fade
105 | label.layer.add(transition, forKey: "attributedText")
106 | label.attributedText = numbersString
107 |
108 | drop(FromText:lastText!, toText:numbersString)
109 | } else {
110 | label.attributedText = numbersString
111 | }
112 | }
113 |
114 | func drop(FromText fromText: NSAttributedString, toText: NSAttributedString) {
115 | for layer in layers {
116 | layer.removeFromSuperlayer();
117 | }
118 |
119 | guard let label = label, let numberPaths = numberPaths else {
120 | return
121 | }
122 |
123 | var paths = [TextDropInfo]()
124 | var pathBounds = CGRect.zero
125 | var fromItr = fromText.string.unicodeScalars.makeIterator()
126 | var toItr = toText.string.unicodeScalars.makeIterator()
127 | while let fromScalar = fromItr.next(), let toScalar = toItr.next() {
128 | guard let charPath = numberPaths.unicodeMap[fromScalar] else {
129 | return
130 | }
131 |
132 | pathBounds.size = CGSize(width: pathBounds.width + charPath.advance.width,
133 | height: max(pathBounds.height, charPath.path.boundingBoxOfPath.height))
134 | let info = TextDropInfo(charPath: charPath, modified: true)
135 | paths.append(info)
136 | }
137 |
138 | var i = 0, j = 0
139 |
140 | var frameOffset = label.bounds.origin
141 | frameOffset.x += (label.bounds.width - pathBounds.width) * 0.5
142 | frameOffset.y += (label.bounds.height - pathBounds.height) * 0.5
143 |
144 | let delays = [1.0, 0.0, 1.5, 4.5, 0.0, 0,0, 0,0, 0,0]
145 | var delay = 0.4
146 |
147 | func d2r(_ value: CGFloat) -> CGFloat {
148 | return value / 180.0 * CGFloat(Float.pi)
149 | }
150 |
151 | var advance = CGFloat(0.0)
152 | var index = 0
153 | for info in paths {
154 | let charPath = info.charPath
155 | let path = CAShapeLayer()
156 | let bounds = charPath.path.boundingBox
157 |
158 | func CT(_ transforms: CATransform3D...) -> CATransform3D {
159 | var T = CATransform3DMakeTranslation(bounds.width*0.5, bounds.height*0.5, 0.0)
160 | for transform in transforms {
161 | T = CATransform3DConcat(transform, T)
162 | }
163 | T = CATransform3DTranslate(T, -bounds.width*0.5, -bounds.height*0.5, 0.0)
164 | return T
165 | }
166 |
167 | if info.modified {
168 | path.path = charPath.path
169 | path.anchorPoint = CGPoint(x: 0.0, y: 0.0)
170 | path.position = CGPoint(x: frameOffset.x + advance, y: frameOffset.y)
171 | path.bounds = CGRect(origin: .zero, size: bounds.size)
172 | path.fillColor = label.textColor.cgColor
173 | path.transform = CATransform3DIdentity
174 | path.opacity = 1.0
175 | label.layer.addSublayer(path)
176 |
177 | let scaleAnim = CABasicAnimation(keyPath: "transform")
178 | scaleAnim.fromValue = CATransform3DIdentity
179 | scaleAnim.toValue = CT(CATransform3DMakeScale(2.0, 2.0, 1.0))
180 |
181 | var alfa = 20.0 + (5.0 - CGFloat(arc4random_uniform(100))/10.0)
182 | if arc4random_uniform(100)<50 {
183 | alfa *= -1.0
184 | }
185 |
186 | let shakeAnim = CAKeyframeAnimation(keyPath: "transform")
187 | shakeAnim.values = [
188 | CT(CATransform3DMakeRotation(d2r(0), 0, 0, 1), CATransform3DMakeScale(1.0, 1.0, 1)),
189 | CT(CATransform3DMakeRotation(d2r(0), 0, 0, 1), CATransform3DMakeScale(1.05, 1.05, 1)),
190 | CT(CATransform3DMakeRotation(d2r(-alfa), 0, 0, 1), CATransform3DMakeScale(0.8, 0.8, 1)),
191 | CT(CATransform3DMakeRotation(d2r(+alfa), 0, 0, 1), CATransform3DMakeScale(0.6, 0.6, 1)),
192 | CT(CATransform3DMakeRotation(d2r(0), 0, 0, 1), CATransform3DMakeScale(0.4, 0.4, 1))
193 | ]
194 | shakeAnim.keyTimes = [0.0, 0.05, 0.25, 0.5, 0.75]
195 | shakeAnim.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)
196 |
197 | let flyAnim = CABasicAnimation(keyPath: "position.y")
198 | flyAnim.isAdditive = true
199 | flyAnim.fromValue = 0.1
200 | flyAnim.toValue = (2.0 + 2.5 * CGFloat(arc4random_uniform(100))/100.0) * bounds.height
201 | flyAnim.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)
202 |
203 | let opacityAnim = CABasicAnimation(keyPath: "opacity")
204 | opacityAnim.duration = CFTimeInterval(duration)
205 | opacityAnim.beginTime = 0.2
206 | opacityAnim.fromValue = 1.0
207 | opacityAnim.toValue = 0.0
208 |
209 | let groupAnim = CAAnimationGroup()
210 | groupAnim.beginTime = CACurrentMediaTime()
211 | groupAnim.duration = CFTimeInterval(duration) + 0.2
212 | groupAnim.animations = [scaleAnim, shakeAnim, flyAnim, opacityAnim]
213 | groupAnim.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)
214 | groupAnim.fillMode = CAMediaTimingFillMode.forwards
215 | groupAnim.isRemovedOnCompletion = false
216 |
217 | path.removeAllAnimations()
218 | path.add(groupAnim, forKey: "animation")
219 | layers.append(path)
220 | }
221 |
222 | advance += charPath.advance.width
223 | index += 1
224 | }
225 | }
226 |
227 | }
228 |
--------------------------------------------------------------------------------
/Samples/FallingLabel/FallingLabel.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 324B5FF0234D2C90009FA70D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 324B5FEF234D2C90009FA70D /* AppDelegate.swift */; };
11 | 324B5FF2234D2C90009FA70D /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 324B5FF1234D2C90009FA70D /* SceneDelegate.swift */; };
12 | 324B5FF4234D2C90009FA70D /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 324B5FF3234D2C90009FA70D /* ViewController.swift */; };
13 | 324B5FF7234D2C90009FA70D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 324B5FF5234D2C90009FA70D /* Main.storyboard */; };
14 | 324B5FF9234D2C92009FA70D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 324B5FF8234D2C92009FA70D /* Assets.xcassets */; };
15 | 324B5FFC234D2C92009FA70D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 324B5FFA234D2C92009FA70D /* LaunchScreen.storyboard */; };
16 | 324B6004234D2DA9009FA70D /* FallingCounterLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 324B6003234D2DA9009FA70D /* FallingCounterLabel.swift */; };
17 | 5973E452FE3F4227A2934B69 /* libPods-FallingLabel.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 715C740E4515FE18A3B192EB /* libPods-FallingLabel.a */; };
18 | /* End PBXBuildFile section */
19 |
20 | /* Begin PBXFileReference section */
21 | 324B5FEC234D2C90009FA70D /* FallingLabel.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FallingLabel.app; sourceTree = BUILT_PRODUCTS_DIR; };
22 | 324B5FEF234D2C90009FA70D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
23 | 324B5FF1234D2C90009FA70D /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
24 | 324B5FF3234D2C90009FA70D /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
25 | 324B5FF6234D2C90009FA70D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
26 | 324B5FF8234D2C92009FA70D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
27 | 324B5FFB234D2C92009FA70D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
28 | 324B5FFD234D2C92009FA70D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
29 | 324B6003234D2DA9009FA70D /* FallingCounterLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FallingCounterLabel.swift; sourceTree = SOURCE_ROOT; };
30 | 3B0D6842C4DC8FFFA31FC4E5 /* Pods-FallingLabel.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FallingLabel.debug.xcconfig"; path = "Target Support Files/Pods-FallingLabel/Pods-FallingLabel.debug.xcconfig"; sourceTree = ""; };
31 | 715C740E4515FE18A3B192EB /* libPods-FallingLabel.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-FallingLabel.a"; sourceTree = BUILT_PRODUCTS_DIR; };
32 | 76EC65B8DA2539BB1EEB553E /* Pods-FallingLabel.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FallingLabel.release.xcconfig"; path = "Target Support Files/Pods-FallingLabel/Pods-FallingLabel.release.xcconfig"; sourceTree = ""; };
33 | /* End PBXFileReference section */
34 |
35 | /* Begin PBXFrameworksBuildPhase section */
36 | 324B5FE9234D2C90009FA70D /* Frameworks */ = {
37 | isa = PBXFrameworksBuildPhase;
38 | buildActionMask = 2147483647;
39 | files = (
40 | 5973E452FE3F4227A2934B69 /* libPods-FallingLabel.a in Frameworks */,
41 | );
42 | runOnlyForDeploymentPostprocessing = 0;
43 | };
44 | /* End PBXFrameworksBuildPhase section */
45 |
46 | /* Begin PBXGroup section */
47 | 324B5FE3234D2C90009FA70D = {
48 | isa = PBXGroup;
49 | children = (
50 | 324B5FEE234D2C90009FA70D /* FallingLabel */,
51 | 324B5FED234D2C90009FA70D /* Products */,
52 | 7CEA1D5A1EB379761EC9D9CC /* Pods */,
53 | D6C6D99D1AF8A1B990F28D7D /* Frameworks */,
54 | );
55 | sourceTree = "";
56 | };
57 | 324B5FED234D2C90009FA70D /* Products */ = {
58 | isa = PBXGroup;
59 | children = (
60 | 324B5FEC234D2C90009FA70D /* FallingLabel.app */,
61 | );
62 | name = Products;
63 | sourceTree = "";
64 | };
65 | 324B5FEE234D2C90009FA70D /* FallingLabel */ = {
66 | isa = PBXGroup;
67 | children = (
68 | 324B6003234D2DA9009FA70D /* FallingCounterLabel.swift */,
69 | 324B5FEF234D2C90009FA70D /* AppDelegate.swift */,
70 | 324B5FF1234D2C90009FA70D /* SceneDelegate.swift */,
71 | 324B5FF3234D2C90009FA70D /* ViewController.swift */,
72 | 324B5FF5234D2C90009FA70D /* Main.storyboard */,
73 | 324B5FF8234D2C92009FA70D /* Assets.xcassets */,
74 | 324B5FFA234D2C92009FA70D /* LaunchScreen.storyboard */,
75 | 324B5FFD234D2C92009FA70D /* Info.plist */,
76 | );
77 | path = FallingLabel;
78 | sourceTree = "";
79 | };
80 | 7CEA1D5A1EB379761EC9D9CC /* Pods */ = {
81 | isa = PBXGroup;
82 | children = (
83 | 3B0D6842C4DC8FFFA31FC4E5 /* Pods-FallingLabel.debug.xcconfig */,
84 | 76EC65B8DA2539BB1EEB553E /* Pods-FallingLabel.release.xcconfig */,
85 | );
86 | path = Pods;
87 | sourceTree = "";
88 | };
89 | D6C6D99D1AF8A1B990F28D7D /* Frameworks */ = {
90 | isa = PBXGroup;
91 | children = (
92 | 715C740E4515FE18A3B192EB /* libPods-FallingLabel.a */,
93 | );
94 | name = Frameworks;
95 | sourceTree = "";
96 | };
97 | /* End PBXGroup section */
98 |
99 | /* Begin PBXNativeTarget section */
100 | 324B5FEB234D2C90009FA70D /* FallingLabel */ = {
101 | isa = PBXNativeTarget;
102 | buildConfigurationList = 324B6000234D2C92009FA70D /* Build configuration list for PBXNativeTarget "FallingLabel" */;
103 | buildPhases = (
104 | 21614D6E4B8C752D862AD1AF /* [CP] Check Pods Manifest.lock */,
105 | 324B5FE8234D2C90009FA70D /* Sources */,
106 | 324B5FE9234D2C90009FA70D /* Frameworks */,
107 | 324B5FEA234D2C90009FA70D /* Resources */,
108 | );
109 | buildRules = (
110 | );
111 | dependencies = (
112 | );
113 | name = FallingLabel;
114 | productName = FallingLabel;
115 | productReference = 324B5FEC234D2C90009FA70D /* FallingLabel.app */;
116 | productType = "com.apple.product-type.application";
117 | };
118 | /* End PBXNativeTarget section */
119 |
120 | /* Begin PBXProject section */
121 | 324B5FE4234D2C90009FA70D /* Project object */ = {
122 | isa = PBXProject;
123 | attributes = {
124 | LastSwiftUpdateCheck = 1100;
125 | LastUpgradeCheck = 1100;
126 | TargetAttributes = {
127 | 324B5FEB234D2C90009FA70D = {
128 | CreatedOnToolsVersion = 11.0;
129 | };
130 | };
131 | };
132 | buildConfigurationList = 324B5FE7234D2C90009FA70D /* Build configuration list for PBXProject "FallingLabel" */;
133 | compatibilityVersion = "Xcode 9.3";
134 | developmentRegion = en;
135 | hasScannedForEncodings = 0;
136 | knownRegions = (
137 | en,
138 | Base,
139 | );
140 | mainGroup = 324B5FE3234D2C90009FA70D;
141 | productRefGroup = 324B5FED234D2C90009FA70D /* Products */;
142 | projectDirPath = "";
143 | projectRoot = "";
144 | targets = (
145 | 324B5FEB234D2C90009FA70D /* FallingLabel */,
146 | );
147 | };
148 | /* End PBXProject section */
149 |
150 | /* Begin PBXResourcesBuildPhase section */
151 | 324B5FEA234D2C90009FA70D /* Resources */ = {
152 | isa = PBXResourcesBuildPhase;
153 | buildActionMask = 2147483647;
154 | files = (
155 | 324B5FFC234D2C92009FA70D /* LaunchScreen.storyboard in Resources */,
156 | 324B5FF9234D2C92009FA70D /* Assets.xcassets in Resources */,
157 | 324B5FF7234D2C90009FA70D /* Main.storyboard in Resources */,
158 | );
159 | runOnlyForDeploymentPostprocessing = 0;
160 | };
161 | /* End PBXResourcesBuildPhase section */
162 |
163 | /* Begin PBXShellScriptBuildPhase section */
164 | 21614D6E4B8C752D862AD1AF /* [CP] Check Pods Manifest.lock */ = {
165 | isa = PBXShellScriptBuildPhase;
166 | buildActionMask = 2147483647;
167 | files = (
168 | );
169 | inputFileListPaths = (
170 | );
171 | inputPaths = (
172 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
173 | "${PODS_ROOT}/Manifest.lock",
174 | );
175 | name = "[CP] Check Pods Manifest.lock";
176 | outputFileListPaths = (
177 | );
178 | outputPaths = (
179 | "$(DERIVED_FILE_DIR)/Pods-FallingLabel-checkManifestLockResult.txt",
180 | );
181 | runOnlyForDeploymentPostprocessing = 0;
182 | shellPath = /bin/sh;
183 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
184 | showEnvVarsInLog = 0;
185 | };
186 | /* End PBXShellScriptBuildPhase section */
187 |
188 | /* Begin PBXSourcesBuildPhase section */
189 | 324B5FE8234D2C90009FA70D /* Sources */ = {
190 | isa = PBXSourcesBuildPhase;
191 | buildActionMask = 2147483647;
192 | files = (
193 | 324B5FF4234D2C90009FA70D /* ViewController.swift in Sources */,
194 | 324B6004234D2DA9009FA70D /* FallingCounterLabel.swift in Sources */,
195 | 324B5FF0234D2C90009FA70D /* AppDelegate.swift in Sources */,
196 | 324B5FF2234D2C90009FA70D /* SceneDelegate.swift in Sources */,
197 | );
198 | runOnlyForDeploymentPostprocessing = 0;
199 | };
200 | /* End PBXSourcesBuildPhase section */
201 |
202 | /* Begin PBXVariantGroup section */
203 | 324B5FF5234D2C90009FA70D /* Main.storyboard */ = {
204 | isa = PBXVariantGroup;
205 | children = (
206 | 324B5FF6234D2C90009FA70D /* Base */,
207 | );
208 | name = Main.storyboard;
209 | sourceTree = "";
210 | };
211 | 324B5FFA234D2C92009FA70D /* LaunchScreen.storyboard */ = {
212 | isa = PBXVariantGroup;
213 | children = (
214 | 324B5FFB234D2C92009FA70D /* Base */,
215 | );
216 | name = LaunchScreen.storyboard;
217 | sourceTree = "";
218 | };
219 | /* End PBXVariantGroup section */
220 |
221 | /* Begin XCBuildConfiguration section */
222 | 324B5FFE234D2C92009FA70D /* Debug */ = {
223 | isa = XCBuildConfiguration;
224 | buildSettings = {
225 | ALWAYS_SEARCH_USER_PATHS = NO;
226 | CLANG_ANALYZER_NONNULL = YES;
227 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
228 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
229 | CLANG_CXX_LIBRARY = "libc++";
230 | CLANG_ENABLE_MODULES = YES;
231 | CLANG_ENABLE_OBJC_ARC = YES;
232 | CLANG_ENABLE_OBJC_WEAK = YES;
233 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
234 | CLANG_WARN_BOOL_CONVERSION = YES;
235 | CLANG_WARN_COMMA = YES;
236 | CLANG_WARN_CONSTANT_CONVERSION = YES;
237 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
238 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
239 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
240 | CLANG_WARN_EMPTY_BODY = YES;
241 | CLANG_WARN_ENUM_CONVERSION = YES;
242 | CLANG_WARN_INFINITE_RECURSION = YES;
243 | CLANG_WARN_INT_CONVERSION = YES;
244 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
245 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
246 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
247 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
248 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
249 | CLANG_WARN_STRICT_PROTOTYPES = YES;
250 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
251 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
252 | CLANG_WARN_UNREACHABLE_CODE = YES;
253 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
254 | COPY_PHASE_STRIP = NO;
255 | DEBUG_INFORMATION_FORMAT = dwarf;
256 | ENABLE_STRICT_OBJC_MSGSEND = YES;
257 | ENABLE_TESTABILITY = YES;
258 | GCC_C_LANGUAGE_STANDARD = gnu11;
259 | GCC_DYNAMIC_NO_PIC = NO;
260 | GCC_NO_COMMON_BLOCKS = YES;
261 | GCC_OPTIMIZATION_LEVEL = 0;
262 | GCC_PREPROCESSOR_DEFINITIONS = (
263 | "DEBUG=1",
264 | "$(inherited)",
265 | );
266 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
267 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
268 | GCC_WARN_UNDECLARED_SELECTOR = YES;
269 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
270 | GCC_WARN_UNUSED_FUNCTION = YES;
271 | GCC_WARN_UNUSED_VARIABLE = YES;
272 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
273 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
274 | MTL_FAST_MATH = YES;
275 | ONLY_ACTIVE_ARCH = YES;
276 | SDKROOT = iphoneos;
277 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
278 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
279 | };
280 | name = Debug;
281 | };
282 | 324B5FFF234D2C92009FA70D /* Release */ = {
283 | isa = XCBuildConfiguration;
284 | buildSettings = {
285 | ALWAYS_SEARCH_USER_PATHS = NO;
286 | CLANG_ANALYZER_NONNULL = YES;
287 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
288 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
289 | CLANG_CXX_LIBRARY = "libc++";
290 | CLANG_ENABLE_MODULES = YES;
291 | CLANG_ENABLE_OBJC_ARC = YES;
292 | CLANG_ENABLE_OBJC_WEAK = YES;
293 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
294 | CLANG_WARN_BOOL_CONVERSION = YES;
295 | CLANG_WARN_COMMA = YES;
296 | CLANG_WARN_CONSTANT_CONVERSION = YES;
297 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
298 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
299 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
300 | CLANG_WARN_EMPTY_BODY = YES;
301 | CLANG_WARN_ENUM_CONVERSION = YES;
302 | CLANG_WARN_INFINITE_RECURSION = YES;
303 | CLANG_WARN_INT_CONVERSION = YES;
304 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
305 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
306 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
307 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
308 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
309 | CLANG_WARN_STRICT_PROTOTYPES = YES;
310 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
311 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
312 | CLANG_WARN_UNREACHABLE_CODE = YES;
313 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
314 | COPY_PHASE_STRIP = NO;
315 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
316 | ENABLE_NS_ASSERTIONS = NO;
317 | ENABLE_STRICT_OBJC_MSGSEND = YES;
318 | GCC_C_LANGUAGE_STANDARD = gnu11;
319 | GCC_NO_COMMON_BLOCKS = YES;
320 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
321 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
322 | GCC_WARN_UNDECLARED_SELECTOR = YES;
323 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
324 | GCC_WARN_UNUSED_FUNCTION = YES;
325 | GCC_WARN_UNUSED_VARIABLE = YES;
326 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
327 | MTL_ENABLE_DEBUG_INFO = NO;
328 | MTL_FAST_MATH = YES;
329 | SDKROOT = iphoneos;
330 | SWIFT_COMPILATION_MODE = wholemodule;
331 | SWIFT_OPTIMIZATION_LEVEL = "-O";
332 | VALIDATE_PRODUCT = YES;
333 | };
334 | name = Release;
335 | };
336 | 324B6001234D2C92009FA70D /* Debug */ = {
337 | isa = XCBuildConfiguration;
338 | baseConfigurationReference = 3B0D6842C4DC8FFFA31FC4E5 /* Pods-FallingLabel.debug.xcconfig */;
339 | buildSettings = {
340 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
341 | CODE_SIGN_STYLE = Automatic;
342 | DEVELOPMENT_TEAM = HT3GA3Z42X;
343 | INFOPLIST_FILE = FallingLabel/Info.plist;
344 | LD_RUNPATH_SEARCH_PATHS = (
345 | "$(inherited)",
346 | "@executable_path/Frameworks",
347 | );
348 | PRODUCT_BUNDLE_IDENTIFIER = cat.thepirate.FallingLabel;
349 | PRODUCT_NAME = "$(TARGET_NAME)";
350 | SWIFT_VERSION = 5.0;
351 | TARGETED_DEVICE_FAMILY = "1,2";
352 | };
353 | name = Debug;
354 | };
355 | 324B6002234D2C92009FA70D /* Release */ = {
356 | isa = XCBuildConfiguration;
357 | baseConfigurationReference = 76EC65B8DA2539BB1EEB553E /* Pods-FallingLabel.release.xcconfig */;
358 | buildSettings = {
359 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
360 | CODE_SIGN_STYLE = Automatic;
361 | DEVELOPMENT_TEAM = HT3GA3Z42X;
362 | INFOPLIST_FILE = FallingLabel/Info.plist;
363 | LD_RUNPATH_SEARCH_PATHS = (
364 | "$(inherited)",
365 | "@executable_path/Frameworks",
366 | );
367 | PRODUCT_BUNDLE_IDENTIFIER = cat.thepirate.FallingLabel;
368 | PRODUCT_NAME = "$(TARGET_NAME)";
369 | SWIFT_VERSION = 5.0;
370 | TARGETED_DEVICE_FAMILY = "1,2";
371 | };
372 | name = Release;
373 | };
374 | /* End XCBuildConfiguration section */
375 |
376 | /* Begin XCConfigurationList section */
377 | 324B5FE7234D2C90009FA70D /* Build configuration list for PBXProject "FallingLabel" */ = {
378 | isa = XCConfigurationList;
379 | buildConfigurations = (
380 | 324B5FFE234D2C92009FA70D /* Debug */,
381 | 324B5FFF234D2C92009FA70D /* Release */,
382 | );
383 | defaultConfigurationIsVisible = 0;
384 | defaultConfigurationName = Release;
385 | };
386 | 324B6000234D2C92009FA70D /* Build configuration list for PBXNativeTarget "FallingLabel" */ = {
387 | isa = XCConfigurationList;
388 | buildConfigurations = (
389 | 324B6001234D2C92009FA70D /* Debug */,
390 | 324B6002234D2C92009FA70D /* Release */,
391 | );
392 | defaultConfigurationIsVisible = 0;
393 | defaultConfigurationName = Release;
394 | };
395 | /* End XCConfigurationList section */
396 | };
397 | rootObject = 324B5FE4234D2C90009FA70D /* Project object */;
398 | }
399 |
--------------------------------------------------------------------------------
/Samples/FallingLabel/FallingLabel.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Samples/FallingLabel/FallingLabel.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Samples/FallingLabel/FallingLabel/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // FallingLabel
4 | //
5 | // Created by Mateusz Malczak on 08/10/2019.
6 | //
7 |
8 | import UIKit
9 |
10 | @UIApplicationMain
11 | class AppDelegate: UIResponder, UIApplicationDelegate {
12 |
13 |
14 |
15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
16 | // Override point for customization after application launch.
17 | return true
18 | }
19 |
20 | // MARK: UISceneSession Lifecycle
21 |
22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
23 | // Called when a new scene session is being created.
24 | // Use this method to select a configuration to create the new scene with.
25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
26 | }
27 |
28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
29 | // Called when the user discards a scene session.
30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
32 | }
33 |
34 |
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/Samples/FallingLabel/FallingLabel/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/Samples/FallingLabel/FallingLabel/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Samples/FallingLabel/FallingLabel/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 |
--------------------------------------------------------------------------------
/Samples/FallingLabel/FallingLabel/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
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 |
--------------------------------------------------------------------------------
/Samples/FallingLabel/FallingLabel/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIApplicationSceneManifest
24 |
25 | UIApplicationSupportsMultipleScenes
26 |
27 | UISceneConfigurations
28 |
29 | UIWindowSceneSessionRoleApplication
30 |
31 |
32 | UISceneConfigurationName
33 | Default Configuration
34 | UISceneDelegateClassName
35 | $(PRODUCT_MODULE_NAME).SceneDelegate
36 | UISceneStoryboardFile
37 | Main
38 |
39 |
40 |
41 |
42 | UILaunchStoryboardName
43 | LaunchScreen
44 | UIMainStoryboardFile
45 | Main
46 | UIRequiredDeviceCapabilities
47 |
48 | armv7
49 |
50 | UISupportedInterfaceOrientations
51 |
52 | UIInterfaceOrientationPortrait
53 | UIInterfaceOrientationLandscapeLeft
54 | UIInterfaceOrientationLandscapeRight
55 |
56 | UISupportedInterfaceOrientations~ipad
57 |
58 | UIInterfaceOrientationPortrait
59 | UIInterfaceOrientationPortraitUpsideDown
60 | UIInterfaceOrientationLandscapeLeft
61 | UIInterfaceOrientationLandscapeRight
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/Samples/FallingLabel/FallingLabel/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // FallingLabel
4 | //
5 | // Created by Mateusz Malczak on 08/10/2019.
6 | //
7 |
8 | import UIKit
9 |
10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
11 |
12 | var window: UIWindow?
13 |
14 |
15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
16 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
17 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
18 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
19 | guard let _ = (scene as? UIWindowScene) else { return }
20 | }
21 |
22 | func sceneDidDisconnect(_ scene: UIScene) {
23 | // Called as the scene is being released by the system.
24 | // This occurs shortly after the scene enters the background, or when its session is discarded.
25 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
26 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
27 | }
28 |
29 | func sceneDidBecomeActive(_ scene: UIScene) {
30 | // Called when the scene has moved from an inactive state to an active state.
31 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
32 | }
33 |
34 | func sceneWillResignActive(_ scene: UIScene) {
35 | // Called when the scene will move from an active state to an inactive state.
36 | // This may occur due to temporary interruptions (ex. an incoming phone call).
37 | }
38 |
39 | func sceneWillEnterForeground(_ scene: UIScene) {
40 | // Called as the scene transitions from the background to the foreground.
41 | // Use this method to undo the changes made on entering the background.
42 | }
43 |
44 | func sceneDidEnterBackground(_ scene: UIScene) {
45 | // Called as the scene transitions from the foreground to the background.
46 | // Use this method to save data, release shared resources, and store enough scene-specific state information
47 | // to restore the scene back to its current state.
48 | }
49 |
50 |
51 | }
52 |
53 |
--------------------------------------------------------------------------------
/Samples/FallingLabel/FallingLabel/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // FallingLabel
4 | //
5 | // Created by Mateusz Malczak on 08/10/2019.
6 | //
7 |
8 | import UIKit
9 |
10 | fileprivate struct Consts {
11 | static let fonts: [UIFont] = [
12 | UIFont(name: "AmericanTypewriter-Light", size: 44)!,
13 | UIFont(name: "BradleyHandITCTT-Bold", size: 44)!,
14 | UIFont.systemFont(ofSize: 120, weight: .regular),
15 | UIFont.systemFont(ofSize: 60, weight: .ultraLight),
16 | UIFont(name: "Zapfino", size: 33)!,
17 | UIFont.systemFont(ofSize: 40, weight: .bold),
18 | ]
19 | }
20 |
21 | class ViewController: UIViewController {
22 |
23 | @IBOutlet weak var label: UILabel!
24 |
25 | var counterDisplay: FallingCounterLabel?
26 |
27 | var timer: Timer?
28 |
29 | var currentFontIndex: Int = 0
30 |
31 | var index: Int = 0
32 |
33 |
34 | override func viewDidLoad() {
35 | super.viewDidLoad()
36 |
37 | let counter = FallingCounterLabel(WithLabel: label)
38 | counter.duration = 0.8
39 | counter.value = 0
40 | self.counterDisplay = counter
41 | }
42 |
43 | override func viewDidAppear(_ animated: Bool) {
44 | super.viewDidAppear(animated)
45 | setFont()
46 | startTimer()
47 | }
48 |
49 | override func viewDidDisappear(_ animated: Bool) {
50 | stopTimer()
51 | super.viewDidDisappear(animated)
52 | }
53 |
54 | func setFont(){
55 | guard let counter = counterDisplay else {
56 | return
57 | }
58 |
59 | counter.numberPaths = NumberPaths(WithFont: Consts.fonts[currentFontIndex],
60 | alignment: .center)
61 |
62 | }
63 |
64 | func startTimer() {
65 | stopTimer()
66 | index = 0
67 | let timer = Timer.scheduledTimer(timeInterval: 1.0,
68 | target: self,
69 | selector: #selector(onTimer),
70 | userInfo: nil,
71 | repeats: true)
72 | self.timer = timer
73 | }
74 |
75 | func stopTimer() {
76 | if let timer = timer {
77 | timer.invalidate()
78 | }
79 | timer = nil
80 | }
81 |
82 | @objc func onTimer(_ t: Any) {
83 | guard let counter = counterDisplay else {
84 | return
85 | }
86 | let value = counter.value
87 | counter.value = value + 1
88 |
89 | index += 1;
90 | if (index % 10) == 0 {
91 | currentFontIndex = (currentFontIndex + 1) % Consts.fonts.count
92 | setFont()
93 | }
94 | }
95 |
96 | }
97 |
98 |
--------------------------------------------------------------------------------
/Samples/FallingLabel/Podfile:
--------------------------------------------------------------------------------
1 | target 'FallingLabel'
2 | pod 'TextPaths', :path => './../..'
3 |
--------------------------------------------------------------------------------
/Samples/FlyingText/.gitignore:
--------------------------------------------------------------------------------
1 | Pods/
2 | Podfile.lock
3 | *.xcworkspace
4 | .DS_Store
5 |
6 | ### Xcode ###
7 | # Xcode
8 | #
9 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
10 |
11 | ## User settings
12 | xcuserdata/
13 |
14 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
15 | *.xcscmblueprint
16 | *.xccheckout
17 |
18 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
19 | build/
20 | DerivedData/
21 | *.moved-aside
22 | *.pbxuser
23 | !default.pbxuser
24 | *.mode1v3
25 | !default.mode1v3
26 | *.mode2v3
27 | !default.mode2v3
28 | *.perspectivev3
29 | !default.perspectivev3
30 |
31 | ## Xcode Patch
32 | *.xcodeproj/*
33 | !*.xcodeproj/project.pbxproj
34 | !*.xcodeproj/xcshareddata/
35 | !*.xcworkspace/contents.xcworkspacedata
36 | /*.gcno
37 |
38 | ### Xcode Patch ###
39 | **/xcshareddata/WorkspaceSettings.xcsettings
40 |
--------------------------------------------------------------------------------
/Samples/FlyingText/FlyingText.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 324B6012234D34D8009FA70D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 324B6011234D34D8009FA70D /* AppDelegate.swift */; };
11 | 324B6014234D34D8009FA70D /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 324B6013234D34D8009FA70D /* SceneDelegate.swift */; };
12 | 324B6016234D34D8009FA70D /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 324B6015234D34D8009FA70D /* ViewController.swift */; };
13 | 324B6019234D34D8009FA70D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 324B6017234D34D8009FA70D /* Main.storyboard */; };
14 | 324B601B234D34DB009FA70D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 324B601A234D34DB009FA70D /* Assets.xcassets */; };
15 | 324B601E234D34DB009FA70D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 324B601C234D34DB009FA70D /* LaunchScreen.storyboard */; };
16 | 784AA094C8646162F00FB3FB /* libPods-FlyingText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F5B1456C8EC7BD5839337514 /* libPods-FlyingText.a */; };
17 | /* End PBXBuildFile section */
18 |
19 | /* Begin PBXFileReference section */
20 | 182A1A11CC5C220406806CAB /* Pods-FlyingText.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FlyingText.debug.xcconfig"; path = "Target Support Files/Pods-FlyingText/Pods-FlyingText.debug.xcconfig"; sourceTree = ""; };
21 | 324B600E234D34D8009FA70D /* FlyingText.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FlyingText.app; sourceTree = BUILT_PRODUCTS_DIR; };
22 | 324B6011234D34D8009FA70D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
23 | 324B6013234D34D8009FA70D /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
24 | 324B6015234D34D8009FA70D /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
25 | 324B6018234D34D8009FA70D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
26 | 324B601A234D34DB009FA70D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
27 | 324B601D234D34DB009FA70D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
28 | 324B601F234D34DB009FA70D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
29 | 4B7D5DDDF312538D0565C98B /* Pods-FlyingText.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FlyingText.release.xcconfig"; path = "Target Support Files/Pods-FlyingText/Pods-FlyingText.release.xcconfig"; sourceTree = ""; };
30 | F5B1456C8EC7BD5839337514 /* libPods-FlyingText.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-FlyingText.a"; sourceTree = BUILT_PRODUCTS_DIR; };
31 | /* End PBXFileReference section */
32 |
33 | /* Begin PBXFrameworksBuildPhase section */
34 | 324B600B234D34D8009FA70D /* Frameworks */ = {
35 | isa = PBXFrameworksBuildPhase;
36 | buildActionMask = 2147483647;
37 | files = (
38 | 784AA094C8646162F00FB3FB /* libPods-FlyingText.a in Frameworks */,
39 | );
40 | runOnlyForDeploymentPostprocessing = 0;
41 | };
42 | /* End PBXFrameworksBuildPhase section */
43 |
44 | /* Begin PBXGroup section */
45 | 036FD4F9993CFE24B80B2227 /* Frameworks */ = {
46 | isa = PBXGroup;
47 | children = (
48 | F5B1456C8EC7BD5839337514 /* libPods-FlyingText.a */,
49 | );
50 | name = Frameworks;
51 | sourceTree = "";
52 | };
53 | 268C2AF0A4AF45506FF400BD /* Pods */ = {
54 | isa = PBXGroup;
55 | children = (
56 | 182A1A11CC5C220406806CAB /* Pods-FlyingText.debug.xcconfig */,
57 | 4B7D5DDDF312538D0565C98B /* Pods-FlyingText.release.xcconfig */,
58 | );
59 | name = Pods;
60 | path = Pods;
61 | sourceTree = "";
62 | };
63 | 324B6005234D34D8009FA70D = {
64 | isa = PBXGroup;
65 | children = (
66 | 324B6010234D34D8009FA70D /* FlyingText */,
67 | 324B600F234D34D8009FA70D /* Products */,
68 | 268C2AF0A4AF45506FF400BD /* Pods */,
69 | 036FD4F9993CFE24B80B2227 /* Frameworks */,
70 | );
71 | sourceTree = "";
72 | };
73 | 324B600F234D34D8009FA70D /* Products */ = {
74 | isa = PBXGroup;
75 | children = (
76 | 324B600E234D34D8009FA70D /* FlyingText.app */,
77 | );
78 | name = Products;
79 | sourceTree = "";
80 | };
81 | 324B6010234D34D8009FA70D /* FlyingText */ = {
82 | isa = PBXGroup;
83 | children = (
84 | 324B6011234D34D8009FA70D /* AppDelegate.swift */,
85 | 324B6013234D34D8009FA70D /* SceneDelegate.swift */,
86 | 324B6015234D34D8009FA70D /* ViewController.swift */,
87 | 324B6017234D34D8009FA70D /* Main.storyboard */,
88 | 324B601A234D34DB009FA70D /* Assets.xcassets */,
89 | 324B601C234D34DB009FA70D /* LaunchScreen.storyboard */,
90 | 324B601F234D34DB009FA70D /* Info.plist */,
91 | );
92 | path = FlyingText;
93 | sourceTree = "";
94 | };
95 | /* End PBXGroup section */
96 |
97 | /* Begin PBXNativeTarget section */
98 | 324B600D234D34D8009FA70D /* FlyingText */ = {
99 | isa = PBXNativeTarget;
100 | buildConfigurationList = 324B6022234D34DB009FA70D /* Build configuration list for PBXNativeTarget "FlyingText" */;
101 | buildPhases = (
102 | 67DC25BBB36DFEB45E706902 /* [CP] Check Pods Manifest.lock */,
103 | 324B600A234D34D8009FA70D /* Sources */,
104 | 324B600B234D34D8009FA70D /* Frameworks */,
105 | 324B600C234D34D8009FA70D /* Resources */,
106 | );
107 | buildRules = (
108 | );
109 | dependencies = (
110 | );
111 | name = FlyingText;
112 | productName = FlyingText;
113 | productReference = 324B600E234D34D8009FA70D /* FlyingText.app */;
114 | productType = "com.apple.product-type.application";
115 | };
116 | /* End PBXNativeTarget section */
117 |
118 | /* Begin PBXProject section */
119 | 324B6006234D34D8009FA70D /* Project object */ = {
120 | isa = PBXProject;
121 | attributes = {
122 | LastSwiftUpdateCheck = 1100;
123 | LastUpgradeCheck = 1100;
124 | TargetAttributes = {
125 | 324B600D234D34D8009FA70D = {
126 | CreatedOnToolsVersion = 11.0;
127 | };
128 | };
129 | };
130 | buildConfigurationList = 324B6009234D34D8009FA70D /* Build configuration list for PBXProject "FlyingText" */;
131 | compatibilityVersion = "Xcode 9.3";
132 | developmentRegion = en;
133 | hasScannedForEncodings = 0;
134 | knownRegions = (
135 | en,
136 | Base,
137 | );
138 | mainGroup = 324B6005234D34D8009FA70D;
139 | productRefGroup = 324B600F234D34D8009FA70D /* Products */;
140 | projectDirPath = "";
141 | projectRoot = "";
142 | targets = (
143 | 324B600D234D34D8009FA70D /* FlyingText */,
144 | );
145 | };
146 | /* End PBXProject section */
147 |
148 | /* Begin PBXResourcesBuildPhase section */
149 | 324B600C234D34D8009FA70D /* Resources */ = {
150 | isa = PBXResourcesBuildPhase;
151 | buildActionMask = 2147483647;
152 | files = (
153 | 324B601E234D34DB009FA70D /* LaunchScreen.storyboard in Resources */,
154 | 324B601B234D34DB009FA70D /* Assets.xcassets in Resources */,
155 | 324B6019234D34D8009FA70D /* Main.storyboard in Resources */,
156 | );
157 | runOnlyForDeploymentPostprocessing = 0;
158 | };
159 | /* End PBXResourcesBuildPhase section */
160 |
161 | /* Begin PBXShellScriptBuildPhase section */
162 | 67DC25BBB36DFEB45E706902 /* [CP] Check Pods Manifest.lock */ = {
163 | isa = PBXShellScriptBuildPhase;
164 | buildActionMask = 2147483647;
165 | files = (
166 | );
167 | inputFileListPaths = (
168 | );
169 | inputPaths = (
170 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
171 | "${PODS_ROOT}/Manifest.lock",
172 | );
173 | name = "[CP] Check Pods Manifest.lock";
174 | outputFileListPaths = (
175 | );
176 | outputPaths = (
177 | "$(DERIVED_FILE_DIR)/Pods-FlyingText-checkManifestLockResult.txt",
178 | );
179 | runOnlyForDeploymentPostprocessing = 0;
180 | shellPath = /bin/sh;
181 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
182 | showEnvVarsInLog = 0;
183 | };
184 | /* End PBXShellScriptBuildPhase section */
185 |
186 | /* Begin PBXSourcesBuildPhase section */
187 | 324B600A234D34D8009FA70D /* Sources */ = {
188 | isa = PBXSourcesBuildPhase;
189 | buildActionMask = 2147483647;
190 | files = (
191 | 324B6016234D34D8009FA70D /* ViewController.swift in Sources */,
192 | 324B6012234D34D8009FA70D /* AppDelegate.swift in Sources */,
193 | 324B6014234D34D8009FA70D /* SceneDelegate.swift in Sources */,
194 | );
195 | runOnlyForDeploymentPostprocessing = 0;
196 | };
197 | /* End PBXSourcesBuildPhase section */
198 |
199 | /* Begin PBXVariantGroup section */
200 | 324B6017234D34D8009FA70D /* Main.storyboard */ = {
201 | isa = PBXVariantGroup;
202 | children = (
203 | 324B6018234D34D8009FA70D /* Base */,
204 | );
205 | name = Main.storyboard;
206 | sourceTree = "";
207 | };
208 | 324B601C234D34DB009FA70D /* LaunchScreen.storyboard */ = {
209 | isa = PBXVariantGroup;
210 | children = (
211 | 324B601D234D34DB009FA70D /* Base */,
212 | );
213 | name = LaunchScreen.storyboard;
214 | sourceTree = "";
215 | };
216 | /* End PBXVariantGroup section */
217 |
218 | /* Begin XCBuildConfiguration section */
219 | 324B6020234D34DB009FA70D /* Debug */ = {
220 | isa = XCBuildConfiguration;
221 | buildSettings = {
222 | ALWAYS_SEARCH_USER_PATHS = NO;
223 | CLANG_ANALYZER_NONNULL = YES;
224 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
225 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
226 | CLANG_CXX_LIBRARY = "libc++";
227 | CLANG_ENABLE_MODULES = YES;
228 | CLANG_ENABLE_OBJC_ARC = YES;
229 | CLANG_ENABLE_OBJC_WEAK = YES;
230 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
231 | CLANG_WARN_BOOL_CONVERSION = YES;
232 | CLANG_WARN_COMMA = YES;
233 | CLANG_WARN_CONSTANT_CONVERSION = YES;
234 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
235 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
236 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
237 | CLANG_WARN_EMPTY_BODY = YES;
238 | CLANG_WARN_ENUM_CONVERSION = YES;
239 | CLANG_WARN_INFINITE_RECURSION = YES;
240 | CLANG_WARN_INT_CONVERSION = YES;
241 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
242 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
243 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
244 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
245 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
246 | CLANG_WARN_STRICT_PROTOTYPES = YES;
247 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
248 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
249 | CLANG_WARN_UNREACHABLE_CODE = YES;
250 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
251 | COPY_PHASE_STRIP = NO;
252 | DEBUG_INFORMATION_FORMAT = dwarf;
253 | ENABLE_STRICT_OBJC_MSGSEND = YES;
254 | ENABLE_TESTABILITY = YES;
255 | GCC_C_LANGUAGE_STANDARD = gnu11;
256 | GCC_DYNAMIC_NO_PIC = NO;
257 | GCC_NO_COMMON_BLOCKS = YES;
258 | GCC_OPTIMIZATION_LEVEL = 0;
259 | GCC_PREPROCESSOR_DEFINITIONS = (
260 | "DEBUG=1",
261 | "$(inherited)",
262 | );
263 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
264 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
265 | GCC_WARN_UNDECLARED_SELECTOR = YES;
266 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
267 | GCC_WARN_UNUSED_FUNCTION = YES;
268 | GCC_WARN_UNUSED_VARIABLE = YES;
269 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
270 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
271 | MTL_FAST_MATH = YES;
272 | ONLY_ACTIVE_ARCH = YES;
273 | SDKROOT = iphoneos;
274 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
275 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
276 | };
277 | name = Debug;
278 | };
279 | 324B6021234D34DB009FA70D /* Release */ = {
280 | isa = XCBuildConfiguration;
281 | buildSettings = {
282 | ALWAYS_SEARCH_USER_PATHS = NO;
283 | CLANG_ANALYZER_NONNULL = YES;
284 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
285 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
286 | CLANG_CXX_LIBRARY = "libc++";
287 | CLANG_ENABLE_MODULES = YES;
288 | CLANG_ENABLE_OBJC_ARC = YES;
289 | CLANG_ENABLE_OBJC_WEAK = YES;
290 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
291 | CLANG_WARN_BOOL_CONVERSION = YES;
292 | CLANG_WARN_COMMA = YES;
293 | CLANG_WARN_CONSTANT_CONVERSION = YES;
294 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
295 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
296 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
297 | CLANG_WARN_EMPTY_BODY = YES;
298 | CLANG_WARN_ENUM_CONVERSION = YES;
299 | CLANG_WARN_INFINITE_RECURSION = YES;
300 | CLANG_WARN_INT_CONVERSION = YES;
301 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
302 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
303 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
304 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
305 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
306 | CLANG_WARN_STRICT_PROTOTYPES = YES;
307 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
308 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
309 | CLANG_WARN_UNREACHABLE_CODE = YES;
310 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
311 | COPY_PHASE_STRIP = NO;
312 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
313 | ENABLE_NS_ASSERTIONS = NO;
314 | ENABLE_STRICT_OBJC_MSGSEND = YES;
315 | GCC_C_LANGUAGE_STANDARD = gnu11;
316 | GCC_NO_COMMON_BLOCKS = YES;
317 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
318 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
319 | GCC_WARN_UNDECLARED_SELECTOR = YES;
320 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
321 | GCC_WARN_UNUSED_FUNCTION = YES;
322 | GCC_WARN_UNUSED_VARIABLE = YES;
323 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
324 | MTL_ENABLE_DEBUG_INFO = NO;
325 | MTL_FAST_MATH = YES;
326 | SDKROOT = iphoneos;
327 | SWIFT_COMPILATION_MODE = wholemodule;
328 | SWIFT_OPTIMIZATION_LEVEL = "-O";
329 | VALIDATE_PRODUCT = YES;
330 | };
331 | name = Release;
332 | };
333 | 324B6023234D34DB009FA70D /* Debug */ = {
334 | isa = XCBuildConfiguration;
335 | baseConfigurationReference = 182A1A11CC5C220406806CAB /* Pods-FlyingText.debug.xcconfig */;
336 | buildSettings = {
337 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
338 | CODE_SIGN_STYLE = Automatic;
339 | DEVELOPMENT_TEAM = HT3GA3Z42X;
340 | INFOPLIST_FILE = FlyingText/Info.plist;
341 | LD_RUNPATH_SEARCH_PATHS = (
342 | "$(inherited)",
343 | "@executable_path/Frameworks",
344 | );
345 | PRODUCT_BUNDLE_IDENTIFIER = cat.thepirate.FlyingText;
346 | PRODUCT_NAME = "$(TARGET_NAME)";
347 | SWIFT_VERSION = 5.0;
348 | TARGETED_DEVICE_FAMILY = "1,2";
349 | };
350 | name = Debug;
351 | };
352 | 324B6024234D34DB009FA70D /* Release */ = {
353 | isa = XCBuildConfiguration;
354 | baseConfigurationReference = 4B7D5DDDF312538D0565C98B /* Pods-FlyingText.release.xcconfig */;
355 | buildSettings = {
356 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
357 | CODE_SIGN_STYLE = Automatic;
358 | DEVELOPMENT_TEAM = HT3GA3Z42X;
359 | INFOPLIST_FILE = FlyingText/Info.plist;
360 | LD_RUNPATH_SEARCH_PATHS = (
361 | "$(inherited)",
362 | "@executable_path/Frameworks",
363 | );
364 | PRODUCT_BUNDLE_IDENTIFIER = cat.thepirate.FlyingText;
365 | PRODUCT_NAME = "$(TARGET_NAME)";
366 | SWIFT_VERSION = 5.0;
367 | TARGETED_DEVICE_FAMILY = "1,2";
368 | };
369 | name = Release;
370 | };
371 | /* End XCBuildConfiguration section */
372 |
373 | /* Begin XCConfigurationList section */
374 | 324B6009234D34D8009FA70D /* Build configuration list for PBXProject "FlyingText" */ = {
375 | isa = XCConfigurationList;
376 | buildConfigurations = (
377 | 324B6020234D34DB009FA70D /* Debug */,
378 | 324B6021234D34DB009FA70D /* Release */,
379 | );
380 | defaultConfigurationIsVisible = 0;
381 | defaultConfigurationName = Release;
382 | };
383 | 324B6022234D34DB009FA70D /* Build configuration list for PBXNativeTarget "FlyingText" */ = {
384 | isa = XCConfigurationList;
385 | buildConfigurations = (
386 | 324B6023234D34DB009FA70D /* Debug */,
387 | 324B6024234D34DB009FA70D /* Release */,
388 | );
389 | defaultConfigurationIsVisible = 0;
390 | defaultConfigurationName = Release;
391 | };
392 | /* End XCConfigurationList section */
393 | };
394 | rootObject = 324B6006234D34D8009FA70D /* Project object */;
395 | }
396 |
--------------------------------------------------------------------------------
/Samples/FlyingText/FlyingText.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Samples/FlyingText/FlyingText.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Samples/FlyingText/FlyingText/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // FlyingText
4 | //
5 | // Created by Mateusz Malczak on 08/10/2019.
6 | //
7 |
8 | import UIKit
9 |
10 | @UIApplicationMain
11 | class AppDelegate: UIResponder, UIApplicationDelegate {
12 |
13 |
14 |
15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
16 | // Override point for customization after application launch.
17 | return true
18 | }
19 |
20 | // MARK: UISceneSession Lifecycle
21 |
22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
23 | // Called when a new scene session is being created.
24 | // Use this method to select a configuration to create the new scene with.
25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
26 | }
27 |
28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
29 | // Called when the user discards a scene session.
30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
32 | }
33 |
34 |
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/Samples/FlyingText/FlyingText/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/Samples/FlyingText/FlyingText/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Samples/FlyingText/FlyingText/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 |
--------------------------------------------------------------------------------
/Samples/FlyingText/FlyingText/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
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 |
--------------------------------------------------------------------------------
/Samples/FlyingText/FlyingText/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIApplicationSceneManifest
24 |
25 | UIApplicationSupportsMultipleScenes
26 |
27 | UISceneConfigurations
28 |
29 | UIWindowSceneSessionRoleApplication
30 |
31 |
32 | UISceneConfigurationName
33 | Default Configuration
34 | UISceneDelegateClassName
35 | $(PRODUCT_MODULE_NAME).SceneDelegate
36 | UISceneStoryboardFile
37 | Main
38 |
39 |
40 |
41 |
42 | UILaunchStoryboardName
43 | LaunchScreen
44 | UIMainStoryboardFile
45 | Main
46 | UIRequiredDeviceCapabilities
47 |
48 | armv7
49 |
50 | UISupportedInterfaceOrientations
51 |
52 | UIInterfaceOrientationPortrait
53 | UIInterfaceOrientationLandscapeLeft
54 | UIInterfaceOrientationLandscapeRight
55 |
56 | UISupportedInterfaceOrientations~ipad
57 |
58 | UIInterfaceOrientationPortrait
59 | UIInterfaceOrientationPortraitUpsideDown
60 | UIInterfaceOrientationLandscapeLeft
61 | UIInterfaceOrientationLandscapeRight
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/Samples/FlyingText/FlyingText/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // FlyingText
4 | //
5 | // Created by Mateusz Malczak on 08/10/2019.
6 | //
7 |
8 | import UIKit
9 |
10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
11 |
12 | var window: UIWindow?
13 |
14 |
15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
16 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
17 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
18 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
19 | guard let _ = (scene as? UIWindowScene) else { return }
20 | }
21 |
22 | func sceneDidDisconnect(_ scene: UIScene) {
23 | // Called as the scene is being released by the system.
24 | // This occurs shortly after the scene enters the background, or when its session is discarded.
25 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
26 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
27 | }
28 |
29 | func sceneDidBecomeActive(_ scene: UIScene) {
30 | // Called when the scene has moved from an inactive state to an active state.
31 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
32 | }
33 |
34 | func sceneWillResignActive(_ scene: UIScene) {
35 | // Called when the scene will move from an active state to an inactive state.
36 | // This may occur due to temporary interruptions (ex. an incoming phone call).
37 | }
38 |
39 | func sceneWillEnterForeground(_ scene: UIScene) {
40 | // Called as the scene transitions from the background to the foreground.
41 | // Use this method to undo the changes made on entering the background.
42 | }
43 |
44 | func sceneDidEnterBackground(_ scene: UIScene) {
45 | // Called as the scene transitions from the foreground to the background.
46 | // Use this method to save data, release shared resources, and store enough scene-specific state information
47 | // to restore the scene back to its current state.
48 | }
49 |
50 |
51 | }
52 |
53 |
--------------------------------------------------------------------------------
/Samples/FlyingText/FlyingText/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // FlyingText
4 | //
5 | // Created by Mateusz Malczak on 08/10/2019.
6 | //
7 |
8 | import UIKit
9 | import TextPaths
10 |
11 | class ViewController: UIViewController {
12 |
13 | @IBOutlet weak var label: UILabel!
14 |
15 | var textPath: TextPath?
16 |
17 | override func viewDidLoad() {
18 | super.viewDidLoad()
19 |
20 | let attributedString = createSlogoText()
21 | let bounds = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
22 | textPath = attributedString.getTextPath(InBounds: bounds,
23 | withAttributes: true,
24 | withPath: false)
25 | }
26 |
27 | override func viewDidAppear(_ animated: Bool) {
28 | super.viewDidAppear(animated)
29 | slogoFlyin()
30 | }
31 |
32 | func createSlogoText() -> NSAttributedString {
33 | let slogo = "what if\ntoday\nwas your\nlast day?"
34 | let slogoLines = slogo.uppercased().components(separatedBy: .newlines)
35 |
36 | let lineColors = [
37 | UIColor.blue
38 | ]
39 | let lineFonts = [
40 | UIFont.systemFont(ofSize: 22, weight: .light),
41 | UIFont.systemFont(ofSize: 42, weight: .ultraLight)
42 | ]
43 |
44 | let attrStr = NSMutableAttributedString()
45 | let para = NSMutableParagraphStyle()
46 | para.alignment = .center
47 | for (index, lineText) in slogoLines.enumerated() {
48 | let color = lineColors[index % lineColors.count]
49 | let font = lineFonts[index % lineFonts.count]
50 | let lineAttrs = [
51 | NSAttributedString.Key.foregroundColor: color,
52 | NSAttributedString.Key.font: font,
53 | NSAttributedString.Key.paragraphStyle: para
54 | ]
55 | let text = "\n\(lineText)"
56 | let attrLineStr = NSAttributedString(string: text, attributes: lineAttrs)
57 | attrStr.append(attrLineStr)
58 | }
59 |
60 | attrStr.append(NSAttributedString(string: "\n\n\n\nToday will never happen again,\nmake the most of it!", attributes: [
61 | NSAttributedString.Key.foregroundColor: lineColors[0],
62 | NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16, weight: .ultraLight),
63 | NSAttributedString.Key.paragraphStyle: para
64 | ]))
65 |
66 | return NSAttributedString(attributedString: attrStr)
67 | }
68 |
69 | func slogoFlyin() {
70 | guard let textPath = textPath else {
71 | return
72 | }
73 |
74 | // clean-up
75 | view
76 | .layer
77 | .sublayers?
78 | .compactMap({ $0 as? CAShapeLayer})
79 | .reversed()
80 | .forEach { $0.removeFromSuperlayer() }
81 |
82 | let frameOffset = CGPoint(x: (view.frame.width - textPath.composedBounds.width) * 0.5,
83 | y: (view.frame.height - textPath.composedBounds.height) * 0.5)
84 |
85 | let duration = 0.2
86 | var i = 0, j = 0
87 | let delays = [0.5, 0.0, 1.0, 1.5]
88 | var delay = 0.2
89 | var time = delay
90 |
91 | var lastAnimation: CAAnimation? = nil
92 |
93 | for line in textPath.frames[0].lines {
94 | line.enumerateGlyphs { [unowned self] _, charPath in
95 | let path = CAShapeLayer()
96 | let bounds = charPath.path.boundingBox
97 |
98 | var T = CATransform3DMakeTranslation(bounds.width*0.5, bounds.height*0.5, 0.0)
99 | T = CATransform3DScale(T, 4.0, 4.0, 1.0)
100 | T = CATransform3DTranslate(T, -bounds.width*0.5, -bounds.height*0.5, 0.0)
101 |
102 | let color = (charPath.attributes?[NSAttributedString.Key.foregroundColor] as? UIColor) ?? UIColor.black
103 | path.path = charPath.path
104 | path.anchorPoint = CGPoint(x: 0.0, y: 0.0)
105 | path.position = CGPoint(x: frameOffset.x + charPath.position.x, y: frameOffset.y + charPath.position.y)
106 | path.bounds = CGRect(origin: .zero, size: bounds.size)
107 | path.fillColor = color.cgColor
108 | path.transform = T
109 | path.opacity = 0.0
110 | self.view.layer.addSublayer(path)
111 |
112 | let scaleAnim = CABasicAnimation(keyPath: "transform")
113 | scaleAnim.fromValue = T
114 | scaleAnim.toValue = CATransform3DIdentity
115 | let opacityAnim = CABasicAnimation(keyPath: "opacity")
116 | opacityAnim.fromValue = 0.0
117 | opacityAnim.toValue = 1.0
118 | let groupAnim = CAAnimationGroup()
119 | groupAnim.beginTime = CACurrentMediaTime() + (duration * 0.3) * Double(j) + delay
120 | groupAnim.duration = duration
121 | groupAnim.animations = [scaleAnim, opacityAnim]
122 | groupAnim.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
123 | groupAnim.fillMode = CAMediaTimingFillMode.forwards
124 | groupAnim.isRemovedOnCompletion = false
125 |
126 | path.removeAllAnimations()
127 | path.add(groupAnim, forKey: "animation")
128 | self.view.layer.addSublayer(path)
129 |
130 | lastAnimation = opacityAnim
131 |
132 | j += 1
133 | time += duration * 0.62
134 | }
135 | delay += (i < delays.count) ? delays[i] : 0
136 | i += 1
137 | }
138 |
139 | if let anim = lastAnimation {
140 | anim.delegate = self
141 | }
142 |
143 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + DispatchTimeInterval.seconds(Int(round(time))), execute: { [unowned self] in
144 | // self.dismiss(animated: true, completion: nil)
145 | self.slogoFlyin()
146 | })
147 | }
148 |
149 | }
150 |
151 | extension ViewController: CAAnimationDelegate {
152 | func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
153 | anim.delegate = nil
154 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + DispatchTimeInterval.seconds(1), execute: { [unowned self] in
155 | // replay
156 | self.slogoFlyin()
157 | })
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/Samples/FlyingText/Podfile:
--------------------------------------------------------------------------------
1 | target 'FlyingText'
2 | pod 'TextPaths', :path => './../..'
3 |
--------------------------------------------------------------------------------
/Samples/TextDecompose/.gitignore:
--------------------------------------------------------------------------------
1 | Pods/
2 | Podfile.lock
3 | *.xcworkspace
4 | .DS_Store
5 |
6 | ### Xcode ###
7 | # Xcode
8 | #
9 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
10 |
11 | ## User settings
12 | xcuserdata/
13 |
14 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
15 | *.xcscmblueprint
16 | *.xccheckout
17 |
18 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
19 | build/
20 | DerivedData/
21 | *.moved-aside
22 | *.pbxuser
23 | !default.pbxuser
24 | *.mode1v3
25 | !default.mode1v3
26 | *.mode2v3
27 | !default.mode2v3
28 | *.perspectivev3
29 | !default.perspectivev3
30 |
31 | ## Xcode Patch
32 | *.xcodeproj/*
33 | !*.xcodeproj/project.pbxproj
34 | !*.xcodeproj/xcshareddata/
35 | !*.xcworkspace/contents.xcworkspacedata
36 | /*.gcno
37 |
38 | ### Xcode Patch ###
39 | **/xcshareddata/WorkspaceSettings.xcsettings
40 |
--------------------------------------------------------------------------------
/Samples/TextDecompose/Podfile:
--------------------------------------------------------------------------------
1 | target 'TextDecompose'
2 | pod 'TextPaths', :path => './../..'
3 |
--------------------------------------------------------------------------------
/Samples/TextDecompose/TextDecompose.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 15B95874AD6FD3794E204186 /* libPods-TextDecompose.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E666E8D120942706C134605B /* libPods-TextDecompose.a */; };
11 | 32039162234E4D7800449B7E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32039161234E4D7800449B7E /* AppDelegate.swift */; };
12 | 32039164234E4D7800449B7E /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32039163234E4D7800449B7E /* SceneDelegate.swift */; };
13 | 32039166234E4D7800449B7E /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32039165234E4D7800449B7E /* ViewController.swift */; };
14 | 32039169234E4D7800449B7E /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 32039167234E4D7800449B7E /* Main.storyboard */; };
15 | 3203916B234E4D7A00449B7E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3203916A234E4D7A00449B7E /* Assets.xcassets */; };
16 | 3203916E234E4D7A00449B7E /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3203916C234E4D7A00449B7E /* LaunchScreen.storyboard */; };
17 | /* End PBXBuildFile section */
18 |
19 | /* Begin PBXFileReference section */
20 | 3203915E234E4D7800449B7E /* TextDecompose.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TextDecompose.app; sourceTree = BUILT_PRODUCTS_DIR; };
21 | 32039161234E4D7800449B7E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
22 | 32039163234E4D7800449B7E /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
23 | 32039165234E4D7800449B7E /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
24 | 32039168234E4D7800449B7E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
25 | 3203916A234E4D7A00449B7E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
26 | 3203916D234E4D7A00449B7E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
27 | 3203916F234E4D7A00449B7E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
28 | 969F72B520AC69B352E1C2AE /* Pods-TextDecompose.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TextDecompose.debug.xcconfig"; path = "Target Support Files/Pods-TextDecompose/Pods-TextDecompose.debug.xcconfig"; sourceTree = ""; };
29 | A3098CBC0AB85AB6ACCF4626 /* Pods-TextDecompose.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TextDecompose.release.xcconfig"; path = "Target Support Files/Pods-TextDecompose/Pods-TextDecompose.release.xcconfig"; sourceTree = ""; };
30 | E666E8D120942706C134605B /* libPods-TextDecompose.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-TextDecompose.a"; sourceTree = BUILT_PRODUCTS_DIR; };
31 | /* End PBXFileReference section */
32 |
33 | /* Begin PBXFrameworksBuildPhase section */
34 | 3203915B234E4D7800449B7E /* Frameworks */ = {
35 | isa = PBXFrameworksBuildPhase;
36 | buildActionMask = 2147483647;
37 | files = (
38 | 15B95874AD6FD3794E204186 /* libPods-TextDecompose.a in Frameworks */,
39 | );
40 | runOnlyForDeploymentPostprocessing = 0;
41 | };
42 | /* End PBXFrameworksBuildPhase section */
43 |
44 | /* Begin PBXGroup section */
45 | 32039155234E4D7800449B7E = {
46 | isa = PBXGroup;
47 | children = (
48 | 32039160234E4D7800449B7E /* TextDecompose */,
49 | 3203915F234E4D7800449B7E /* Products */,
50 | 5ABD16147F39F43254D2897E /* Pods */,
51 | 97C68AA34C85D603CE6F4662 /* Frameworks */,
52 | );
53 | sourceTree = "";
54 | };
55 | 3203915F234E4D7800449B7E /* Products */ = {
56 | isa = PBXGroup;
57 | children = (
58 | 3203915E234E4D7800449B7E /* TextDecompose.app */,
59 | );
60 | name = Products;
61 | sourceTree = "";
62 | };
63 | 32039160234E4D7800449B7E /* TextDecompose */ = {
64 | isa = PBXGroup;
65 | children = (
66 | 32039161234E4D7800449B7E /* AppDelegate.swift */,
67 | 32039163234E4D7800449B7E /* SceneDelegate.swift */,
68 | 32039165234E4D7800449B7E /* ViewController.swift */,
69 | 32039167234E4D7800449B7E /* Main.storyboard */,
70 | 3203916A234E4D7A00449B7E /* Assets.xcassets */,
71 | 3203916C234E4D7A00449B7E /* LaunchScreen.storyboard */,
72 | 3203916F234E4D7A00449B7E /* Info.plist */,
73 | );
74 | path = TextDecompose;
75 | sourceTree = "";
76 | };
77 | 5ABD16147F39F43254D2897E /* Pods */ = {
78 | isa = PBXGroup;
79 | children = (
80 | 969F72B520AC69B352E1C2AE /* Pods-TextDecompose.debug.xcconfig */,
81 | A3098CBC0AB85AB6ACCF4626 /* Pods-TextDecompose.release.xcconfig */,
82 | );
83 | name = Pods;
84 | path = Pods;
85 | sourceTree = "";
86 | };
87 | 97C68AA34C85D603CE6F4662 /* Frameworks */ = {
88 | isa = PBXGroup;
89 | children = (
90 | E666E8D120942706C134605B /* libPods-TextDecompose.a */,
91 | );
92 | name = Frameworks;
93 | sourceTree = "";
94 | };
95 | /* End PBXGroup section */
96 |
97 | /* Begin PBXNativeTarget section */
98 | 3203915D234E4D7800449B7E /* TextDecompose */ = {
99 | isa = PBXNativeTarget;
100 | buildConfigurationList = 32039172234E4D7A00449B7E /* Build configuration list for PBXNativeTarget "TextDecompose" */;
101 | buildPhases = (
102 | B8D7EC7FC41E957F4FC16B66 /* [CP] Check Pods Manifest.lock */,
103 | 3203915A234E4D7800449B7E /* Sources */,
104 | 3203915B234E4D7800449B7E /* Frameworks */,
105 | 3203915C234E4D7800449B7E /* Resources */,
106 | );
107 | buildRules = (
108 | );
109 | dependencies = (
110 | );
111 | name = TextDecompose;
112 | productName = TextDecompose;
113 | productReference = 3203915E234E4D7800449B7E /* TextDecompose.app */;
114 | productType = "com.apple.product-type.application";
115 | };
116 | /* End PBXNativeTarget section */
117 |
118 | /* Begin PBXProject section */
119 | 32039156234E4D7800449B7E /* Project object */ = {
120 | isa = PBXProject;
121 | attributes = {
122 | LastSwiftUpdateCheck = 1100;
123 | LastUpgradeCheck = 1100;
124 | TargetAttributes = {
125 | 3203915D234E4D7800449B7E = {
126 | CreatedOnToolsVersion = 11.0;
127 | };
128 | };
129 | };
130 | buildConfigurationList = 32039159234E4D7800449B7E /* Build configuration list for PBXProject "TextDecompose" */;
131 | compatibilityVersion = "Xcode 9.3";
132 | developmentRegion = en;
133 | hasScannedForEncodings = 0;
134 | knownRegions = (
135 | en,
136 | Base,
137 | );
138 | mainGroup = 32039155234E4D7800449B7E;
139 | productRefGroup = 3203915F234E4D7800449B7E /* Products */;
140 | projectDirPath = "";
141 | projectRoot = "";
142 | targets = (
143 | 3203915D234E4D7800449B7E /* TextDecompose */,
144 | );
145 | };
146 | /* End PBXProject section */
147 |
148 | /* Begin PBXResourcesBuildPhase section */
149 | 3203915C234E4D7800449B7E /* Resources */ = {
150 | isa = PBXResourcesBuildPhase;
151 | buildActionMask = 2147483647;
152 | files = (
153 | 3203916E234E4D7A00449B7E /* LaunchScreen.storyboard in Resources */,
154 | 3203916B234E4D7A00449B7E /* Assets.xcassets in Resources */,
155 | 32039169234E4D7800449B7E /* Main.storyboard in Resources */,
156 | );
157 | runOnlyForDeploymentPostprocessing = 0;
158 | };
159 | /* End PBXResourcesBuildPhase section */
160 |
161 | /* Begin PBXShellScriptBuildPhase section */
162 | B8D7EC7FC41E957F4FC16B66 /* [CP] Check Pods Manifest.lock */ = {
163 | isa = PBXShellScriptBuildPhase;
164 | buildActionMask = 2147483647;
165 | files = (
166 | );
167 | inputFileListPaths = (
168 | );
169 | inputPaths = (
170 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
171 | "${PODS_ROOT}/Manifest.lock",
172 | );
173 | name = "[CP] Check Pods Manifest.lock";
174 | outputFileListPaths = (
175 | );
176 | outputPaths = (
177 | "$(DERIVED_FILE_DIR)/Pods-TextDecompose-checkManifestLockResult.txt",
178 | );
179 | runOnlyForDeploymentPostprocessing = 0;
180 | shellPath = /bin/sh;
181 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
182 | showEnvVarsInLog = 0;
183 | };
184 | /* End PBXShellScriptBuildPhase section */
185 |
186 | /* Begin PBXSourcesBuildPhase section */
187 | 3203915A234E4D7800449B7E /* Sources */ = {
188 | isa = PBXSourcesBuildPhase;
189 | buildActionMask = 2147483647;
190 | files = (
191 | 32039166234E4D7800449B7E /* ViewController.swift in Sources */,
192 | 32039162234E4D7800449B7E /* AppDelegate.swift in Sources */,
193 | 32039164234E4D7800449B7E /* SceneDelegate.swift in Sources */,
194 | );
195 | runOnlyForDeploymentPostprocessing = 0;
196 | };
197 | /* End PBXSourcesBuildPhase section */
198 |
199 | /* Begin PBXVariantGroup section */
200 | 32039167234E4D7800449B7E /* Main.storyboard */ = {
201 | isa = PBXVariantGroup;
202 | children = (
203 | 32039168234E4D7800449B7E /* Base */,
204 | );
205 | name = Main.storyboard;
206 | sourceTree = "";
207 | };
208 | 3203916C234E4D7A00449B7E /* LaunchScreen.storyboard */ = {
209 | isa = PBXVariantGroup;
210 | children = (
211 | 3203916D234E4D7A00449B7E /* Base */,
212 | );
213 | name = LaunchScreen.storyboard;
214 | sourceTree = "";
215 | };
216 | /* End PBXVariantGroup section */
217 |
218 | /* Begin XCBuildConfiguration section */
219 | 32039170234E4D7A00449B7E /* Debug */ = {
220 | isa = XCBuildConfiguration;
221 | buildSettings = {
222 | ALWAYS_SEARCH_USER_PATHS = NO;
223 | CLANG_ANALYZER_NONNULL = YES;
224 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
225 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
226 | CLANG_CXX_LIBRARY = "libc++";
227 | CLANG_ENABLE_MODULES = YES;
228 | CLANG_ENABLE_OBJC_ARC = YES;
229 | CLANG_ENABLE_OBJC_WEAK = YES;
230 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
231 | CLANG_WARN_BOOL_CONVERSION = YES;
232 | CLANG_WARN_COMMA = YES;
233 | CLANG_WARN_CONSTANT_CONVERSION = YES;
234 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
235 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
236 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
237 | CLANG_WARN_EMPTY_BODY = YES;
238 | CLANG_WARN_ENUM_CONVERSION = YES;
239 | CLANG_WARN_INFINITE_RECURSION = YES;
240 | CLANG_WARN_INT_CONVERSION = YES;
241 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
242 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
243 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
244 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
245 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
246 | CLANG_WARN_STRICT_PROTOTYPES = YES;
247 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
248 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
249 | CLANG_WARN_UNREACHABLE_CODE = YES;
250 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
251 | COPY_PHASE_STRIP = NO;
252 | DEBUG_INFORMATION_FORMAT = dwarf;
253 | ENABLE_STRICT_OBJC_MSGSEND = YES;
254 | ENABLE_TESTABILITY = YES;
255 | GCC_C_LANGUAGE_STANDARD = gnu11;
256 | GCC_DYNAMIC_NO_PIC = NO;
257 | GCC_NO_COMMON_BLOCKS = YES;
258 | GCC_OPTIMIZATION_LEVEL = 0;
259 | GCC_PREPROCESSOR_DEFINITIONS = (
260 | "DEBUG=1",
261 | "$(inherited)",
262 | );
263 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
264 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
265 | GCC_WARN_UNDECLARED_SELECTOR = YES;
266 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
267 | GCC_WARN_UNUSED_FUNCTION = YES;
268 | GCC_WARN_UNUSED_VARIABLE = YES;
269 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
270 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
271 | MTL_FAST_MATH = YES;
272 | ONLY_ACTIVE_ARCH = YES;
273 | SDKROOT = iphoneos;
274 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
275 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
276 | };
277 | name = Debug;
278 | };
279 | 32039171234E4D7A00449B7E /* Release */ = {
280 | isa = XCBuildConfiguration;
281 | buildSettings = {
282 | ALWAYS_SEARCH_USER_PATHS = NO;
283 | CLANG_ANALYZER_NONNULL = YES;
284 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
285 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
286 | CLANG_CXX_LIBRARY = "libc++";
287 | CLANG_ENABLE_MODULES = YES;
288 | CLANG_ENABLE_OBJC_ARC = YES;
289 | CLANG_ENABLE_OBJC_WEAK = YES;
290 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
291 | CLANG_WARN_BOOL_CONVERSION = YES;
292 | CLANG_WARN_COMMA = YES;
293 | CLANG_WARN_CONSTANT_CONVERSION = YES;
294 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
295 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
296 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
297 | CLANG_WARN_EMPTY_BODY = YES;
298 | CLANG_WARN_ENUM_CONVERSION = YES;
299 | CLANG_WARN_INFINITE_RECURSION = YES;
300 | CLANG_WARN_INT_CONVERSION = YES;
301 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
302 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
303 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
304 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
305 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
306 | CLANG_WARN_STRICT_PROTOTYPES = YES;
307 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
308 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
309 | CLANG_WARN_UNREACHABLE_CODE = YES;
310 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
311 | COPY_PHASE_STRIP = NO;
312 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
313 | ENABLE_NS_ASSERTIONS = NO;
314 | ENABLE_STRICT_OBJC_MSGSEND = YES;
315 | GCC_C_LANGUAGE_STANDARD = gnu11;
316 | GCC_NO_COMMON_BLOCKS = YES;
317 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
318 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
319 | GCC_WARN_UNDECLARED_SELECTOR = YES;
320 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
321 | GCC_WARN_UNUSED_FUNCTION = YES;
322 | GCC_WARN_UNUSED_VARIABLE = YES;
323 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
324 | MTL_ENABLE_DEBUG_INFO = NO;
325 | MTL_FAST_MATH = YES;
326 | SDKROOT = iphoneos;
327 | SWIFT_COMPILATION_MODE = wholemodule;
328 | SWIFT_OPTIMIZATION_LEVEL = "-O";
329 | VALIDATE_PRODUCT = YES;
330 | };
331 | name = Release;
332 | };
333 | 32039173234E4D7A00449B7E /* Debug */ = {
334 | isa = XCBuildConfiguration;
335 | baseConfigurationReference = 969F72B520AC69B352E1C2AE /* Pods-TextDecompose.debug.xcconfig */;
336 | buildSettings = {
337 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
338 | CODE_SIGN_STYLE = Automatic;
339 | DEVELOPMENT_TEAM = HT3GA3Z42X;
340 | INFOPLIST_FILE = TextDecompose/Info.plist;
341 | LD_RUNPATH_SEARCH_PATHS = (
342 | "$(inherited)",
343 | "@executable_path/Frameworks",
344 | );
345 | PRODUCT_BUNDLE_IDENTIFIER = cat.thepirate.TextDecompose;
346 | PRODUCT_NAME = "$(TARGET_NAME)";
347 | SWIFT_VERSION = 5.0;
348 | TARGETED_DEVICE_FAMILY = "1,2";
349 | };
350 | name = Debug;
351 | };
352 | 32039174234E4D7A00449B7E /* Release */ = {
353 | isa = XCBuildConfiguration;
354 | baseConfigurationReference = A3098CBC0AB85AB6ACCF4626 /* Pods-TextDecompose.release.xcconfig */;
355 | buildSettings = {
356 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
357 | CODE_SIGN_STYLE = Automatic;
358 | DEVELOPMENT_TEAM = HT3GA3Z42X;
359 | INFOPLIST_FILE = TextDecompose/Info.plist;
360 | LD_RUNPATH_SEARCH_PATHS = (
361 | "$(inherited)",
362 | "@executable_path/Frameworks",
363 | );
364 | PRODUCT_BUNDLE_IDENTIFIER = cat.thepirate.TextDecompose;
365 | PRODUCT_NAME = "$(TARGET_NAME)";
366 | SWIFT_VERSION = 5.0;
367 | TARGETED_DEVICE_FAMILY = "1,2";
368 | };
369 | name = Release;
370 | };
371 | /* End XCBuildConfiguration section */
372 |
373 | /* Begin XCConfigurationList section */
374 | 32039159234E4D7800449B7E /* Build configuration list for PBXProject "TextDecompose" */ = {
375 | isa = XCConfigurationList;
376 | buildConfigurations = (
377 | 32039170234E4D7A00449B7E /* Debug */,
378 | 32039171234E4D7A00449B7E /* Release */,
379 | );
380 | defaultConfigurationIsVisible = 0;
381 | defaultConfigurationName = Release;
382 | };
383 | 32039172234E4D7A00449B7E /* Build configuration list for PBXNativeTarget "TextDecompose" */ = {
384 | isa = XCConfigurationList;
385 | buildConfigurations = (
386 | 32039173234E4D7A00449B7E /* Debug */,
387 | 32039174234E4D7A00449B7E /* Release */,
388 | );
389 | defaultConfigurationIsVisible = 0;
390 | defaultConfigurationName = Release;
391 | };
392 | /* End XCConfigurationList section */
393 | };
394 | rootObject = 32039156234E4D7800449B7E /* Project object */;
395 | }
396 |
--------------------------------------------------------------------------------
/Samples/TextDecompose/TextDecompose.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Samples/TextDecompose/TextDecompose.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Samples/TextDecompose/TextDecompose/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // TextDecompose
4 | //
5 | // Created by Mateusz Malczak on 09/10/2019.
6 | //
7 |
8 | import UIKit
9 |
10 | @UIApplicationMain
11 | class AppDelegate: UIResponder, UIApplicationDelegate {
12 |
13 |
14 |
15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
16 | // Override point for customization after application launch.
17 | return true
18 | }
19 |
20 | // MARK: UISceneSession Lifecycle
21 |
22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
23 | // Called when a new scene session is being created.
24 | // Use this method to select a configuration to create the new scene with.
25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
26 | }
27 |
28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
29 | // Called when the user discards a scene session.
30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
32 | }
33 |
34 |
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/Samples/TextDecompose/TextDecompose/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/Samples/TextDecompose/TextDecompose/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Samples/TextDecompose/TextDecompose/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 |
--------------------------------------------------------------------------------
/Samples/TextDecompose/TextDecompose/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 | Lorem Ipsum is simply dummy
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 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
115 |
121 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
--------------------------------------------------------------------------------
/Samples/TextDecompose/TextDecompose/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIApplicationSceneManifest
24 |
25 | UIApplicationSupportsMultipleScenes
26 |
27 | UISceneConfigurations
28 |
29 | UIWindowSceneSessionRoleApplication
30 |
31 |
32 | UISceneConfigurationName
33 | Default Configuration
34 | UISceneDelegateClassName
35 | $(PRODUCT_MODULE_NAME).SceneDelegate
36 | UISceneStoryboardFile
37 | Main
38 |
39 |
40 |
41 |
42 | UILaunchStoryboardName
43 | LaunchScreen
44 | UIMainStoryboardFile
45 | Main
46 | UIRequiredDeviceCapabilities
47 |
48 | armv7
49 |
50 | UISupportedInterfaceOrientations
51 |
52 | UIInterfaceOrientationPortrait
53 | UIInterfaceOrientationLandscapeLeft
54 | UIInterfaceOrientationLandscapeRight
55 |
56 | UISupportedInterfaceOrientations~ipad
57 |
58 | UIInterfaceOrientationPortrait
59 | UIInterfaceOrientationPortraitUpsideDown
60 | UIInterfaceOrientationLandscapeLeft
61 | UIInterfaceOrientationLandscapeRight
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/Samples/TextDecompose/TextDecompose/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // TextDecompose
4 | //
5 | // Created by Mateusz Malczak on 09/10/2019.
6 | //
7 |
8 | import UIKit
9 |
10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
11 |
12 | var window: UIWindow?
13 |
14 |
15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
16 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
17 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
18 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
19 | guard let _ = (scene as? UIWindowScene) else { return }
20 | }
21 |
22 | func sceneDidDisconnect(_ scene: UIScene) {
23 | // Called as the scene is being released by the system.
24 | // This occurs shortly after the scene enters the background, or when its session is discarded.
25 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
26 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
27 | }
28 |
29 | func sceneDidBecomeActive(_ scene: UIScene) {
30 | // Called when the scene has moved from an inactive state to an active state.
31 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
32 | }
33 |
34 | func sceneWillResignActive(_ scene: UIScene) {
35 | // Called when the scene will move from an active state to an inactive state.
36 | // This may occur due to temporary interruptions (ex. an incoming phone call).
37 | }
38 |
39 | func sceneWillEnterForeground(_ scene: UIScene) {
40 | // Called as the scene transitions from the background to the foreground.
41 | // Use this method to undo the changes made on entering the background.
42 | }
43 |
44 | func sceneDidEnterBackground(_ scene: UIScene) {
45 | // Called as the scene transitions from the foreground to the background.
46 | // Use this method to save data, release shared resources, and store enough scene-specific state information
47 | // to restore the scene back to its current state.
48 | }
49 |
50 |
51 | }
52 |
53 |
--------------------------------------------------------------------------------
/Samples/TextDecompose/TextDecompose/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // TextDecompose
4 | //
5 | // Created by Mateusz Malczak on 09/10/2019.
6 | //
7 |
8 | import UIKit
9 | import TextPaths
10 |
11 | class ViewController: UIViewController {
12 |
13 | @IBOutlet weak var textView: UITextView!
14 |
15 | @IBOutlet weak var container: UIView!
16 |
17 | var drawLineBounds = true
18 |
19 | var drawTextLineBounds = true
20 |
21 | var drawGlyphsBounds = true
22 |
23 | @IBAction func switchLineLeading(_ sender: UISwitch) {
24 | drawLineBounds = !drawLineBounds
25 | decomposeText()
26 | }
27 |
28 | @IBAction func switchLineText(_ sender: UISwitch) {
29 | drawTextLineBounds = !drawTextLineBounds
30 | decomposeText()
31 | }
32 |
33 | @IBAction func switchGlyphs(_ sender: UISwitch) {
34 | drawGlyphsBounds = !drawGlyphsBounds
35 | decomposeText()
36 | }
37 |
38 | override func viewDidLoad() {
39 | super.viewDidLoad()
40 | decomposeText()
41 | }
42 |
43 | func decomposeText() {
44 | let hardcodedTextViewMargins: CGFloat = 12
45 | let bounds = CGSize(width: textView.frame.width - hardcodedTextViewMargins,
46 | height: CGFloat.greatestFiniteMagnitude)
47 | guard let textPaths = textView.attributedText.getTextPath(InBounds: bounds,
48 | withAttributes: true,
49 | withPath: true) else {
50 | return
51 | }
52 |
53 | let textBounds = textPaths.composedBounds
54 |
55 | let offset = CGPoint(
56 | x: textBounds.width < container.bounds.width ? (container.bounds.width - textBounds.width) * 0.5 : 0,
57 | y: textBounds.height < container.bounds.height ? (container.bounds.height - textBounds.height) * 0.5 : 0
58 | )
59 |
60 | let layer = container.layer
61 |
62 | // cleanup
63 | layer
64 | .sublayers?
65 | .reversed()
66 | .forEach { $0.removeFromSuperlayer() }
67 |
68 | let container = CAShapeLayer()
69 | container.bounds = textBounds
70 | container.anchorPoint = .zero
71 | container.position = offset
72 | layer.addSublayer(container);
73 |
74 | // draw composed text path
75 | let textShape = CAShapeLayer()
76 | textShape.anchorPoint = .zero
77 | textShape.bounds = textBounds
78 | textShape.path = textPaths.composedPath!
79 | textShape.fillColor = UIColor.darkText.cgColor
80 | container.addSublayer(textShape)
81 |
82 | for frame in textPaths.frames {
83 | container.addSublayer(rect(frame.path.boundingBoxOfPath, .red))
84 |
85 | var lineOffsetY: CGFloat = 0.0
86 |
87 | for line in frame.lines {
88 | let lineBounds = line.lineBounds
89 | let textBounds = line.textBounds
90 |
91 | if drawLineBounds {
92 | let shape = rect(lineBounds, .blue)
93 | shape.position = CGPoint(x: lineBounds.origin.x,
94 | y: lineOffsetY)
95 | container.addSublayer(shape)
96 | }
97 |
98 | if drawTextLineBounds {
99 | let textShape = rect(textBounds, .purple)
100 | textShape.position = CGPoint(x: lineBounds.origin.x + textBounds.origin.x,
101 | y: lineOffsetY + lineBounds.origin.x)
102 | container.addSublayer(textShape)
103 | }
104 |
105 | if drawGlyphsBounds {
106 | line.enumerateGlyphs { (_, glyph) in
107 | let glyphBounds = glyph.path.boundingBoxOfPath
108 | let bounds = CGRect(origin: glyph.position, size: glyphBounds.size)
109 | let shape = rect(bounds, .green)
110 | shape.position = CGPoint(x: glyph.position.x,
111 | y: glyph.position.y)
112 | container.addSublayer(shape)
113 | }
114 | }
115 |
116 | // move to next line (we keep line bounds in own coordinate space)
117 | lineOffsetY = lineOffsetY + lineBounds.height
118 | }
119 | }
120 |
121 | }
122 |
123 | func rect(_ rect: CGRect, _ color: UIColor) -> CAShapeLayer {
124 | let shape = CAShapeLayer()
125 | let bounds = CGRect(origin: .zero, size: rect.size)
126 | let origin = rect.origin
127 | shape.bounds = bounds
128 | shape.strokeColor = color.cgColor
129 | shape.fillColor = nil
130 | shape.anchorPoint = .zero
131 | shape.path = UIBezierPath(rect: bounds).cgPath
132 | shape.position = origin
133 | return shape
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/Samples/TextToSVG/.gitignore:
--------------------------------------------------------------------------------
1 | Pods/
2 | Podfile.lock
3 | *.xcworkspace
4 | .DS_Store
5 |
6 | ### Xcode ###
7 | # Xcode
8 | #
9 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
10 |
11 | ## User settings
12 | xcuserdata/
13 |
14 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
15 | *.xcscmblueprint
16 | *.xccheckout
17 |
18 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
19 | build/
20 | DerivedData/
21 | *.moved-aside
22 | *.pbxuser
23 | !default.pbxuser
24 | *.mode1v3
25 | !default.mode1v3
26 | *.mode2v3
27 | !default.mode2v3
28 | *.perspectivev3
29 | !default.perspectivev3
30 |
31 | ## Xcode Patch
32 | *.xcodeproj/*
33 | !*.xcodeproj/project.pbxproj
34 | !*.xcodeproj/xcshareddata/
35 | !*.xcworkspace/contents.xcworkspacedata
36 | /*.gcno
37 |
38 | ### Xcode Patch ###
39 | **/xcshareddata/WorkspaceSettings.xcsettings
40 |
--------------------------------------------------------------------------------
/Samples/TextToSVG/Podfile:
--------------------------------------------------------------------------------
1 | target 'TextToSVG'
2 | pod 'TextPaths', :path => './../..'
3 |
--------------------------------------------------------------------------------
/Samples/TextToSVG/TextToSVG.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 324B6032234D3C9F009FA70D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 324B6031234D3C9F009FA70D /* AppDelegate.swift */; };
11 | 324B6034234D3C9F009FA70D /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 324B6033234D3C9F009FA70D /* SceneDelegate.swift */; };
12 | 324B6036234D3C9F009FA70D /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 324B6035234D3C9F009FA70D /* ViewController.swift */; };
13 | 324B6039234D3C9F009FA70D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 324B6037234D3C9F009FA70D /* Main.storyboard */; };
14 | 324B603B234D3CA0009FA70D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 324B603A234D3CA0009FA70D /* Assets.xcassets */; };
15 | 324B603E234D3CA0009FA70D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 324B603C234D3CA0009FA70D /* LaunchScreen.storyboard */; };
16 | 8BA963CE2B654EAD49D1C075 /* libPods-TextToSVG.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FCD469CC2E5397EEEA52197 /* libPods-TextToSVG.a */; };
17 | /* End PBXBuildFile section */
18 |
19 | /* Begin PBXFileReference section */
20 | 10508FD6454EC7889AD3B05A /* Pods-TextToSVG.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TextToSVG.release.xcconfig"; path = "Target Support Files/Pods-TextToSVG/Pods-TextToSVG.release.xcconfig"; sourceTree = ""; };
21 | 324B602E234D3C9F009FA70D /* TextToSVG.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TextToSVG.app; sourceTree = BUILT_PRODUCTS_DIR; };
22 | 324B6031234D3C9F009FA70D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
23 | 324B6033234D3C9F009FA70D /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
24 | 324B6035234D3C9F009FA70D /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
25 | 324B6038234D3C9F009FA70D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
26 | 324B603A234D3CA0009FA70D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
27 | 324B603D234D3CA0009FA70D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
28 | 324B603F234D3CA0009FA70D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
29 | 7FCD469CC2E5397EEEA52197 /* libPods-TextToSVG.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-TextToSVG.a"; sourceTree = BUILT_PRODUCTS_DIR; };
30 | E6FED3283B3AF78AB183D784 /* Pods-TextToSVG.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TextToSVG.debug.xcconfig"; path = "Target Support Files/Pods-TextToSVG/Pods-TextToSVG.debug.xcconfig"; sourceTree = ""; };
31 | /* End PBXFileReference section */
32 |
33 | /* Begin PBXFrameworksBuildPhase section */
34 | 324B602B234D3C9F009FA70D /* Frameworks */ = {
35 | isa = PBXFrameworksBuildPhase;
36 | buildActionMask = 2147483647;
37 | files = (
38 | 8BA963CE2B654EAD49D1C075 /* libPods-TextToSVG.a in Frameworks */,
39 | );
40 | runOnlyForDeploymentPostprocessing = 0;
41 | };
42 | /* End PBXFrameworksBuildPhase section */
43 |
44 | /* Begin PBXGroup section */
45 | 324B6025234D3C9F009FA70D = {
46 | isa = PBXGroup;
47 | children = (
48 | 324B6030234D3C9F009FA70D /* TextToSVG */,
49 | 324B602F234D3C9F009FA70D /* Products */,
50 | 37CE83FF7C8A5A86D1C30D90 /* Pods */,
51 | 514369197E1D2FCF5CFAEBA2 /* Frameworks */,
52 | );
53 | sourceTree = "";
54 | };
55 | 324B602F234D3C9F009FA70D /* Products */ = {
56 | isa = PBXGroup;
57 | children = (
58 | 324B602E234D3C9F009FA70D /* TextToSVG.app */,
59 | );
60 | name = Products;
61 | sourceTree = "";
62 | };
63 | 324B6030234D3C9F009FA70D /* TextToSVG */ = {
64 | isa = PBXGroup;
65 | children = (
66 | 324B6031234D3C9F009FA70D /* AppDelegate.swift */,
67 | 324B6033234D3C9F009FA70D /* SceneDelegate.swift */,
68 | 324B6035234D3C9F009FA70D /* ViewController.swift */,
69 | 324B6037234D3C9F009FA70D /* Main.storyboard */,
70 | 324B603A234D3CA0009FA70D /* Assets.xcassets */,
71 | 324B603C234D3CA0009FA70D /* LaunchScreen.storyboard */,
72 | 324B603F234D3CA0009FA70D /* Info.plist */,
73 | );
74 | path = TextToSVG;
75 | sourceTree = "";
76 | };
77 | 37CE83FF7C8A5A86D1C30D90 /* Pods */ = {
78 | isa = PBXGroup;
79 | children = (
80 | E6FED3283B3AF78AB183D784 /* Pods-TextToSVG.debug.xcconfig */,
81 | 10508FD6454EC7889AD3B05A /* Pods-TextToSVG.release.xcconfig */,
82 | );
83 | name = Pods;
84 | path = Pods;
85 | sourceTree = "";
86 | };
87 | 514369197E1D2FCF5CFAEBA2 /* Frameworks */ = {
88 | isa = PBXGroup;
89 | children = (
90 | 7FCD469CC2E5397EEEA52197 /* libPods-TextToSVG.a */,
91 | );
92 | name = Frameworks;
93 | sourceTree = "";
94 | };
95 | /* End PBXGroup section */
96 |
97 | /* Begin PBXNativeTarget section */
98 | 324B602D234D3C9F009FA70D /* TextToSVG */ = {
99 | isa = PBXNativeTarget;
100 | buildConfigurationList = 324B6042234D3CA0009FA70D /* Build configuration list for PBXNativeTarget "TextToSVG" */;
101 | buildPhases = (
102 | 91B92085F1662A96B3FC5A8E /* [CP] Check Pods Manifest.lock */,
103 | 324B602A234D3C9F009FA70D /* Sources */,
104 | 324B602B234D3C9F009FA70D /* Frameworks */,
105 | 324B602C234D3C9F009FA70D /* Resources */,
106 | );
107 | buildRules = (
108 | );
109 | dependencies = (
110 | );
111 | name = TextToSVG;
112 | productName = TextToSVG;
113 | productReference = 324B602E234D3C9F009FA70D /* TextToSVG.app */;
114 | productType = "com.apple.product-type.application";
115 | };
116 | /* End PBXNativeTarget section */
117 |
118 | /* Begin PBXProject section */
119 | 324B6026234D3C9F009FA70D /* Project object */ = {
120 | isa = PBXProject;
121 | attributes = {
122 | LastSwiftUpdateCheck = 1100;
123 | LastUpgradeCheck = 1100;
124 | TargetAttributes = {
125 | 324B602D234D3C9F009FA70D = {
126 | CreatedOnToolsVersion = 11.0;
127 | };
128 | };
129 | };
130 | buildConfigurationList = 324B6029234D3C9F009FA70D /* Build configuration list for PBXProject "TextToSVG" */;
131 | compatibilityVersion = "Xcode 9.3";
132 | developmentRegion = en;
133 | hasScannedForEncodings = 0;
134 | knownRegions = (
135 | en,
136 | Base,
137 | );
138 | mainGroup = 324B6025234D3C9F009FA70D;
139 | productRefGroup = 324B602F234D3C9F009FA70D /* Products */;
140 | projectDirPath = "";
141 | projectRoot = "";
142 | targets = (
143 | 324B602D234D3C9F009FA70D /* TextToSVG */,
144 | );
145 | };
146 | /* End PBXProject section */
147 |
148 | /* Begin PBXResourcesBuildPhase section */
149 | 324B602C234D3C9F009FA70D /* Resources */ = {
150 | isa = PBXResourcesBuildPhase;
151 | buildActionMask = 2147483647;
152 | files = (
153 | 324B603E234D3CA0009FA70D /* LaunchScreen.storyboard in Resources */,
154 | 324B603B234D3CA0009FA70D /* Assets.xcassets in Resources */,
155 | 324B6039234D3C9F009FA70D /* Main.storyboard in Resources */,
156 | );
157 | runOnlyForDeploymentPostprocessing = 0;
158 | };
159 | /* End PBXResourcesBuildPhase section */
160 |
161 | /* Begin PBXShellScriptBuildPhase section */
162 | 91B92085F1662A96B3FC5A8E /* [CP] Check Pods Manifest.lock */ = {
163 | isa = PBXShellScriptBuildPhase;
164 | buildActionMask = 2147483647;
165 | files = (
166 | );
167 | inputFileListPaths = (
168 | );
169 | inputPaths = (
170 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
171 | "${PODS_ROOT}/Manifest.lock",
172 | );
173 | name = "[CP] Check Pods Manifest.lock";
174 | outputFileListPaths = (
175 | );
176 | outputPaths = (
177 | "$(DERIVED_FILE_DIR)/Pods-TextToSVG-checkManifestLockResult.txt",
178 | );
179 | runOnlyForDeploymentPostprocessing = 0;
180 | shellPath = /bin/sh;
181 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
182 | showEnvVarsInLog = 0;
183 | };
184 | /* End PBXShellScriptBuildPhase section */
185 |
186 | /* Begin PBXSourcesBuildPhase section */
187 | 324B602A234D3C9F009FA70D /* Sources */ = {
188 | isa = PBXSourcesBuildPhase;
189 | buildActionMask = 2147483647;
190 | files = (
191 | 324B6036234D3C9F009FA70D /* ViewController.swift in Sources */,
192 | 324B6032234D3C9F009FA70D /* AppDelegate.swift in Sources */,
193 | 324B6034234D3C9F009FA70D /* SceneDelegate.swift in Sources */,
194 | );
195 | runOnlyForDeploymentPostprocessing = 0;
196 | };
197 | /* End PBXSourcesBuildPhase section */
198 |
199 | /* Begin PBXVariantGroup section */
200 | 324B6037234D3C9F009FA70D /* Main.storyboard */ = {
201 | isa = PBXVariantGroup;
202 | children = (
203 | 324B6038234D3C9F009FA70D /* Base */,
204 | );
205 | name = Main.storyboard;
206 | sourceTree = "";
207 | };
208 | 324B603C234D3CA0009FA70D /* LaunchScreen.storyboard */ = {
209 | isa = PBXVariantGroup;
210 | children = (
211 | 324B603D234D3CA0009FA70D /* Base */,
212 | );
213 | name = LaunchScreen.storyboard;
214 | sourceTree = "";
215 | };
216 | /* End PBXVariantGroup section */
217 |
218 | /* Begin XCBuildConfiguration section */
219 | 324B6040234D3CA0009FA70D /* Debug */ = {
220 | isa = XCBuildConfiguration;
221 | buildSettings = {
222 | ALWAYS_SEARCH_USER_PATHS = NO;
223 | CLANG_ANALYZER_NONNULL = YES;
224 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
225 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
226 | CLANG_CXX_LIBRARY = "libc++";
227 | CLANG_ENABLE_MODULES = YES;
228 | CLANG_ENABLE_OBJC_ARC = YES;
229 | CLANG_ENABLE_OBJC_WEAK = YES;
230 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
231 | CLANG_WARN_BOOL_CONVERSION = YES;
232 | CLANG_WARN_COMMA = YES;
233 | CLANG_WARN_CONSTANT_CONVERSION = YES;
234 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
235 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
236 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
237 | CLANG_WARN_EMPTY_BODY = YES;
238 | CLANG_WARN_ENUM_CONVERSION = YES;
239 | CLANG_WARN_INFINITE_RECURSION = YES;
240 | CLANG_WARN_INT_CONVERSION = YES;
241 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
242 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
243 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
244 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
245 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
246 | CLANG_WARN_STRICT_PROTOTYPES = YES;
247 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
248 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
249 | CLANG_WARN_UNREACHABLE_CODE = YES;
250 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
251 | COPY_PHASE_STRIP = NO;
252 | DEBUG_INFORMATION_FORMAT = dwarf;
253 | ENABLE_STRICT_OBJC_MSGSEND = YES;
254 | ENABLE_TESTABILITY = YES;
255 | GCC_C_LANGUAGE_STANDARD = gnu11;
256 | GCC_DYNAMIC_NO_PIC = NO;
257 | GCC_NO_COMMON_BLOCKS = YES;
258 | GCC_OPTIMIZATION_LEVEL = 0;
259 | GCC_PREPROCESSOR_DEFINITIONS = (
260 | "DEBUG=1",
261 | "$(inherited)",
262 | );
263 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
264 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
265 | GCC_WARN_UNDECLARED_SELECTOR = YES;
266 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
267 | GCC_WARN_UNUSED_FUNCTION = YES;
268 | GCC_WARN_UNUSED_VARIABLE = YES;
269 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
270 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
271 | MTL_FAST_MATH = YES;
272 | ONLY_ACTIVE_ARCH = YES;
273 | SDKROOT = iphoneos;
274 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
275 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
276 | };
277 | name = Debug;
278 | };
279 | 324B6041234D3CA0009FA70D /* Release */ = {
280 | isa = XCBuildConfiguration;
281 | buildSettings = {
282 | ALWAYS_SEARCH_USER_PATHS = NO;
283 | CLANG_ANALYZER_NONNULL = YES;
284 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
285 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
286 | CLANG_CXX_LIBRARY = "libc++";
287 | CLANG_ENABLE_MODULES = YES;
288 | CLANG_ENABLE_OBJC_ARC = YES;
289 | CLANG_ENABLE_OBJC_WEAK = YES;
290 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
291 | CLANG_WARN_BOOL_CONVERSION = YES;
292 | CLANG_WARN_COMMA = YES;
293 | CLANG_WARN_CONSTANT_CONVERSION = YES;
294 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
295 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
296 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
297 | CLANG_WARN_EMPTY_BODY = YES;
298 | CLANG_WARN_ENUM_CONVERSION = YES;
299 | CLANG_WARN_INFINITE_RECURSION = YES;
300 | CLANG_WARN_INT_CONVERSION = YES;
301 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
302 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
303 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
304 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
305 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
306 | CLANG_WARN_STRICT_PROTOTYPES = YES;
307 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
308 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
309 | CLANG_WARN_UNREACHABLE_CODE = YES;
310 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
311 | COPY_PHASE_STRIP = NO;
312 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
313 | ENABLE_NS_ASSERTIONS = NO;
314 | ENABLE_STRICT_OBJC_MSGSEND = YES;
315 | GCC_C_LANGUAGE_STANDARD = gnu11;
316 | GCC_NO_COMMON_BLOCKS = YES;
317 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
318 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
319 | GCC_WARN_UNDECLARED_SELECTOR = YES;
320 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
321 | GCC_WARN_UNUSED_FUNCTION = YES;
322 | GCC_WARN_UNUSED_VARIABLE = YES;
323 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
324 | MTL_ENABLE_DEBUG_INFO = NO;
325 | MTL_FAST_MATH = YES;
326 | SDKROOT = iphoneos;
327 | SWIFT_COMPILATION_MODE = wholemodule;
328 | SWIFT_OPTIMIZATION_LEVEL = "-O";
329 | VALIDATE_PRODUCT = YES;
330 | };
331 | name = Release;
332 | };
333 | 324B6043234D3CA0009FA70D /* Debug */ = {
334 | isa = XCBuildConfiguration;
335 | baseConfigurationReference = E6FED3283B3AF78AB183D784 /* Pods-TextToSVG.debug.xcconfig */;
336 | buildSettings = {
337 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
338 | CODE_SIGN_STYLE = Automatic;
339 | DEVELOPMENT_TEAM = HT3GA3Z42X;
340 | INFOPLIST_FILE = TextToSVG/Info.plist;
341 | LD_RUNPATH_SEARCH_PATHS = (
342 | "$(inherited)",
343 | "@executable_path/Frameworks",
344 | );
345 | PRODUCT_BUNDLE_IDENTIFIER = cat.thepirate.TextToSVG;
346 | PRODUCT_NAME = "$(TARGET_NAME)";
347 | SWIFT_VERSION = 5.0;
348 | TARGETED_DEVICE_FAMILY = "1,2";
349 | };
350 | name = Debug;
351 | };
352 | 324B6044234D3CA0009FA70D /* Release */ = {
353 | isa = XCBuildConfiguration;
354 | baseConfigurationReference = 10508FD6454EC7889AD3B05A /* Pods-TextToSVG.release.xcconfig */;
355 | buildSettings = {
356 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
357 | CODE_SIGN_STYLE = Automatic;
358 | DEVELOPMENT_TEAM = HT3GA3Z42X;
359 | INFOPLIST_FILE = TextToSVG/Info.plist;
360 | LD_RUNPATH_SEARCH_PATHS = (
361 | "$(inherited)",
362 | "@executable_path/Frameworks",
363 | );
364 | PRODUCT_BUNDLE_IDENTIFIER = cat.thepirate.TextToSVG;
365 | PRODUCT_NAME = "$(TARGET_NAME)";
366 | SWIFT_VERSION = 5.0;
367 | TARGETED_DEVICE_FAMILY = "1,2";
368 | };
369 | name = Release;
370 | };
371 | /* End XCBuildConfiguration section */
372 |
373 | /* Begin XCConfigurationList section */
374 | 324B6029234D3C9F009FA70D /* Build configuration list for PBXProject "TextToSVG" */ = {
375 | isa = XCConfigurationList;
376 | buildConfigurations = (
377 | 324B6040234D3CA0009FA70D /* Debug */,
378 | 324B6041234D3CA0009FA70D /* Release */,
379 | );
380 | defaultConfigurationIsVisible = 0;
381 | defaultConfigurationName = Release;
382 | };
383 | 324B6042234D3CA0009FA70D /* Build configuration list for PBXNativeTarget "TextToSVG" */ = {
384 | isa = XCConfigurationList;
385 | buildConfigurations = (
386 | 324B6043234D3CA0009FA70D /* Debug */,
387 | 324B6044234D3CA0009FA70D /* Release */,
388 | );
389 | defaultConfigurationIsVisible = 0;
390 | defaultConfigurationName = Release;
391 | };
392 | /* End XCConfigurationList section */
393 | };
394 | rootObject = 324B6026234D3C9F009FA70D /* Project object */;
395 | }
396 |
--------------------------------------------------------------------------------
/Samples/TextToSVG/TextToSVG.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Samples/TextToSVG/TextToSVG.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Samples/TextToSVG/TextToSVG/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // TextToSVG
4 | //
5 | // Created by Mateusz Malczak on 08/10/2019.
6 | //
7 |
8 | import UIKit
9 |
10 | @UIApplicationMain
11 | class AppDelegate: UIResponder, UIApplicationDelegate {
12 |
13 |
14 |
15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
16 | // Override point for customization after application launch.
17 | return true
18 | }
19 |
20 | // MARK: UISceneSession Lifecycle
21 |
22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
23 | // Called when a new scene session is being created.
24 | // Use this method to select a configuration to create the new scene with.
25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
26 | }
27 |
28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
29 | // Called when the user discards a scene session.
30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
32 | }
33 |
34 |
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/Samples/TextToSVG/TextToSVG/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/Samples/TextToSVG/TextToSVG/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Samples/TextToSVG/TextToSVG/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 |
--------------------------------------------------------------------------------
/Samples/TextToSVG/TextToSVG/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 | Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
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 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/Samples/TextToSVG/TextToSVG/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIApplicationSceneManifest
24 |
25 | UIApplicationSupportsMultipleScenes
26 |
27 | UISceneConfigurations
28 |
29 | UIWindowSceneSessionRoleApplication
30 |
31 |
32 | UISceneConfigurationName
33 | Default Configuration
34 | UISceneDelegateClassName
35 | $(PRODUCT_MODULE_NAME).SceneDelegate
36 | UISceneStoryboardFile
37 | Main
38 |
39 |
40 |
41 |
42 | UILaunchStoryboardName
43 | LaunchScreen
44 | UIMainStoryboardFile
45 | Main
46 | UIRequiredDeviceCapabilities
47 |
48 | armv7
49 |
50 | UISupportedInterfaceOrientations
51 |
52 | UIInterfaceOrientationPortrait
53 | UIInterfaceOrientationLandscapeLeft
54 | UIInterfaceOrientationLandscapeRight
55 |
56 | UISupportedInterfaceOrientations~ipad
57 |
58 | UIInterfaceOrientationPortrait
59 | UIInterfaceOrientationPortraitUpsideDown
60 | UIInterfaceOrientationLandscapeLeft
61 | UIInterfaceOrientationLandscapeRight
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/Samples/TextToSVG/TextToSVG/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // TextToSVG
4 | //
5 | // Created by Mateusz Malczak on 08/10/2019.
6 | //
7 |
8 | import UIKit
9 |
10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
11 |
12 | var window: UIWindow?
13 |
14 |
15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
16 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
17 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
18 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
19 | guard let _ = (scene as? UIWindowScene) else { return }
20 | }
21 |
22 | func sceneDidDisconnect(_ scene: UIScene) {
23 | // Called as the scene is being released by the system.
24 | // This occurs shortly after the scene enters the background, or when its session is discarded.
25 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
26 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
27 | }
28 |
29 | func sceneDidBecomeActive(_ scene: UIScene) {
30 | // Called when the scene has moved from an inactive state to an active state.
31 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
32 | }
33 |
34 | func sceneWillResignActive(_ scene: UIScene) {
35 | // Called when the scene will move from an active state to an inactive state.
36 | // This may occur due to temporary interruptions (ex. an incoming phone call).
37 | }
38 |
39 | func sceneWillEnterForeground(_ scene: UIScene) {
40 | // Called as the scene transitions from the background to the foreground.
41 | // Use this method to undo the changes made on entering the background.
42 | }
43 |
44 | func sceneDidEnterBackground(_ scene: UIScene) {
45 | // Called as the scene transitions from the foreground to the background.
46 | // Use this method to save data, release shared resources, and store enough scene-specific state information
47 | // to restore the scene back to its current state.
48 | }
49 |
50 |
51 | }
52 |
53 |
--------------------------------------------------------------------------------
/Samples/TextToSVG/TextToSVG/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // TextToSVG
4 | //
5 | // Created by Mateusz Malczak on 08/10/2019.
6 | //
7 |
8 | import UIKit
9 | import WebKit
10 | import TextPaths
11 |
12 | class ViewController: UIViewController {
13 |
14 | @IBOutlet weak var textView: UITextView!
15 |
16 | @IBOutlet weak var svgTextView: UITextView!
17 |
18 | @IBOutlet weak var webView: WKWebView!
19 |
20 | @IBAction func switchChanged(_ control: UISegmentedControl) {
21 | webView.isHidden = control.selectedSegmentIndex == 0
22 | }
23 |
24 | override func viewDidAppear(_ animated: Bool) {
25 | super.viewDidAppear(animated)
26 | getSVG()
27 | }
28 |
29 | func getSVG() {
30 | let hardcodedTextViewMargins: CGFloat = 12
31 |
32 | if let attributedText = textView.attributedText {
33 | let bounds = CGSize(width: textView.frame.width - hardcodedTextViewMargins,
34 | height: CGFloat.greatestFiniteMagnitude)
35 | let textPath = attributedText.getTextPath(InBounds: bounds,
36 | withAttributes: false,
37 | withPath: true)
38 | if let composedPath = textPath?.composedPath, let composedBounds = textPath?.composedBounds {
39 |
40 | let svg_path = svg(fromPath: composedPath)
41 |
42 | let svg = """
43 |
47 | """
48 |
49 | svgTextView.text = svg
50 |
51 | let html = """
52 |
53 |
54 | TextPaths
55 |
56 |
57 |
58 | SVG Preview
Text below is a SVG path
59 | \(svg)
60 |
61 |
62 | """
63 |
64 | webView.loadHTMLString(html, baseURL: nil)
65 | }
66 | }
67 | }
68 |
69 | func svg(fromPath path: CGPath) -> String {
70 | var data = Data()
71 | path.apply(info: &data) { userData, elementPtr in
72 | var data = userData!.assumingMemoryBound(to: Data.self).pointee
73 | let element = elementPtr.pointee
74 | switch element.type {
75 | case .moveToPoint:
76 | let point = element.points.pointee
77 | data.append(String(format: "M%.2f,%.2f", point.x, point.y).data(using: .utf8)!)
78 | break;
79 | case .addLineToPoint:
80 | let point = element.points.pointee
81 | data.append(String(format: "L%.2f,%.2f", point.x, point.y).data(using: .utf8)!)
82 | break;
83 | case .addQuadCurveToPoint:
84 | let ctrl = element.points.pointee
85 | let point = element.points.advanced(by: 1).pointee
86 |
87 | data.append(String(format: "Q%.2f,%.2f,%.2f,%.2f", ctrl.x, ctrl.y, point.x, point.y).data(using: .utf8)!)
88 | break
89 | case .addCurveToPoint:
90 | let ctrl1 = element.points.pointee
91 | let ctrl2 = element.points.advanced(by: 1).pointee
92 | let point = element.points.advanced(by: 2).pointee
93 | data.append(String(format: "C%.2f,%.2f,%.2f,%.2f,%.2f,%.2f", ctrl1.x, ctrl1.y, ctrl2.x, ctrl2.y, point.x, point.y).data(using: .utf8)!)
94 | break
95 | case .closeSubpath:
96 | data.append("Z".data(using: .utf8)!)
97 | break
98 | @unknown default:
99 | break
100 | }
101 | userData!.assumingMemoryBound(to: Data.self).pointee = data
102 | }
103 |
104 | return String(bytes: data, encoding: .utf8)!
105 | }
106 |
107 | }
108 |
109 | extension ViewController: UITextViewDelegate {
110 | func textViewDidChange(_ textView: UITextView) {
111 | getSVG()
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/Source/NSAttributedString.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSAttributedString.swift
3 | // TextPaths
4 | //
5 | // Created by Mateusz Malczak on 21/01/17.
6 | // Copyright © 2017 The Pirate Cat. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import CoreGraphics
11 |
12 | typealias TextPathAttributes = [NSAttributedString.Key:Any]
13 |
14 | /**
15 | Class represents single character representation - glyph
16 | */
17 | public class TextPathGlyph {
18 | public typealias Index = String.UnicodeScalarView.Index
19 |
20 | /// Character index in source string unicode view
21 | public fileprivate(set) var index: Index
22 |
23 | /// Glyph path defined in glyph coordinates
24 | public fileprivate(set) var path: CGPath
25 |
26 | /// Glyph path position (top left corner) in line space
27 | public fileprivate(set) var position: CGPoint
28 |
29 | /// Glyph line advance
30 | public fileprivate(set) var advance = CGSize.zero
31 |
32 | /// Glyph origin offset (x component is a glyph origin offset, y component is an offset to baseline)
33 | public fileprivate(set) var originOffset = CGPoint.zero
34 |
35 | /// Glyph line run index
36 | fileprivate var lineRun: Int = 0
37 |
38 | /// Glyph line
39 | fileprivate weak var line: TextPathLine?
40 |
41 | /// Get glyph attributes as defined in source string
42 | public var attributes: [NSAttributedString.Key:Any]? {
43 | return line?.attributes(ForGlyph: self)
44 | }
45 |
46 | init(index: Index, path: CGPath, position: CGPoint){
47 | self.index = index
48 | self.path = path
49 | self.position = position
50 | }
51 | }
52 |
53 | /**
54 | Single text line representation
55 | */
56 | public class TextPathLine {
57 |
58 | /// Line index
59 | public fileprivate(set) var index: Int
60 |
61 | /**
62 | Line typographic bounds based on line ascent / descent
63 |
64 | Rectangle is based on typographic line properties (ie. ascent, descent)
65 | */
66 | public fileprivate(set) var lineBounds = CGRect.zero
67 |
68 | /**
69 | Line path bounds based on text path
70 |
71 | Rectangle defined by _textBounds_ is smaller than _lineBounds_ and is based only o text bounds
72 | */
73 | public fileprivate(set) var textBounds = CGRect.zero
74 |
75 | /// Line leading
76 | public fileprivate(set) var leading = CGFloat(0.0)
77 |
78 | /// Line ascent
79 | public fileprivate(set) var ascent = CGFloat(0.0)
80 |
81 | /// Line descent
82 | public fileprivate(set) var descent = CGFloat(0.0)
83 |
84 | /**
85 | Line effective descent is calculated based on lineRuns typographic properties,
86 | in most cases is equal to _descent_. In some rare cases this rect is smaller that _descent_ property.
87 |
88 | ## Example ##
89 | ````
90 | let str = NSMutableAttributedString(string: "First line".uppercased(), attributes: [
91 | NSFontAttributeName: UIFont.systemFont(ofSize: 22, weight: UIFontWeightUltraLight)
92 | ])
93 | str.append(NSAttributedString(string: "\nSecond line".uppercased(), attributes: [
94 | NSFontAttributeName: UIFont.systemFont(ofSize: 64, weight: UIFontWeightMedium)
95 | ]))
96 | ````
97 | In second line much larger font is used, it also applied to the 'line break' (\n)
98 | character. As a result typographic size of the first line is different that one used by TextKit to draw text
99 | in text components (UILabel, UITextView).
100 | In this case _effectiveDescent_ of the first line is smaller that _descent_ measured by CoreText, because it is calculated
101 | only based on visible characters (ie. not including '\n' metrics)
102 | */
103 | public fileprivate(set) var effectiveDescent = CGFloat(0.0)
104 |
105 | /// Line effective ascent (read more above)
106 | public fileprivate(set) var effectiveAscent = CGFloat(0.0)
107 |
108 | /// Line glyph to attributes mapping
109 | fileprivate var attributes: [TextPathAttributes]?
110 |
111 | /// Collection of all glyphs in line
112 | fileprivate var glyphs = [TextPathGlyph]()
113 |
114 | init(index: Int) {
115 | self.index = index
116 | }
117 |
118 | /**
119 | Enumerates over all glyphs in line
120 | - Parameter callback: Called for each glyph in line
121 | - Parameter line: Current text line
122 | - Parameter glyph: Current glyph
123 | */
124 | public func enumerateGlyphs(_ callback:(_ line: TextPathLine, _ glyph: TextPathGlyph) -> ()) {
125 | for glyph in glyphs {
126 | callback(self, glyph)
127 | }
128 | }
129 |
130 | /**
131 | Get attributes for a line glyph
132 | - Parameter glyph: Glyph in line
133 | - Returns: Glyph attributes
134 | */
135 | fileprivate func attributes(ForGlyph glyph: TextPathGlyph) -> TextPathAttributes? {
136 | if let attributes = attributes {
137 | return attributes[glyph.lineRun]
138 | }
139 | return nil
140 | }
141 | }
142 |
143 | /**
144 | Text frame representation. This can represent a simple text rectangle (eq. UITextView text content),
145 | as well as a complex frame defined by CGPath
146 | */
147 | public class TextPathFrame {
148 |
149 | /// Frame shape path (eq. textfield bounds rect)
150 | public fileprivate(set) var path: CGPath
151 |
152 | /// Text frame lines
153 | public fileprivate(set) var lines = [TextPathLine]()
154 |
155 | init(path: CGPath) {
156 | self.path = path
157 | }
158 |
159 | /**
160 | Enumerates over all glyphs in text frame
161 | - Parameter callback: Called for each glyph in text frame
162 | - Parameter line: Current text line
163 | - Parameter glyph: Current glyph
164 | */
165 | public func enumerateGlyphs(_ callback: @escaping(_ line: TextPathLine, _ glyph: TextPathGlyph) -> ()) {
166 | for line in lines {
167 | line.enumerateGlyphs(callback)
168 | }
169 | }
170 | }
171 |
172 | /**
173 | Text path represents a CGPath representation of text frame, glyphs and typographic properties
174 | */
175 | public class TextPath {
176 |
177 | /// Attributed text used to generate text path
178 | public fileprivate(set) var attributedString: NSAttributedString
179 |
180 | /// Text path composed for input text
181 | public fileprivate(set) var composedPath: CGPath?
182 |
183 | /// Composed text path bounding box
184 | public fileprivate(set) var composedBounds: CGRect
185 |
186 | /// text frames
187 | public fileprivate(set) var frames = [TextPathFrame]()
188 |
189 | init(text: NSAttributedString, path: CGPath? = nil) {
190 | self.attributedString = text
191 | self.composedPath = path
192 | self.composedBounds = path?.boundingBoxOfPath ?? CGRect.zero
193 | }
194 | }
195 |
196 | public extension NSAttributedString {
197 |
198 | /**
199 | Creates a text path and a collection of text lines and
200 | glyphs with additional typographic informations (ie. ascent, descent, bounds)
201 |
202 | - Parameter bounds: text bounding box
203 | - Parameter withAttributes: if _true_ glyph attributes are included in returned TextPath
204 | - Parameter withPath: if _true_ a composed text path is included
205 | - Returns: created text path or NULL if failed
206 | */
207 | func getTextPath(InBounds bounds:CGSize, withAttributes: Bool = false, withPath: Bool = true) -> TextPath? {
208 | let clearText = self.string
209 | if clearText.isEmpty {
210 | return nil
211 | }
212 |
213 | let fontAttributeKey = NSAttributedString.Key.font
214 | let defaultAttributes: TextPathAttributes = [
215 | fontAttributeKey: UIFont.systemFont(ofSize: UIFont.systemFontSize),
216 | NSAttributedString.Key.foregroundColor: UIColor.black
217 | ]
218 |
219 | var lineIndex = 0
220 | let unicodeScalars = clearText.unicodeScalars
221 | var unicodeIndex = unicodeScalars.startIndex
222 |
223 | let frameSetter = CTFramesetterCreateWithAttributedString(self)
224 | let textRange = CFRangeMake(0, self.length)
225 | let frameSize = CTFramesetterSuggestFrameSizeWithConstraints(frameSetter, textRange, nil, bounds, nil)
226 | let framePath = UIBezierPath(rect: CGRect(origin: .zero, size: frameSize)).cgPath
227 | let frame = CTFramesetterCreateFrame(frameSetter, textRange, framePath, nil)
228 |
229 | let tpFrame = TextPathFrame(path: framePath)
230 | let frames = [tpFrame]
231 |
232 | // 0. fetch all lines / glyphs + text path
233 | let ignoredCharsSet = CharacterSet.whitespacesAndNewlines
234 | let path = CGMutablePath()
235 |
236 | if let lines = CTFrameGetLines(frame) as? [CTLine] {
237 | var linesShift = CGFloat(0)
238 | var origins = [CGPoint](repeating: CGPoint.zero, count: lines.count)
239 | CTFrameGetLineOrigins(frame, CFRangeMake(0, lines.count), &origins)
240 | var originItr = origins.makeIterator()
241 |
242 | for line in lines {
243 | let lineOrigin = originItr.next() ?? CGPoint.zero
244 | let tpLine = TextPathLine(index: lineIndex)
245 |
246 | tpLine.lineBounds = CTLineGetBoundsWithOptions(line, .excludeTypographicLeading)
247 | tpLine.textBounds = CTLineGetBoundsWithOptions(line, .useGlyphPathBounds)
248 |
249 | let _ = CTLineGetTypographicBounds(line, &tpLine.ascent, &tpLine.descent, &tpLine.leading)
250 |
251 | if let lineRuns = CTLineGetGlyphRuns(line) as? [CTRun] {
252 | if withAttributes {
253 | tpLine.attributes = [TextPathAttributes](repeating:defaultAttributes,
254 | count: lineRuns.count)
255 | }
256 |
257 | var effectiveDescent = CGFloat(0)
258 | var effectiveAscent = CGFloat(0)
259 |
260 | var lineRunIndex = 0
261 | for lineRun in lineRuns {
262 | let glyphsCount = CTRunGetGlyphCount(lineRun)
263 | if glyphsCount == 0 {
264 | continue
265 | }
266 |
267 | let attributes = (CTRunGetAttributes(lineRun) as? TextPathAttributes) ?? defaultAttributes
268 | let font = (attributes[fontAttributeKey] as? UIFont) ?? (defaultAttributes[fontAttributeKey] as! UIFont)
269 |
270 | if withAttributes {
271 | tpLine.attributes![lineRunIndex] = attributes
272 | }
273 |
274 | var rt_ascent = CGFloat(0.0)
275 | var rt_descent = CGFloat(0.0)
276 | var rt_leading = CGFloat(0.0)
277 | let _ = CTRunGetTypographicBounds(lineRun, CFRangeMake(0, glyphsCount), &rt_ascent, &rt_descent, &rt_leading)
278 |
279 | let lineRunInfo = (CTRunGetGlyphsPtr(lineRun), CTRunGetPositionsPtr(lineRun), CTRunGetAdvancesPtr(lineRun))
280 | switch( lineRunInfo ) {
281 | case let (glyphsPtr?, positionsPtr?, advancesPtr?):
282 | var glyphPtr = glyphsPtr
283 | var positionPtr = positionsPtr
284 | var advancePtr = advancesPtr
285 |
286 | for _ in 0.. "mateusz@malczak.info" }
9 | s.source = { :git => "https://github.com/malczak/TextPaths.git", :branch => "swift" }
10 |
11 | s.platform = :ios, "8.0"
12 |
13 | s.source_files = "Source/*.swift"
14 | s.exclude_files = "Source/*Tests.swift"
15 |
16 | s.requires_arc = true
17 | end
18 |
--------------------------------------------------------------------------------
/_Assets/textpaths-bounds-01.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/malczak/TextPaths/f1c9ded08d86e6ce44446a370121893a3b95b4f2/_Assets/textpaths-bounds-01.jpg
--------------------------------------------------------------------------------
/_Assets/textpaths-bounds-02.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/malczak/TextPaths/f1c9ded08d86e6ce44446a370121893a3b95b4f2/_Assets/textpaths-bounds-02.jpg
--------------------------------------------------------------------------------
/_Assets/textpaths-bounds-03.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/malczak/TextPaths/f1c9ded08d86e6ce44446a370121893a3b95b4f2/_Assets/textpaths-bounds-03.jpg
--------------------------------------------------------------------------------
/_Assets/textpaths-bounds-04.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/malczak/TextPaths/f1c9ded08d86e6ce44446a370121893a3b95b4f2/_Assets/textpaths-bounds-04.jpg
--------------------------------------------------------------------------------
/_Assets/textpaths-falling-uilabel-animation.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/malczak/TextPaths/f1c9ded08d86e6ce44446a370121893a3b95b4f2/_Assets/textpaths-falling-uilabel-animation.gif
--------------------------------------------------------------------------------
/_Assets/textpaths-fly-in-text-animation.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/malczak/TextPaths/f1c9ded08d86e6ce44446a370121893a3b95b4f2/_Assets/textpaths-fly-in-text-animation.gif
--------------------------------------------------------------------------------
/_Assets/textpaths-nsattributedtext-decompose.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/malczak/TextPaths/f1c9ded08d86e6ce44446a370121893a3b95b4f2/_Assets/textpaths-nsattributedtext-decompose.jpg
--------------------------------------------------------------------------------
/_Assets/textpaths-nsattributedtext-to-svg-01.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/malczak/TextPaths/f1c9ded08d86e6ce44446a370121893a3b95b4f2/_Assets/textpaths-nsattributedtext-to-svg-01.jpg
--------------------------------------------------------------------------------
/_Assets/textpaths-nsattributedtext-to-svg-02.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/malczak/TextPaths/f1c9ded08d86e6ce44446a370121893a3b95b4f2/_Assets/textpaths-nsattributedtext-to-svg-02.jpg
--------------------------------------------------------------------------------