├── .gitattributes
├── Example SwiftUI
├── Assets.xcassets
│ ├── Contents.json
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── Image.imageset
│ │ └── Contents.json
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
├── Example SwiftUI.entitlements
├── Info.plist
├── TextGenerator.swift
├── Example_SwiftUIApp.swift
└── ContentView.swift
├── Sources
├── module.modulemap
├── LabelKit
│ ├── Config.xcconfig
│ ├── Utils.swift
│ ├── LKBoundsDidChangeAction.swift
│ ├── LabelView.swift
│ ├── LKLabel.swift
│ ├── LKExtensions.swift
│ ├── LKLabelLayer.swift
│ └── LKTextDidChangeAction.swift
├── Info.plist
└── LabelKit.h
├── LabelKit.gif
├── Tests
├── LinuxMain.swift
└── LabelKitTests
│ ├── XCTestManifests.swift
│ └── LabelKitTests.swift
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── LabelKit.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── WorkspaceSettings.xcsettings
│ │ └── IDEWorkspaceChecks.plist
├── xcshareddata
│ └── xcschemes
│ │ ├── LabelKit iOS Tests.xcscheme
│ │ ├── LabelKit tvOS Tests.xcscheme
│ │ ├── LabelKit iOS.xcscheme
│ │ ├── LabelKit tvOS.xcscheme
│ │ ├── LabelKit Example tvOS.xcscheme
│ │ ├── LabelKit Example SwiftUI.xcscheme
│ │ └── LabelKit Example iOS.xcscheme
└── project.pbxproj
├── Example iOS
├── Example iOS.entitlements
├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
├── SceneDelegate.swift
├── Info.plist
├── AppDelegate.swift
├── ViewController.swift
└── NSAttributedString+Random.swift
├── LabelKit.podspec
├── LabelKit iOS Tests
├── Info.plist
└── LKExtensionsTests.swift
├── LabelKit tvOS Tests
└── Info.plist
├── Package.swift
├── Example tvOS
├── Info.plist
├── AppDelegate.swift
└── Base.lproj
│ └── Main.storyboard
├── .gitignore
├── LICENSE
├── .travis.yml
├── INSTALL.md
└── README.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | LabelKit.gif filter=lfs diff=lfs merge=lfs -text
2 |
--------------------------------------------------------------------------------
/Example SwiftUI/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Sources/module.modulemap:
--------------------------------------------------------------------------------
1 | framework module LabelKit {
2 | umbrella header "LabelKit.h"
3 | export *
4 | module * { export * }
5 | }
6 |
--------------------------------------------------------------------------------
/Example SwiftUI/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/LabelKit.gif:
--------------------------------------------------------------------------------
1 | version https://git-lfs.github.com/spec/v1
2 | oid sha256:dd66a71fcf65c4b2ed46f9f75578e9ba15b78b0d5e47e7a09e6827c155b6d50e
3 | size 11115503
4 |
--------------------------------------------------------------------------------
/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | import LabelKitTests
4 |
5 | var tests = [XCTestCaseEntry]()
6 | tests += LabelKitTests.allTests()
7 | XCTMain(tests)
8 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example SwiftUI/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Tests/LabelKitTests/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | #if !canImport(ObjectiveC)
4 | public func allTests() -> [XCTestCaseEntry] {
5 | return [
6 | testCase(LabelKitTests.allTests),
7 | ]
8 | }
9 | #endif
10 |
--------------------------------------------------------------------------------
/LabelKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/LabelKit.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/LabelKit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example iOS/Example iOS.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.network.client
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Example SwiftUI/Example SwiftUI.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.network.client
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Example SwiftUI/Assets.xcassets/Image.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "universal",
13 | "scale" : "3x"
14 | }
15 | ],
16 | "info" : {
17 | "author" : "xcode",
18 | "version" : 1
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Sources/LabelKit/Config.xcconfig:
--------------------------------------------------------------------------------
1 | //
2 | // Config.xcconfig
3 | // LabelKit
4 | //
5 | // Created by Eugene Dudnyk on 28/02/2021.
6 | // Copyright © 2021 Imaginarium Works. All rights reserved.
7 | //
8 |
9 | // Configuration settings file format documentation can be found at:
10 | // https://help.apple.com/xcode/#/dev745c5c974
11 | PATHCONFIG_Debug = "TEST-DEBUG"
12 | PATHCONFIG_Release = "TEST-RELEASE"
13 | PATHCONFIG_RESOLVED = $(PATHCONFIG_$(CONFIGURATION))
14 |
--------------------------------------------------------------------------------
/Tests/LabelKitTests/LabelKitTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import LabelKit
3 |
4 | final class LabelKitTests: XCTestCase {
5 | func testExample() {
6 | // This is an example of a functional test case.
7 | // Use XCTAssert and related functions to verify your tests produce the correct
8 | // results.
9 | XCTAssertEqual(LabelKit().text, "Hello, World!")
10 | }
11 |
12 | static var allTests = [
13 | ("testExample", testExample),
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/LabelKit.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'LabelKit'
3 | s.version = '2.1.0'
4 | s.license = 'Simplified BSD License'
5 | s.summary = 'An smart and animatable UILabel with tools for advanced text layouts, written in Swift'
6 | s.homepage = 'https://github.com/edudnyk/LabelKit'
7 | s.authors = { 'Eugene Dudnyk' => 'edudnyk@gmail.com' }
8 | s.source = { :git => 'https://github.com/edudnyk/LabelKit.git', :tag => s.version }
9 | s.ios.deployment_target = '12.0'
10 | s.tvos.deployment_target = '12.0'
11 | s.source_files = 'Sources/**/*.swift'
12 | s.swift_version = '5.0'
13 | end
14 |
--------------------------------------------------------------------------------
/LabelKit iOS Tests/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 |
22 |
23 |
--------------------------------------------------------------------------------
/LabelKit tvOS Tests/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 |
22 |
23 |
--------------------------------------------------------------------------------
/Sources/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 | $(MARKETING_VERSION)
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.0
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "LabelKit",
8 | products: [
9 | // Products define the executables and libraries produced by a package, and make them visible to other packages.
10 | .library(
11 | name: "LabelKit",
12 | targets: ["LabelKit"]),
13 | ],
14 | dependencies: [
15 | // Dependencies declare other packages that this package depends on.
16 | // .package(url: /* package url */, from: "1.0.0"),
17 | ],
18 | targets: [
19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
20 | // Targets can depend on other targets in this package, and on products in packages which this package depends on.
21 | .target(
22 | name: "LabelKit",
23 | dependencies: []),
24 | .testTarget(
25 | name: "LabelKitTests",
26 | dependencies: ["LabelKit"]),
27 | ]
28 | )
29 |
--------------------------------------------------------------------------------
/Example tvOS/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 | $(CURRENT_PROJECT_VERSION)
21 | LSRequiresIPhoneOS
22 |
23 | UIMainStoryboardFile
24 | Main
25 | UIRequiredDeviceCapabilities
26 |
27 | arm64
28 |
29 | UIUserInterfaceStyle
30 | Automatic
31 |
32 |
33 |
--------------------------------------------------------------------------------
/.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 | *.xccheckout
22 | *.moved-aside
23 | *.xcuserstate
24 | *.xcscmblueprint
25 | .DS_Store
26 |
27 | ## Obj-C/Swift specific
28 | *.hmap
29 | *.ipa
30 |
31 | ## Playgrounds
32 | timeline.xctimeline
33 | playground.xcworkspace
34 |
35 | # Swift Package Manager
36 | #
37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
38 | # Packages/
39 | .build/
40 |
41 | # CocoaPods
42 | #
43 | # We recommend against adding the Pods directory to your .gitignore. However
44 | # you should judge for yourself, the pros and cons are mentioned at:
45 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
46 | #
47 | # Pods/
48 |
49 | # Carthage
50 | #
51 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
52 | # Carthage/Checkouts
53 |
54 | Carthage/Build
55 |
56 | # fastlane
57 | #
58 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
59 | # screenshots whenever they are needed.
60 | # For more information about the recommended setup visit:
61 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md
62 |
63 | fastlane/report.xml
64 | fastlane/screenshots
65 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The Simplified BSD License (2-clause BSD license) (BSD)
2 |
3 | Copyright (c) 2019-2021 Eugene Dudnyk
4 |
5 | All rights reserved.
6 |
7 | Redistribution and use in source and binary forms, with or without
8 | modification, are permitted provided that the following conditions are met:
9 |
10 | 1. Redistributions of source code must retain the above copyright notice, this
11 | list of conditions and the following disclaimer.
12 | 2. Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 |
27 | The views and conclusions contained in the software and documentation are those
28 | of the authors and should not be interpreted as representing official policies,
29 | either expressed or implied, of the LabelKit project.
30 |
--------------------------------------------------------------------------------
/Example iOS/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 |
--------------------------------------------------------------------------------
/Sources/LabelKit.h:
--------------------------------------------------------------------------------
1 | //
2 | // LabelKit.h
3 | // LabelKit
4 | //
5 | // Copyright (c) 2019-2021 Eugene Dudnyk
6 | //
7 | // All rights reserved.
8 | //
9 | // Redistribution and use in source and binary forms, with or without
10 | // modification, are permitted provided that the following conditions are met:
11 | //
12 | // 1. Redistributions of source code must retain the above copyright notice, this
13 | // list of conditions and the following disclaimer.
14 | // 2. Redistributions in binary form must reproduce the above copyright notice,
15 | // this list of conditions and the following disclaimer in the documentation
16 | // and/or other materials provided with the distribution.
17 | //
18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 | //
29 | // The views and conclusions contained in the software and documentation are those
30 | // of the authors and should not be interpreted as representing official policies,
31 | // either expressed or implied, of the LabelKit project.
32 | //
33 |
34 | #import
35 |
--------------------------------------------------------------------------------
/Sources/LabelKit/Utils.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Utils.swift
3 | // LabelKit
4 | //
5 | // Copyright (c) 2019-2021 Eugene Dudnyk
6 | //
7 | // All rights reserved.
8 | //
9 | // Redistribution and use in source and binary forms, with or without
10 | // modification, are permitted provided that the following conditions are met:
11 | //
12 | // 1. Redistributions of source code must retain the above copyright notice, this
13 | // list of conditions and the following disclaimer.
14 | // 2. Redistributions in binary form must reproduce the above copyright notice,
15 | // this list of conditions and the following disclaimer in the documentation
16 | // and/or other materials provided with the distribution.
17 | //
18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 | //
29 | // The views and conclusions contained in the software and documentation are those
30 | // of the authors and should not be interpreted as representing official policies,
31 | // either expressed or implied, of the LabelKit project.
32 |
33 | import UIKit
34 |
35 | internal func roundToPixels(_ value: T, scale: T) -> T {
36 | return round(value * scale) / scale
37 | }
38 |
39 | internal func ceilToPixels(_ value: T, scale: T) -> T {
40 | return ceil(value * scale) / scale
41 | }
42 |
--------------------------------------------------------------------------------
/Example SwiftUI/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | }
93 | ],
94 | "info" : {
95 | "author" : "xcode",
96 | "version" : 1
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/LabelKit.xcodeproj/xcshareddata/xcschemes/LabelKit iOS Tests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
14 |
15 |
17 |
23 |
24 |
25 |
26 |
27 |
37 |
38 |
44 |
45 |
47 |
48 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/LabelKit.xcodeproj/xcshareddata/xcschemes/LabelKit tvOS Tests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
14 |
15 |
17 |
23 |
24 |
25 |
26 |
27 |
37 |
38 |
44 |
45 |
47 |
48 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/LabelKit iOS Tests/LKExtensionsTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LabelKit_iOS_Tests.swift
3 | // LabelKit iOS Tests
4 | //
5 | // Created by Eugene Dudnyk on 20/07/2019.
6 | // Copyright © 2019 Imaginarium Works. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import LabelKit
11 |
12 | class LKExtensionsTests : XCTestCase {
13 |
14 | func testFontWeightDecoding() {
15 | let font = UIFont.systemFont(ofSize: 14, weight: .semibold)
16 | let dict = UIFont.lk_encode(object: font)
17 | let font2 = UIFont.lk_decode(dictionaryRepresentation: dict)
18 | XCTAssertEqual(font, font2)
19 | }
20 |
21 | func testColorDecoding() {
22 | let color = UIColor(red: 0.2, green: 0.4, blue: 0.3, alpha: 0.55)
23 | let dict = UIColor.lk_encode(object: color)
24 | let color2 = UIColor.lk_decode(dictionaryRepresentation: dict)
25 | XCTAssertEqual(color, color2)
26 | }
27 |
28 | func testParagraphStyleDecoding() {
29 | let ps = NSMutableParagraphStyle()
30 | ps.lineSpacing = 42
31 | let dict = NSParagraphStyle.lk_encode(object: ps)
32 | let ps2 = NSParagraphStyle.lk_decode(dictionaryRepresentation: dict)
33 | XCTAssertEqual(ps, ps2)
34 | }
35 |
36 | func testShadowDecoding() {
37 | let shadow = NSShadow()
38 | shadow.shadowColor = UIColor.magenta
39 | let dict = NSShadow.lk_encode(object: shadow)
40 | let shadow2 = NSShadow.lk_decode(dictionaryRepresentation: dict)
41 | XCTAssertEqual(shadow, shadow2)
42 | }
43 |
44 | func testFontWeightDecodingPerformance() {
45 | // This is an example of a performance test case.
46 | self.measure {
47 | for _ in 0 ..< 1000 {
48 | let font = UIFont.systemFont(ofSize: 10 + rnd() * 20, weight: UIFont.Weight(rnd() * 2 - 1))
49 | let dict = UIFont.lk_encode(object: font)
50 | let _ = UIFont.lk_decode(dictionaryRepresentation: dict)
51 | }
52 | }
53 | }
54 |
55 | func rnd()->CGFloat {
56 | return CGFloat(arc4random()) / CGFloat(UINT32_MAX)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Example iOS/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // LabelKit - Example iOS
4 | //
5 | // Copyright (c) 2019-2021 Eugene Dudnyk
6 | //
7 | // All rights reserved.
8 | //
9 | // Redistribution and use in source and binary forms, with or without
10 | // modification, are permitted provided that the following conditions are met:
11 | //
12 | // 1. Redistributions of source code must retain the above copyright notice, this
13 | // list of conditions and the following disclaimer.
14 | // 2. Redistributions in binary form must reproduce the above copyright notice,
15 | // this list of conditions and the following disclaimer in the documentation
16 | // and/or other materials provided with the distribution.
17 | //
18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 | //
29 | // The views and conclusions contained in the software and documentation are those
30 | // of the authors and should not be interpreted as representing official policies,
31 | // either expressed or implied, of the LabelKit project.
32 | //
33 |
34 | import UIKit
35 |
36 | @available(iOS 13.0, *)
37 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
38 | var window: UIWindow?
39 |
40 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
41 | guard let _ = (scene as? UIWindowScene) else { return }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Example tvOS/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // LabelKit - Example tvOS
4 | //
5 | // Copyright (c) 2019-2021 Eugene Dudnyk
6 | //
7 | // All rights reserved.
8 | //
9 | // Redistribution and use in source and binary forms, with or without
10 | // modification, are permitted provided that the following conditions are met:
11 | //
12 | // 1. Redistributions of source code must retain the above copyright notice, this
13 | // list of conditions and the following disclaimer.
14 | // 2. Redistributions in binary form must reproduce the above copyright notice,
15 | // this list of conditions and the following disclaimer in the documentation
16 | // and/or other materials provided with the distribution.
17 | //
18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 | //
29 | // The views and conclusions contained in the software and documentation are those
30 | // of the authors and should not be interpreted as representing official policies,
31 | // either expressed or implied, of the LabelKit project.
32 | //
33 |
34 | import UIKit
35 |
36 | @UIApplicationMain
37 | class AppDelegate: UIResponder, UIApplicationDelegate {
38 | var window: UIWindow?
39 |
40 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
41 | // Override point for customization after application launch.
42 | return true
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: swift
2 | osx_image: xcode12
3 | env:
4 | global:
5 | - LC_CTYPE=en_US.UTF-8
6 | - LANG=en_US.UTF-8
7 | - PROJECT=LabelKit.xcodeproj
8 | # Framework targets
9 | - FRAMEWORK_SCHEME_IOS="LabelKit iOS"
10 | - FRAMEWORK_SCHEME_TVOS="LabelKit tvOS"
11 | # Example targets
12 | - EXAMPLE_SCHEME_IOS="LabelKit Example iOS"
13 | - EXAMPLE_SCHEME_TVOS="LabelKit Example tvOS"
14 |
15 | matrix:
16 | - DESTINATION="OS=13.5,name=iPhone 11 Pro" SCHEME="$FRAMEWORK_SCHEME_IOS" RUN_TESTS="YES" BUILD_EXAMPLE="YES" EXAMPLE_SCHEME="$EXAMPLE_SCHEME_IOS" POD_LINT="YES"
17 | - DESTINATION="OS=12.2,name=iPhone Xs" SCHEME="$FRAMEWORK_SCHEME_IOS" RUN_TESTS="YES" BUILD_EXAMPLE="YES" EXAMPLE_SCHEME="$EXAMPLE_SCHEME_IOS" POD_LINT="NO"
18 | - DESTINATION="OS=12.2,name=Apple TV 4K (at 1080p)" SCHEME="$FRAMEWORK_SCHEME_TVOS" RUN_TESTS="YES" BUILD_EXAMPLE="YES" EXAMPLE_SCHEME="$EXAMPLE_SCHEME_TVOS" POD_LINT="NO"
19 | - DESTINATION="OS=13.4,name=Apple TV 4K" SCHEME="$FRAMEWORK_SCHEME_TVOS" RUN_TESTS="YES" BUILD_EXAMPLE="YES" EXAMPLE_SCHEME="$EXAMPLE_SCHEME_TVOS" POD_LINT="NO"
20 |
21 | before_install:
22 | - gem install cocoapods --no-document --quiet
23 |
24 | script:
25 | - set -o pipefail
26 | - xcodebuild -version
27 | - xcodebuild -showsdks
28 |
29 | # Build Framework in Debug and Run Tests if specified
30 | - if [ $RUN_TESTS == "YES" ]; then
31 | xcodebuild -project "$PROJECT" -scheme "$SCHEME" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO test | xcpretty -c;
32 | else
33 | xcodebuild -project "$PROJECT" -scheme "$SCHEME" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO build | xcpretty -c;
34 | fi
35 |
36 | # Build Framework in Release and Run Tests if specified
37 | - xcodebuild -project "$PROJECT" -scheme "$SCHEME" -destination "$DESTINATION" -configuration Release ONLY_ACTIVE_ARCH=NO build | xcpretty -c;
38 |
39 | # Build Example in Debug if specified
40 | - if [ $BUILD_EXAMPLE == "YES" ]; then
41 | xcodebuild -project "$PROJECT" -scheme "$EXAMPLE_SCHEME" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO build | xcpretty -c;
42 | fi
43 |
44 | # Run `pod lib lint` if specified
45 | - if [ $POD_LINT == "YES" ]; then
46 | pod lib lint;
47 | fi
48 |
--------------------------------------------------------------------------------
/Example SwiftUI/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 | UILaunchStoryboardName
33 | LaunchScreen
34 | UISceneConfigurationName
35 | Default Configuration
36 | UISceneDelegateClassName
37 | $(PRODUCT_MODULE_NAME).SceneDelegate
38 |
39 |
40 |
41 |
42 | UIApplicationSupportsIndirectInputEvents
43 |
44 | UILaunchScreen
45 |
46 | UILaunchStoryboardName
47 | LaunchScreen
48 | UIRequiredDeviceCapabilities
49 |
50 | armv7
51 |
52 | UIStatusBarHidden
53 |
54 | UISupportedInterfaceOrientations
55 |
56 | UIInterfaceOrientationPortrait
57 | UIInterfaceOrientationLandscapeLeft
58 | UIInterfaceOrientationLandscapeRight
59 |
60 | UISupportedInterfaceOrientations~ipad
61 |
62 | UIInterfaceOrientationPortrait
63 | UIInterfaceOrientationPortraitUpsideDown
64 | UIInterfaceOrientationLandscapeLeft
65 | UIInterfaceOrientationLandscapeRight
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/Example iOS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIcons
10 |
11 | CFBundleIcons~ipad
12 |
13 | CFBundleIdentifier
14 | $(PRODUCT_BUNDLE_IDENTIFIER)
15 | CFBundleInfoDictionaryVersion
16 | 6.0
17 | CFBundleName
18 | $(PRODUCT_NAME)
19 | CFBundlePackageType
20 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
21 | CFBundleShortVersionString
22 | 1.0
23 | CFBundleVersion
24 | $(CURRENT_PROJECT_VERSION)
25 | LSRequiresIPhoneOS
26 |
27 | UIApplicationSceneManifest
28 |
29 | UIApplicationSupportsMultipleScenes
30 |
31 | UISceneConfigurations
32 |
33 | UIWindowSceneSessionRoleApplication
34 |
35 |
36 | UILaunchStoryboardName
37 | LaunchScreen
38 | UISceneConfigurationName
39 | Default Configuration
40 | UISceneDelegateClassName
41 | $(PRODUCT_MODULE_NAME).SceneDelegate
42 | UISceneStoryboardFile
43 | Main
44 |
45 |
46 |
47 |
48 | UILaunchStoryboardName
49 | LaunchScreen
50 | UIMainStoryboardFile
51 | Main
52 | UIRequiredDeviceCapabilities
53 |
54 | armv7
55 |
56 | UISupportedInterfaceOrientations
57 |
58 | UIInterfaceOrientationPortrait
59 | UIInterfaceOrientationLandscapeLeft
60 | UIInterfaceOrientationLandscapeRight
61 |
62 | UISupportedInterfaceOrientations~ipad
63 |
64 | UIInterfaceOrientationPortrait
65 | UIInterfaceOrientationPortraitUpsideDown
66 | UIInterfaceOrientationLandscapeLeft
67 | UIInterfaceOrientationLandscapeRight
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/Example iOS/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // LabelKit - Example iOS
4 | //
5 | // Copyright (c) 2019-2021 Eugene Dudnyk
6 | //
7 | // All rights reserved.
8 | //
9 | // Redistribution and use in source and binary forms, with or without
10 | // modification, are permitted provided that the following conditions are met:
11 | //
12 | // 1. Redistributions of source code must retain the above copyright notice, this
13 | // list of conditions and the following disclaimer.
14 | // 2. Redistributions in binary form must reproduce the above copyright notice,
15 | // this list of conditions and the following disclaimer in the documentation
16 | // and/or other materials provided with the distribution.
17 | //
18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 | //
29 | // The views and conclusions contained in the software and documentation are those
30 | // of the authors and should not be interpreted as representing official policies,
31 | // either expressed or implied, of the LabelKit project.
32 | //
33 |
34 | import UIKit
35 |
36 | @UIApplicationMain
37 | class AppDelegate: UIResponder, UIApplicationDelegate {
38 | var window: UIWindow?
39 |
40 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
41 | handler()
42 | return true
43 | }
44 |
45 | // MARK: UISceneSession Lifecycle
46 |
47 | @available(iOS 13.0, *)
48 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
49 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Example SwiftUI/TextGenerator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TextGenerator.swift
3 | // LabelKit - Example SwiftUI
4 | //
5 | // Copyright (c) 2019-2021 Eugene Dudnyk
6 | //
7 | // All rights reserved.
8 | //
9 | // Redistribution and use in source and binary forms, with or without
10 | // modification, are permitted provided that the following conditions are met:
11 | //
12 | // 1. Redistributions of source code must retain the above copyright notice, this
13 | // list of conditions and the following disclaimer.
14 | // 2. Redistributions in binary form must reproduce the above copyright notice,
15 | // this list of conditions and the following disclaimer in the documentation
16 | // and/or other materials provided with the distribution.
17 | //
18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 | //
29 | // The views and conclusions contained in the software and documentation are those
30 | // of the authors and should not be interpreted as representing official policies,
31 | // either expressed or implied, of the LabelKit project.
32 | //
33 |
34 | import Foundation
35 | import Combine
36 | import SwiftUI
37 |
38 | class TextGenerator: ObservableObject {
39 | @Published var attributedString: NSAttributedString = NSAttributedString.random()
40 | @Published var text = Text.random()
41 |
42 | private var attributedStringSubscription: Cancellable?
43 | private var textSubscription: Cancellable?
44 | init () {
45 | let autoconnect = Timer.publish(every: 3, on: .main, in: .common).autoconnect()
46 | attributedStringSubscription = autoconnect
47 | .receive(on: DispatchQueue.main)
48 | .map({ _ in
49 | return NSAttributedString.random()
50 | })
51 | .assign(to: \TextGenerator.attributedString, on: self)
52 | textSubscription = autoconnect
53 | .receive(on: DispatchQueue.main)
54 | .map({ _ in
55 | return Text.random()
56 | })
57 | .assign(to: \TextGenerator.text, on: self)
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Example iOS/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // LabelKit - Example iOS
4 | //
5 | // Copyright (c) 2019-2020 Eugene Dudnyk
6 | //
7 | // All rights reserved.
8 | //
9 | // Redistribution and use in source and binary forms, with or without
10 | // modification, are permitted provided that the following conditions are met:
11 | //
12 | // 1. Redistributions of source code must retain the above copyright notice, this
13 | // list of conditions and the following disclaimer.
14 | // 2. Redistributions in binary form must reproduce the above copyright notice,
15 | // this list of conditions and the following disclaimer in the documentation
16 | // and/or other materials provided with the distribution.
17 | //
18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 | //
29 | // The views and conclusions contained in the software and documentation are those
30 | // of the authors and should not be interpreted as representing official policies,
31 | // either expressed or implied, of the LabelKit project.
32 | //
33 |
34 | import LabelKit
35 | import UIKit
36 |
37 | class ViewController: UIViewController {
38 | var timer: Timer?
39 | @IBOutlet var label: LKLabel!
40 |
41 | override func viewWillAppear(_ animated: Bool) {
42 | super.viewWillAppear(animated)
43 | UIView.performWithoutAnimation {
44 | label.attributedText = NSAttributedString.createRandom()
45 | }
46 | startAnimation()
47 | }
48 |
49 | override func viewDidDisappear(_ animated: Bool) {
50 | super.viewDidDisappear(animated)
51 | stopAnimation()
52 | }
53 |
54 | func startAnimation() {
55 | guard timer == nil else { return }
56 | let timer = Timer(timeInterval: 3, target: self, selector: #selector(updateText), userInfo: nil, repeats: true)
57 | self.timer = timer
58 | RunLoop.current.add(timer, forMode: .default)
59 | updateText()
60 | }
61 |
62 | func stopAnimation() {
63 | timer?.invalidate()
64 | timer = nil
65 | }
66 |
67 | @objc func updateText() {
68 | view.setNeedsLayout()
69 | label.setNeedsLayout()
70 | UIView.animate(withDuration: 3, delay: 0, options: [], animations: {
71 | self.label.attributedText = NSAttributedString.createRandom()
72 | self.view.layoutIfNeeded()
73 | }, completion: nil)
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/LabelKit.xcodeproj/xcshareddata/xcschemes/LabelKit iOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
53 |
54 |
60 |
61 |
67 |
68 |
69 |
70 |
72 |
73 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/LabelKit.xcodeproj/xcshareddata/xcschemes/LabelKit tvOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
53 |
54 |
60 |
61 |
67 |
68 |
69 |
70 |
72 |
73 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/LabelKit.xcodeproj/xcshareddata/xcschemes/LabelKit Example tvOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
45 |
51 |
52 |
53 |
54 |
60 |
62 |
68 |
69 |
70 |
71 |
73 |
74 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/INSTALL.md:
--------------------------------------------------------------------------------
1 |
2 | # LabelKit
3 |
4 | A `UILabel` that does true animation of attributed text.
5 |
6 | Usage >> [`instructions`](https://github.com/edudnyk/LabelKit/blob/master/README.md) <<
7 |
8 | ## Installation
9 | - [CocoaPods](#cocoapods)
10 | - [Carthage](#carthage)
11 | - [Swift Package Manager](#swift-package-manager)
12 | - [Manually](#manually)
13 |
14 | #
15 |
16 | ### CocoaPods
17 |
18 | [CocoaPods](http://cocoapods.org) is a dependency manager for Swift and Objective-C Cocoa projects. You can install it with the following command:
19 |
20 | ```bash
21 | $ gem install cocoapods
22 | ```
23 |
24 | To give `LabelKit` a try with an example project, run the following command:
25 |
26 | ```bash
27 | $ pod try LabelKit
28 | ```
29 |
30 | To integrate `LabelKit` into your Xcode project, specify it in your `Podfile`:
31 |
32 | ```ruby
33 | source 'https://github.com/CocoaPods/Specs.git'
34 | platform :ios, '1.0'
35 | use_frameworks!
36 |
37 | target 'MyApp' do
38 | pod 'LabelKit', '~> 1.0'
39 | end
40 | ```
41 |
42 | Then, run the following command:
43 |
44 | ```bash
45 | $ pod install
46 | ```
47 |
48 | ### Carthage
49 |
50 | [Carthage](https://github.com/Carthage/Carthage) is a dependency manager that builds your dependencies and provides you with binary frameworks.
51 |
52 | To install Carthage with [Homebrew](http://brew.sh/) use the following command:
53 |
54 | ```bash
55 | $ brew update
56 | $ brew install carthage
57 | ```
58 | To integrate LabelKit into your Xcode project using Carthage, specify it in your `Cartfile`:
59 |
60 | ```ogdl
61 | github "edudnyk/LabelKit" ~> 1.0
62 | ```
63 | Build the framework:
64 |
65 | ```bash
66 | $ carthage update
67 | ```
68 | Then, drag the built `LabelKit.framework` into your Xcode project.
69 |
70 | ### Swift Package Manager
71 |
72 | "The [Swift Package Manager](https://swift.org/package-manager/) is a tool for managing the distribution of Swift code. It’s integrated with the Swift build system to automate the process of downloading, compiling, and linking dependencies."
73 |
74 | To integrate `LabelKit` into your project, specify it in your `Package.swift` file:
75 |
76 | ```swift
77 | let package = Package(
78 | name: "MyApp",
79 | dependencies: [
80 | .Package(url: "https://github.com/edudnyk/LabelKit.git", majorVersion: 1)
81 | ]
82 | )
83 | ```
84 |
85 | Then run:
86 |
87 | ```bash
88 | $ swift build
89 | ```
90 |
91 | Or, alternatively:
92 |
93 | ```bash
94 | $ swift package generate-xcodeproj
95 | ```
96 |
97 | ### Manually
98 |
99 | Drag `LabelKit.xcodeproj` into your Xcode project.
100 |
101 | > It should appear nested underneath your application's blue project icon.
102 |
103 | Click on the `+` button under the "Embedded Binaries" section of your app's target and select the `LabelKit.framework` that matches the desired platform.
104 |
105 | ## Credits
106 | Similarities to [FeedKit](https://github.com/nmdias/FeedKit)'s impecable setup instructions are not a coincidence 😃
107 |
108 | ## License
109 |
110 | LabelKit is released under the Simplified BSD license. See [LICENSE](https://github.com/edudnyk/LabelKit/blob/master/LICENSE) for details.
111 |
112 |
113 |
114 |
--------------------------------------------------------------------------------
/LabelKit.xcodeproj/xcshareddata/xcschemes/LabelKit Example SwiftUI.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
45 |
51 |
52 |
53 |
54 |
60 |
62 |
68 |
69 |
70 |
71 |
73 |
74 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/LabelKit.xcodeproj/xcshareddata/xcschemes/LabelKit Example iOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
44 |
46 |
52 |
53 |
54 |
55 |
61 |
63 |
69 |
70 |
71 |
72 |
74 |
75 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 
2 |
3 | [](https://travis-ci.org/edudnyk/LabelKit)
4 | [](https://cocoapods.org/pods/LabelKit)
5 | [](https://github.com/Carthage/Carthage)
6 | [](https://swift.org)
7 | [](https://github.com/edudnyk/LabelKit/releases)
8 |
9 | A `UILabel` that does true animation of attributed text.
10 |
11 | It animates all text attributes that are possible to animate, unlike `CATextLayer` that animates only font and color.
12 |
13 | It also has a great support of multiline text animations while keeping almost all the benefits of being `UILabel`.
14 |
15 | It uses CoreText text rendering instead of CoreGraphics text rendering of `CATextLayer`. That makes the text to look the same by advancement and line height as the text in regular `UILabel`.
16 |
17 | It is a great and simple building block for UI which implements material design.
18 |
19 | ## Features
20 | - [x] [Documentation](https://edudnyk.github.io/LabelKit/index.html)
21 | - [x] Unit Test Coverage
22 |
23 | ## Requirements
24 |
25 | 
26 | 
27 | 
28 | 
29 |
30 | Installation >> [`instructions`](https://github.com/edudnyk/LabelKit/blob/master/INSTALL.md) <<
31 |
32 | ## Usage
33 |
34 | You can use either `LKLabel` or `LKLabelLayer`, both support implicitly animatable text change.
35 | When the layer is hosted by `LKLabel`, animations of text during bounds change are more stable.
36 |
37 | Animating text change in `LKLabel` can be something like this:
38 | ```swift
39 | // Swift
40 | self.label.superview.setNeedsLayout()
41 | self.label.setNeedsLayout()
42 | UIView.animate(withDuration: 3, delay: 0, options: [], animations: {
43 | self.label.attributedText = attributedText
44 | self.label.superview.layoutIfNeeded()
45 | }, completion: nil)
46 | ```
47 |
48 | ```objective-c
49 | // Objective-C
50 | [self.label.superview setNeedsLayout];
51 | [self.label setNeedsLayout];
52 | [UIView animateWithDuration:3 delay:0 options:kNilOptions animations:^{
53 | self.label.attributedText = attributedText;
54 | [self.label.superview layoutIfNeeded];
55 | } completion:nil];
56 | ```
57 |
58 | Animating text change in `LKLabelLayer` can be something like this:
59 | ```swift
60 | // Swift
61 | CATransaction.begin()
62 | CATransaction.setAnimationDuration(3.0)
63 | labelLayer.attributedText = attributedText
64 | CATransaction.commit()
65 | ```
66 |
67 | ```objective-c
68 | // Objective-C
69 | [CATransaction begin];
70 | [CATransaction setAnimationDuration:3.0];
71 | labelLayer.attributedText = attributedText;
72 | [CATransaction commit];
73 | ```
74 |
75 | > Refer to the [`documentation`](https://edudnyk.github.io/LabelKit/index.html) for the detailed description of possibilities.
76 |
77 | ## License
78 |
79 | LabelKit is released under the Simplified BSD license. See [LICENSE](https://github.com/edudnyk/LabelKit/blob/master/LICENSE) for details.
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/Example iOS/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/Example SwiftUI/Example_SwiftUIApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Example_SwiftUIApp.swift
3 | // LabelKit - Example SwiftUI
4 | //
5 | // Copyright (c) 2019-2021 Eugene Dudnyk
6 | //
7 | // All rights reserved.
8 | //
9 | // Redistribution and use in source and binary forms, with or without
10 | // modification, are permitted provided that the following conditions are met:
11 | //
12 | // 1. Redistributions of source code must retain the above copyright notice, this
13 | // list of conditions and the following disclaimer.
14 | // 2. Redistributions in binary form must reproduce the above copyright notice,
15 | // this list of conditions and the following disclaimer in the documentation
16 | // and/or other materials provided with the distribution.
17 | //
18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 | //
29 | // The views and conclusions contained in the software and documentation are those
30 | // of the authors and should not be interpreted as representing official policies,
31 | // either expressed or implied, of the LabelKit project.
32 | //
33 |
34 | import SwiftUI
35 |
36 | struct ContentViewWrapper: View {
37 | var body: some View {
38 | ContentView().environmentObject(TextGenerator())
39 | }
40 | }
41 |
42 | /// If min iOS version is 14.0, use this:
43 |
44 | //@main
45 | //struct Example_SwiftUIApp: App {
46 | // var body: some Scene {
47 | // WindowGroup {
48 | // ContentViewWrapper()
49 | // }
50 | // }
51 | //}
52 |
53 | /// Otherwise
54 |
55 | @available(iOS 13.0, *)
56 | @UIApplicationMain
57 | class AppDelegate: UIResponder, UIApplicationDelegate {
58 | var window: UIWindow?
59 |
60 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
61 | return true
62 | }
63 |
64 | // MARK: UISceneSession Lifecycle
65 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
66 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
67 | }
68 | }
69 |
70 | @available(iOS 13.0, *)
71 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
72 | var window: UIWindow?
73 |
74 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
75 | guard let scene = scene as? UIWindowScene else { return }
76 | if let window = scene.windows.first {
77 | self.window = window
78 | } else {
79 | window = UIWindow(frame: UIScreen.main.bounds)
80 | window?.windowScene = scene
81 | }
82 | window?.rootViewController = UIHostingController(rootView: ContentViewWrapper())
83 | window?.makeKeyAndVisible()
84 | }
85 | }
86 |
87 | /// End if
88 |
--------------------------------------------------------------------------------
/Example tvOS/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 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/Sources/LabelKit/LKBoundsDidChangeAction.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LKBoundsDidChangeAnimation.swift
3 | // LabelKit
4 | //
5 | // Copyright (c) 2019-2021 Eugene Dudnyk
6 | //
7 | // All rights reserved.
8 | //
9 | // Redistribution and use in source and binary forms, with or without
10 | // modification, are permitted provided that the following conditions are met:
11 | //
12 | // 1. Redistributions of source code must retain the above copyright notice, this
13 | // list of conditions and the following disclaimer.
14 | // 2. Redistributions in binary form must reproduce the above copyright notice,
15 | // this list of conditions and the following disclaimer in the documentation
16 | // and/or other materials provided with the distribution.
17 | //
18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 | //
29 | // The views and conclusions contained in the software and documentation are those
30 | // of the authors and should not be interpreted as representing official policies,
31 | // either expressed or implied, of the LabelKit project.
32 | //
33 |
34 | import QuartzCore
35 | import UIKit
36 |
37 | class LKBoundsDidChangeAnimation: CABasicAnimation {
38 | @objc var bounds = CGRect.zero
39 |
40 | override func copy(with zone: NSZone? = nil) -> Any {
41 | let result = super.copy(with: zone)
42 | if let action = result as? Self {
43 | action.bounds = bounds
44 | return action
45 | }
46 | return result
47 | }
48 | }
49 |
50 | class LKBoundsDidChangeAction: CAAction {
51 | var pendingAnimation: LKBoundsDidChangeAnimation
52 |
53 | init(fromBounds: CGRect) {
54 | let rootContextKey = keyPath(\LKLabelLayer.currentBoundsDidChangeAnimation)
55 | let animation = LKBoundsDidChangeAnimation(keyPath:
56 | keyPath(rootContextKey, suffix: keyPath(\LKBoundsDidChangeAnimation.bounds)))
57 | animation.fromValue = fromBounds
58 | animation.duration = UIView.inheritedAnimationDuration > 0 ? UIView.inheritedAnimationDuration : CATransaction.animationDuration()
59 | animation.bounds = fromBounds
60 | animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
61 | pendingAnimation = animation
62 | }
63 |
64 | func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable: Any]?) {
65 | guard let textLayer = anObject as? LKLabelLayer, event == keyPath(\LKLabelLayer.bounds) else { return }
66 | if !pendingAnimation.bounds.equalTo(textLayer.bounds) {
67 | pendingAnimation.toValue = textLayer.bounds
68 | textLayer.add(pendingAnimation, forKey: keyPath(\LKBoundsDidChangeAnimation.bounds))
69 | }
70 | }
71 | }
72 |
73 | class LKCompositeAction: CAAction {
74 | var actions: [CAAction]
75 | init(actions: [CAAction]) {
76 | self.actions = actions
77 | }
78 |
79 | func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable: Any]?) {
80 | actions.forEach { action in
81 | action.run(forKey: event, object: anObject, arguments: dict)
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/Example SwiftUI/ContentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentView.swift
3 | // LabelKit - Example SwiftUI
4 | //
5 | // Copyright (c) 2019-2021 Eugene Dudnyk
6 | //
7 | // All rights reserved.
8 | //
9 | // Redistribution and use in source and binary forms, with or without
10 | // modification, are permitted provided that the following conditions are met:
11 | //
12 | // 1. Redistributions of source code must retain the above copyright notice, this
13 | // list of conditions and the following disclaimer.
14 | // 2. Redistributions in binary form must reproduce the above copyright notice,
15 | // this list of conditions and the following disclaimer in the documentation
16 | // and/or other materials provided with the distribution.
17 | //
18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 | //
29 | // The views and conclusions contained in the software and documentation are those
30 | // of the authors and should not be interpreted as representing official policies,
31 | // either expressed or implied, of the LabelKit project.
32 | //
33 |
34 | import LabelKit
35 | import SwiftUI
36 |
37 | struct ContentView: View {
38 | @EnvironmentObject var textGenerator: TextGenerator
39 | @State var swiftUITextToggled = false
40 | var body: some View {
41 | GeometryReader { geometry in
42 | VStack(spacing: 0) {
43 | ZStack(alignment: .top) {
44 | ScrollView(.vertical, showsIndicators: false) {
45 | HStack {
46 | if swiftUITextToggled {
47 | textGenerator.text
48 | .randomFont()
49 | .randomForegroundColor()
50 | .randomShadow()
51 | .animation(.easeInOut(duration: 3))
52 | .fixedSize(horizontal: false, vertical: true)
53 | } else {
54 | LabelView(attributedText: textGenerator.attributedString)
55 | .animation(.easeInOut(duration: 3))
56 | .fixedSize(horizontal: false, vertical: true)
57 | }
58 | }
59 | .background(Color(.white))
60 | .frame(minHeight: UIScreen.main.bounds.height)
61 | }
62 | Button(swiftUITextToggled ? "Animate NSAttributedString with LabelKit" : "Animate Text with SwiftUI") {
63 | swiftUITextToggled.toggle()
64 | }
65 | .foregroundColor(.white)
66 | .padding(EdgeInsets(top: 12, leading: 12, bottom: 12, trailing: 12))
67 | .background(Color(.red))
68 | }.background(Color(.white))
69 | Text(swiftUITextToggled ? "Animating Text with SwiftUI" : "Animating NSAttributedString with LabelKit")
70 | .frame(maxWidth: .infinity)
71 | .padding(EdgeInsets(top: 12, leading: 0, bottom: 12, trailing: 0))
72 | .layoutPriority(1)
73 | }
74 | .padding(.bottom, geometry.safeAreaInsets.bottom)
75 | .background(Color(.systemGray5))
76 | .frame(maxWidth: .infinity)
77 | }
78 | .edgesIgnoringSafeArea(.bottom)
79 | }
80 | }
81 |
82 | struct ContentView_Previews: PreviewProvider {
83 | static var previews: some View {
84 | ContentView().environmentObject(TextGenerator())
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/Sources/LabelKit/LabelView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LKLabel.swift
3 | // LabelKit
4 | //
5 | // Copyright (c) 2019-2021 Eugene Dudnyk
6 | //
7 | // All rights reserved.
8 | //
9 | // Redistribution and use in source and binary forms, with or without
10 | // modification, are permitted provided that the following conditions are met:
11 | //
12 | // 1. Redistributions of source code must retain the above copyright notice, this
13 | // list of conditions and the following disclaimer.
14 | // 2. Redistributions in binary form must reproduce the above copyright notice,
15 | // this list of conditions and the following disclaimer in the documentation
16 | // and/or other materials provided with the distribution.
17 | //
18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 | //
29 | // The views and conclusions contained in the software and documentation are those
30 | // of the authors and should not be interpreted as representing official policies,
31 | // either expressed or implied, of the LabelKit project.
32 |
33 | #if canImport(SwiftUI)
34 | import SwiftUI
35 |
36 | @available(iOS 13.0.0, *)
37 | @available(tvOS 13.0.0, *)
38 | public struct LabelView: View {
39 | public let attributedText: NSAttributedString?
40 |
41 | public init(attributedText: NSAttributedString?) {
42 | self.attributedText = attributedText
43 | }
44 |
45 | public init(text: String?) {
46 | guard let text = text else {
47 | attributedText = nil
48 | return
49 | }
50 | attributedText = NSAttributedString(string: text)
51 | }
52 |
53 | public var body: some View {
54 | internalLabelView
55 | .fixedSize(horizontal: false, vertical: true)
56 | }
57 |
58 | var internalLabelView: some View {
59 | InternalLabelView(attributedText: attributedText)
60 | }
61 | }
62 |
63 | @available(iOS 13.0.0, *)
64 | @available(tvOS 13.0.0, *)
65 | private struct InternalLabelView: UIViewRepresentable {
66 | private static let durationRegEx = try? NSRegularExpression(pattern: "duration: ([\\d\\.]*),",
67 | options: [.caseInsensitive])
68 |
69 | typealias UIViewType = LKLabel
70 |
71 | var attributedText: NSAttributedString?
72 | var numberOfLines: Int
73 |
74 | init(attributedText: NSAttributedString?, numberOfLines: Int = 0) {
75 | self.attributedText = attributedText
76 | self.numberOfLines = numberOfLines
77 | }
78 |
79 | func makeUIView(context: Context) -> UIViewType {
80 | let label = UIViewType()
81 | UIView.performWithoutAnimation {
82 | label.numberOfLines = numberOfLines
83 | label.attributedText = attributedText
84 | label.setContentHuggingPriority(.defaultHigh, for: .vertical)
85 | }
86 | return label
87 | }
88 |
89 | func updateUIView(_ label: UIViewType, context: Context) {
90 | label.numberOfLines = numberOfLines
91 | let transaction = context.transaction
92 | var duration: Double = 0
93 | if let animation = transaction.animation {
94 | let animationDescription = String(describing: animation)
95 | if animationDescription.count > 0,
96 | let durationResult = Self.durationRegEx?.firstMatch(in: animationDescription,
97 | options: [],
98 | range: NSRange(location: 0,
99 | length: animationDescription.count)),
100 | durationResult.numberOfRanges > 0
101 | {
102 | let matchRange = durationResult.range(at: 1)
103 | duration = animationDescription.lk_substringAsDouble(matchRange)
104 | }
105 | }
106 |
107 | let setter = {
108 | label.attributedText = attributedText
109 | }
110 |
111 | if duration > 0 && !transaction.disablesAnimations {
112 | UIView.animate(withDuration: duration, delay: 0, options: .curveEaseInOut, animations: setter, completion: nil)
113 | } else {
114 | UIView.performWithoutAnimation(setter)
115 | }
116 | }
117 |
118 | func _overrideSizeThatFits(_ size: inout CGSize, in proposedSize: _ProposedSize, uiView: UIViewType) {
119 | let mirror = Mirror(reflecting: proposedSize)
120 | let width = mirror.children.first?.value as? CGFloat
121 | uiView.preferredMaxLayoutWidth = width ?? 0
122 | size = uiView.intrinsicContentSize
123 | }
124 | }
125 |
126 | extension String {
127 | func lk_substring(_ range: NSRange) -> Substring {
128 | let start = index(startIndex, offsetBy: range.location)
129 | let end = index(start, offsetBy: range.length)
130 | return self[start ..< end]
131 | }
132 |
133 | func lk_substringAsDouble(_ range: NSRange) -> Double {
134 | return Double(lk_substring(range)) ?? 0
135 | }
136 | }
137 |
138 | @available(iOS 13.0.0, *)
139 | @available(tvOS 13.0.0, *)
140 | public struct LabelView_Previews: PreviewProvider {
141 | public static var previews: some View {
142 | HStack {
143 | LabelView(attributedText: NSAttributedString(string: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis scelerisque semper diam in pharetra. Nullam ultrices varius enim ac ultrices. In convallis felis leo, sit amet mollis nisl sodales in. Mauris ut bibendum tortor. Praesent sollicitudin lacus nec lorem finibus convallis. Quisque quis ultricies ante, id malesuada nibh. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Quisque a vehicula ligula. Duis quis mauris porttitor, pulvinar elit ut, maximus dolor. Cras et sem vitae nisl accumsan porta non in quam. Nullam molestie, ipsum ut convallis iaculis, dui nibh facilisis mi, quis porta mi sem nec augue. Nulla elit sem, tempor id elit vitae, bibendum lacinia urna."))
144 | }
145 | }
146 | }
147 | #endif
148 |
--------------------------------------------------------------------------------
/Sources/LabelKit/LKLabel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LKLabel.swift
3 | // LabelKit
4 | //
5 | // Copyright (c) 2019-2021 Eugene Dudnyk
6 | //
7 | // All rights reserved.
8 | //
9 | // Redistribution and use in source and binary forms, with or without
10 | // modification, are permitted provided that the following conditions are met:
11 | //
12 | // 1. Redistributions of source code must retain the above copyright notice, this
13 | // list of conditions and the following disclaimer.
14 | // 2. Redistributions in binary form must reproduce the above copyright notice,
15 | // this list of conditions and the following disclaimer in the documentation
16 | // and/or other materials provided with the distribution.
17 | //
18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 | //
29 | // The views and conclusions contained in the software and documentation are those
30 | // of the authors and should not be interpreted as representing official policies,
31 | // either expressed or implied, of the LabelKit project.
32 | //
33 |
34 | import UIKit
35 |
36 | /// A subclass of `UILabel` that displays one or more lines of read-only text, often used in conjunction with controls to describe their intended purpose.
37 | /// `allowsDefaultTighteningForTruncation` and `baselineAdjustment` properties are not supported.
38 | /// Only `.byTruncatingTail` line break mode is supported.
39 | @objc
40 | open class LKLabel: UILabel {
41 | @objc
42 | public weak var delegate: AnyObject?
43 |
44 | override open class var layerClass: AnyClass {
45 | return LKLabelLayer.self
46 | }
47 |
48 | var labelLayer: LKLabelLayer? {
49 | return self.layer as? LKLabelLayer
50 | }
51 |
52 | override public init(frame: CGRect) {
53 | super.init(frame: frame)
54 | internalInit()
55 | }
56 |
57 | public required init?(coder: NSCoder) {
58 | super.init(coder: coder)
59 | internalInit()
60 | }
61 |
62 | private func internalInit() {
63 | labelLayer?.isOpaque = false
64 | labelLayer?.needsDisplayOnBoundsChange = true
65 | labelLayer?.contentsScale = UIScreen.main.scale
66 | let minimumScaleFactor = self.minimumScaleFactor
67 | let numberOfLines = self.numberOfLines
68 | self.minimumScaleFactor = minimumScaleFactor
69 | self.numberOfLines = numberOfLines
70 | isOpaque = false
71 | isUserInteractionEnabled = false
72 | contentMode = .redraw
73 | }
74 |
75 | /// The underlying attributed string drawn by the label, if set, the label ignores the `font`, `textColor`, `shadowColor`, and `shadowOffset` properties.
76 | /// If `.paragraphStyle` attribute is absent in the attributed string, it is created incorporating the label's `textAlignment` property.
77 | /// Animatable.
78 | @objc override open var attributedText: NSAttributedString? {
79 | didSet(previousValue) {
80 | CATransaction.begin()
81 | CATransaction.setAnimationDuration(UIView.inheritedAnimationDuration)
82 | labelLayer?.attributedText = alignedAttributedText(attributedText, defaultFont: font)
83 | CATransaction.commit()
84 | }
85 | }
86 |
87 | /// The current text that is displayed by the label.
88 | /// Animatable.
89 | @objc
90 | override open var text: String? {
91 | didSet(previousValue) {
92 | CATransaction.begin()
93 | CATransaction.setAnimationDuration(UIView.inheritedAnimationDuration)
94 | labelLayer?.attributedText = alignedAttributedText(attributedText, defaultFont: font)
95 | CATransaction.commit()
96 | }
97 | }
98 |
99 | @objc
100 | override open var backgroundColor: UIColor? {
101 | didSet(previousValue) {
102 | labelLayer?.backgroundColor = backgroundColor?.cgColor ?? UIColor.clear.cgColor
103 | }
104 | }
105 |
106 | /// Triggers bounds animation which provides public access to interpolated bounds during the text animation.
107 | @objc
108 | override open func action(for layer: CALayer, forKey event: String) -> CAAction? {
109 | let result = super.action(for: layer, forKey: event)
110 | if event == keyPath(\CALayer.bounds), result != nil, UIView.inheritedAnimationDuration > 0 {
111 | let textDrawingBoundsAction = LKBoundsDidChangeAction(fromBounds: layer.bounds)
112 | let action = LKCompositeAction(actions: [result!, textDrawingBoundsAction])
113 | return action
114 | }
115 | return result
116 | }
117 |
118 | /// Draws the text. Called by the layer, must not be explicitly called by consumers.
119 | @objc
120 | override open func display(_ layer: CALayer) {
121 | if let labelLayer = layer as? LKLabelLayer {
122 | let textDrawingBoundsAction = labelLayer.currentBoundsDidChangeAnimation
123 | let rect = textDrawingBoundsAction?.bounds ?? bounds
124 | let limit = CGFloat(UINT16_MAX)
125 | if rect.isEmpty || rect.size.width > limit || rect.size.height > limit {
126 | labelLayer.contents = nil
127 | return
128 | } else {
129 | UIGraphicsBeginImageContextWithOptions(rect.size, false, UIScreen.main.scale)
130 | drawText(in: rect)
131 | layer.contents = UIGraphicsGetImageFromCurrentImageContext()?.cgImage
132 | UIGraphicsEndImageContext()
133 | return
134 | }
135 | }
136 | super.display(layer)
137 | }
138 |
139 | private func alignedAttributedText(_ attributedText: NSAttributedString?, defaultFont: UIFont? = nil) -> NSAttributedString? {
140 | guard let attributedText = attributedText else { return nil }
141 | var skipParagraphStyleSetup = false
142 | var skipFontSetup = defaultFont == nil
143 | let range = NSRange(location: 0, length: attributedText.length)
144 | attributedText.enumerateAttributes(in: range, options: []) { value, _, stop in
145 | skipParagraphStyleSetup = skipParagraphStyleSetup || value[.paragraphStyle] != nil
146 | skipFontSetup = skipFontSetup || value[.font] != nil
147 | stop.assign(repeating: ObjCBool(skipParagraphStyleSetup && skipFontSetup), count: 1)
148 | }
149 | if !skipFontSetup || !skipParagraphStyleSetup {
150 | let mutableText = NSMutableAttributedString(attributedString: attributedText)
151 | var attributes = [NSAttributedString.Key: Any]()
152 | if !skipParagraphStyleSetup {
153 | let paragraphStyle = NSMutableParagraphStyle()
154 | paragraphStyle.alignment = textAlignment
155 | attributes[.paragraphStyle] = paragraphStyle
156 | }
157 | if !skipFontSetup {
158 | attributes[.font] = defaultFont
159 | }
160 | mutableText.addAttributes(attributes, range: range)
161 | return mutableText
162 | } else {
163 | return attributedText
164 | }
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/Example iOS/NSAttributedString+Random.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSAttributedString+Random.swift
3 | // LabelKit - Example iOS
4 | //
5 | // Copyright (c) 2019-2021 Eugene Dudnyk
6 | //
7 | // All rights reserved.
8 | //
9 | // Redistribution and use in source and binary forms, with or without
10 | // modification, are permitted provided that the following conditions are met:
11 | //
12 | // 1. Redistributions of source code must retain the above copyright notice, this
13 | // list of conditions and the following disclaimer.
14 | // 2. Redistributions in binary form must reproduce the above copyright notice,
15 | // this list of conditions and the following disclaimer in the documentation
16 | // and/or other materials provided with the distribution.
17 | //
18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 | //
29 | // The views and conclusions contained in the software and documentation are those
30 | // of the authors and should not be interpreted as representing official policies,
31 | // either expressed or implied, of the LabelKit project.
32 | //
33 |
34 | import UIKit
35 |
36 | let strings = ["Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis scelerisque semper diam in pharetra. Nullam ultrices varius enim ac ultrices. In convallis felis leo, sit amet mollis nisl sodales in. Mauris ut bibendum tortor. Praesent sollicitudin lacus nec lorem finibus convallis. Quisque quis ultricies ante, id malesuada nibh. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Quisque a vehicula ligula. Duis quis mauris porttitor, pulvinar elit ut, maximus dolor. Cras et sem vitae nisl accumsan porta non in quam. Nullam molestie, ipsum ut convallis iaculis, dui nibh facilisis mi, quis porta mi sem nec augue. Nulla elit sem, tempor id elit vitae, bibendum lacinia urna.",
37 |
38 | "Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nam sed sapien lobortis, posuere neque sit amet, vulputate nisl. Nullam quis enim venenatis, porttitor risus nec, ullamcorper dolor. Pellentesque dictum risus lacus, a sodales elit bibendum eu. Vivamus sit amet dignissim ante. Nunc imperdiet porttitor commodo. Aenean in hendrerit est. Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec et purus id diam blandit varius. Pellentesque orci est, varius non fermentum vitae, condimentum eget ex. Mauris placerat, elit non blandit pretium, risus neque suscipit felis, vel facilisis est eros non turpis.",
39 |
40 | " شيء آخر بالضبط ما كان لدينا. لا توجد كتلة ، الاتحاد الأوروبي ، ولكن ، للراحة ، بيئية ، وهذا هو. في شبكات العقارات الاحماء. لا نفسها. تصنيع دعاية الصلصة. الآن ابتسامة الفلفل الحار ، عطلة نهاية الأسبوع تشعل الكرتون في كرة القدم ولكن ، سابين. السريرية المستهدفة في شركة",
41 |
42 | "Curabitur ornare ex quis nunc eleifend efficitur. Donec nisl nulla, eleifend eget justo vel, rhoncus condimentum risus. Nulla erat nunc, lacinia sed commodo a, finibus efficitur ipsum. Ut ut maximus augue, vel consequat lacus. Integer quis aliquet quam, at condimentum dui. Pellentesque quis pellentesque lectus, at luctus quam. Vestibulum libero ligula, blandit non metus in, sollicitudin lacinia nisi. Morbi nulla arcu, cursus sed erat at, maximus finibus libero. Proin efficitur, diam nec blandit feugiat, erat nisi aliquet elit, ut venenatis augue ante et tortor. Maecenas finibus, dui id malesuada interdum, massa risus convallis dui, in sagittis lorem justo a elit.",
43 |
44 | " شيء آخر بالضبط ما كان لدينا. لا توجد كتلة ، الاتحاد الأوروبي ، ولكن ، للراحة ، بيئية ، وهذا هو. في شبكات العقارات الاحماء. لا نفسها. تصنيع دعاية الصلصة. الآن ابتسامة الفلفل الحار ، عطلة نهاية الأسبوع تشعل الكرتون في كرة القدم ولكن ، سابين. السريرية المستهدفة في شركة"]
45 |
46 |
47 | extension NSAttributedString {
48 |
49 | static func random() -> NSAttributedString {
50 | let idx = Int(round(rnd() * CGFloat(strings.count - 1)))
51 | let string = strings[idx]
52 | let attributedText = NSMutableAttributedString(string: string)
53 | attributedText.beginEditing()
54 | attributedText.addAttributes([.foregroundColor: randomColor(),
55 | .font: randomFont(),
56 | .shadow: randomShadow()], range: NSRange(string.startIndex ..< string.endIndex, in: string))
57 | attributedText.endEditing()
58 | return attributedText
59 | }
60 |
61 | private static func randomFont() -> UIFont {
62 | let pointSize = 20.0 + rnd() * 22.0
63 | let weigth = UIFont.Weight(CGFloat(0)) // UIFont.Weight(CGFloat(rnd() * 2 - 1))
64 | return UIFont.systemFont(ofSize: pointSize, weight: weigth)
65 | }
66 |
67 | private static func randomColor() -> UIColor {
68 | return UIColor(red: rnd() * 0.6, green: rnd() * 0.6, blue: rnd() * 0.6, alpha: 1)
69 | }
70 |
71 | private static func randomShadow() -> NSShadow {
72 | let shadow = NSShadow()
73 | shadow.shadowColor = randomColor()
74 | shadow.shadowBlurRadius = rnd() * 5
75 | shadow.shadowOffset = CGSize(width: rnd() * -5, height: rnd() * -5)
76 | return shadow
77 | }
78 | }
79 |
80 | fileprivate func rnd() -> CGFloat {
81 | return CGFloat(arc4random()) / CGFloat(UINT32_MAX)
82 | }
83 |
84 | fileprivate extension NSRange {
85 | private init(string: String, lowerBound: String.Index, upperBound: String.Index) {
86 | let utf16 = string.utf16
87 |
88 | guard let lowerBound = lowerBound.samePosition(in: utf16),
89 | let upperBound = upperBound.samePosition(in: utf16)
90 | else {
91 | self.init(location: NSNotFound, length: 0)
92 | return
93 | }
94 | let location = utf16.distance(from: utf16.startIndex, to: lowerBound)
95 | let length = utf16.distance(from: lowerBound, to: upperBound)
96 |
97 | self.init(location: location, length: length)
98 | }
99 |
100 | init(range: Range, in string: String) {
101 | self.init(string: string, lowerBound: range.lowerBound, upperBound: range.upperBound)
102 | }
103 |
104 | init(range: ClosedRange, in string: String) {
105 | self.init(string: string, lowerBound: range.lowerBound, upperBound: range.upperBound)
106 | }
107 | }
108 |
109 | #if canImport(SwiftUI)
110 | import SwiftUI
111 |
112 |
113 | @available(iOS 13.0.0, *)
114 | @available(tvOS 13.0.0, *)
115 | public extension Text {
116 | static func random() -> Text {
117 | let idx = Int(round(rnd() * CGFloat(strings.count - 1)))
118 | let string = strings[idx]
119 | return Text(string)
120 | }
121 | }
122 |
123 | extension View {
124 | func randomShadow() -> some View {
125 | shadow(color: randomColor, radius: rnd() * 5, x: rnd() * -5, y: rnd() * -5)
126 | }
127 |
128 | func randomFont() -> some View {
129 | let pointSize = 20.0 + rnd() * 22.0
130 | // let weigth = Font.Weight(CGFloat(rnd() * 2 - 1))
131 | return font(Font.system(size: pointSize))
132 | }
133 |
134 | func randomForegroundColor() -> some View {
135 | foregroundColor(randomColor)
136 | }
137 |
138 | var randomColor: Color {
139 | Color(red: Double(rnd()) * 0.6, green: Double(rnd()) * 0.6, blue: Double(rnd()) * 0.6, opacity: 1)
140 | }
141 | }
142 |
143 | #endif
144 |
--------------------------------------------------------------------------------
/Sources/LabelKit/LKExtensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LKExtensions.swift
3 | // LabelKit
4 | //
5 | // Copyright (c) 2019-2021 Eugene Dudnyk
6 | //
7 | // All rights reserved.
8 | //
9 | // Redistribution and use in source and binary forms, with or without
10 | // modification, are permitted provided that the following conditions are met:
11 | //
12 | // 1. Redistributions of source code must retain the above copyright notice, this
13 | // list of conditions and the following disclaimer.
14 | // 2. Redistributions in binary form must reproduce the above copyright notice,
15 | // this list of conditions and the following disclaimer in the documentation
16 | // and/or other materials provided with the distribution.
17 | //
18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 | //
29 | // The views and conclusions contained in the software and documentation are those
30 | // of the authors and should not be interpreted as representing official policies,
31 | // either expressed or implied, of the LabelKit project.
32 | //
33 |
34 | import UIKit
35 |
36 | func keyPath(_ base: Any, suffix: Any) -> String {
37 | return "\(base).\(suffix)"
38 | }
39 |
40 | func keyPath(_ base: Any, range: NSRange) -> String {
41 | return keyPath(base, suffix: NSStringFromRange(range))
42 | }
43 |
44 | func keyPath(_ keyPath: KeyPath) ->String {
45 | return NSExpression(forKeyPath: keyPath).keyPath
46 | }
47 |
48 | func keyPathCocoa(_ keyPath: KeyPath) -> NSString {
49 | return NSExpression(forKeyPath: keyPath).keyPath as NSString
50 | }
51 |
52 | protocol LKNSDictionaryCoding {
53 | static func lk_encode(object: AnyObject?) -> NSMutableDictionary
54 | static func lk_decode(dictionaryRepresentation dictionary: NSDictionary?)->Self
55 | }
56 |
57 | extension UIColor: LKNSDictionaryCoding {
58 | static func lk_encode(object: AnyObject?) -> NSMutableDictionary {
59 | let color = object as? UIColor
60 | var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0
61 | if color?.getRed(&red, green: &green, blue: &blue, alpha: &alpha) == false {
62 | _ = color?.getWhite(&red, alpha: &alpha)
63 | green = red
64 | blue = red
65 | }
66 | let result = NSMutableDictionary(sharedKeySet: NSMutableDictionary.sharedKeySet(forKeys: [NSString("r"), NSString("g"), NSString("b"), NSString("a")]))
67 | result["r"] = red
68 | result["g"] = green
69 | result["b"] = blue
70 | result["a"] = alpha
71 | return result
72 | }
73 |
74 | static func lk_decode(dictionaryRepresentation dictionary: NSDictionary?) -> Self {
75 | var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0
76 | if let redValue = dictionary?["r"] as? CGFloat {
77 | red = redValue
78 | }
79 | if let greenValue = dictionary?["g"] as? CGFloat {
80 | green = greenValue
81 | }
82 | if let blueValue = dictionary?["b"] as? CGFloat {
83 | blue = blueValue
84 | }
85 | if let alphaValue = dictionary?["a"] as? CGFloat {
86 | alpha = alphaValue
87 | }
88 | return Self(red: red, green: green, blue: blue, alpha: alpha)
89 | }
90 | }
91 |
92 | extension UIFont: LKNSDictionaryCoding {
93 | static func lk_encode(object: AnyObject?) -> NSMutableDictionary {
94 | let font = object as? UIFont ?? UIFont.preferredFont(forTextStyle: .body)
95 | let traits = font.fontDescriptor.object(forKey: UIFontDescriptor.AttributeName.traits) as? [UIFontDescriptor.TraitKey: Any] ?? [:]
96 | let weight = traits[.weight] as? UIFont.Weight ?? UIFont.Weight(0)
97 | let dictionary = NSMutableDictionary(dictionary: ["pointSize": font.pointSize,
98 | "weight": weight.rawValue])
99 | return dictionary
100 | }
101 |
102 | static func lk_decode(dictionaryRepresentation dictionary: NSDictionary?) -> Self {
103 | guard let pointSize = dictionary?["pointSize"] as? CGFloat, let weight = dictionary?["weight"] as? CGFloat else {
104 | return UIFont.preferredFont(forTextStyle: .body) as! Self
105 | }
106 | return UIFont.systemFont(ofSize: pointSize, weight: UIFont.Weight(rawValue: weight)) as! Self
107 | }
108 | }
109 |
110 | extension NSParagraphStyle: LKNSDictionaryCoding {
111 | static func lk_encode(object: AnyObject?) -> NSMutableDictionary {
112 | let paragraphStyle = object as? NSParagraphStyle ?? NSParagraphStyle.default
113 | return NSMutableDictionary(dictionary: paragraphStyle.dictionaryWithValues(forKeys: [keyPath(\NSParagraphStyle.lineSpacing),
114 | keyPath(\NSParagraphStyle.paragraphSpacing),
115 | keyPath(\NSParagraphStyle.alignment),
116 | keyPath(\NSParagraphStyle.headIndent),
117 | keyPath(\NSParagraphStyle.tailIndent),
118 | keyPath(\NSParagraphStyle.firstLineHeadIndent),
119 | keyPath(\NSParagraphStyle.minimumLineHeight),
120 | keyPath(\NSParagraphStyle.maximumLineHeight),
121 | keyPath(\NSParagraphStyle.lineBreakMode),
122 | keyPath(\NSParagraphStyle.baseWritingDirection),
123 | keyPath(\NSParagraphStyle.lineHeightMultiple),
124 | keyPath(\NSParagraphStyle.paragraphSpacingBefore),
125 | keyPath(\NSParagraphStyle.hyphenationFactor),
126 | keyPath(\NSParagraphStyle.defaultTabInterval),
127 | keyPath(\NSParagraphStyle.allowsDefaultTighteningForTruncation)]))
128 | }
129 |
130 | static func lk_decode(dictionaryRepresentation dictionary: NSDictionary?) -> Self {
131 | let mutableParagraphStyle = NSMutableParagraphStyle()
132 | mutableParagraphStyle.setValuesForKeys(dictionary as! [String: Any])
133 | return mutableParagraphStyle as NSParagraphStyle as! Self
134 | }
135 | }
136 |
137 | extension NSShadow: LKNSDictionaryCoding {
138 | static func lk_encode(object: AnyObject?) -> NSMutableDictionary {
139 | let result = NSMutableDictionary(sharedKeySet: NSMutableDictionary.sharedKeySet(forKeys: [keyPathCocoa(\NSShadow.shadowOffset),
140 | keyPathCocoa(\NSShadow.shadowBlurRadius),
141 | keyPathCocoa(\NSShadow.shadowColor)]))
142 | let shadow = object as? NSShadow ?? NSShadow()
143 | result[keyPath(\NSShadow.shadowOffset)] = shadow.shadowOffset
144 | result[keyPath(\NSShadow.shadowBlurRadius)] = shadow.shadowBlurRadius
145 | let color = shadow.shadowColor as? UIColor ?? UIColor.clear
146 | result[keyPath(\NSShadow.shadowColor)] = UIColor.lk_encode(object: color)
147 | return result
148 | }
149 |
150 | static func lk_decode(dictionaryRepresentation dictionary: NSDictionary?) -> Self {
151 | let color = UIColor.lk_decode(dictionaryRepresentation: dictionary?[keyPath(\NSShadow.shadowColor)] as? NSDictionary)
152 | let result = Self()
153 | var shadowOffset = CGSize.zero
154 | if let shadowOffsetValue = dictionary?[keyPath(\NSShadow.shadowOffset)] as? CGSize {
155 | shadowOffset = shadowOffsetValue
156 | }
157 | result.shadowOffset = shadowOffset
158 | var shadowBlurRadius: CGFloat = 0.0
159 | if let shadowBlurRadiusValue = dictionary?[keyPath(\NSShadow.shadowBlurRadius)] as? CGFloat {
160 | shadowBlurRadius = shadowBlurRadiusValue
161 | }
162 | result.shadowBlurRadius = shadowBlurRadius
163 | result.shadowColor = color
164 | return result
165 | }
166 | }
167 |
168 | extension LKLabelLayer {
169 | @objc var currentTextDidChangeAnimation: LKTextDidChangeAction? {
170 | return animation(forKey: keyPath(\LKLabelLayer.attributedText)) as? LKTextDidChangeAction
171 | }
172 |
173 | @objc var currentBoundsDidChangeAnimation: LKBoundsDidChangeAnimation? {
174 | return animation(forKey: keyPath(\LKBoundsDidChangeAnimation.bounds)) as? LKBoundsDidChangeAnimation
175 | }
176 | }
177 |
178 | extension CABasicAnimation {
179 | convenience init(forKeyPath keyPath: String, fromValue: Any?, toValue: Any?, duration: TimeInterval) {
180 | self.init(keyPath: keyPath)
181 | self.fromValue = fromValue
182 | self.toValue = toValue
183 | self.duration = duration
184 | }
185 | }
186 |
--------------------------------------------------------------------------------
/Sources/LabelKit/LKLabelLayer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LKLabelLayer.swift
3 | // LabelKit
4 | //
5 | // Copyright (c) 2019-2021 Eugene Dudnyk
6 | //
7 | // All rights reserved.
8 | //
9 | // Redistribution and use in source and binary forms, with or without
10 | // modification, are permitted provided that the following conditions are met:
11 | //
12 | // 1. Redistributions of source code must retain the above copyright notice, this
13 | // list of conditions and the following disclaimer.
14 | // 2. Redistributions in binary form must reproduce the above copyright notice,
15 | // this list of conditions and the following disclaimer in the documentation
16 | // and/or other materials provided with the distribution.
17 | //
18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 | //
29 | // The views and conclusions contained in the software and documentation are those
30 | // of the authors and should not be interpreted as representing official policies,
31 | // either expressed or implied, of the LabelKit project.
32 | //
33 |
34 | import QuartzCore
35 | import UIKit
36 |
37 | @objc
38 | open class LKLabelLayer: CALayer {
39 | fileprivate var stringDrawingContext: NSStringDrawingContext!
40 | fileprivate var stringDrawingOptions: NSStringDrawingOptions!
41 | private weak var textPresentationLayer: LKLabelLayer!
42 | public var adjustsTextForCustomLineHeight = false
43 |
44 | /// The underlying attributed string drawn by the label layer.
45 | /// Animatable.
46 | @NSManaged
47 | open dynamic var attributedText: NSAttributedString?
48 |
49 | override public init() {
50 | super.init()
51 | commonInit()
52 | }
53 |
54 | public required init?(coder: NSCoder) {
55 | super.init(coder: coder)
56 | commonInit()
57 | }
58 |
59 | override private init(layer: Any) {
60 | super.init(layer: layer)
61 | masksToBounds = false
62 | guard let textLayer = layer as? LKLabelLayer, let recentLayer = textLayer.textPresentationLayer != nil ? textLayer.textPresentationLayer : textLayer else { return }
63 | stringDrawingOptions = recentLayer.stringDrawingOptions
64 | stringDrawingContext = recentLayer.stringDrawingContext
65 | textLayer.textPresentationLayer = self
66 | }
67 |
68 | private func commonInit() {
69 | stringDrawingOptions = [.usesFontLeading, .truncatesLastVisibleLine]
70 | stringDrawingContext = NSStringDrawingContext()
71 | needsDisplayOnBoundsChange = true
72 | isOpaque = false
73 | }
74 |
75 | /// Triggers attributed text change implicit animation.
76 | /// If layer is used without `LKLabel`, also triggers supporting bounds change action.
77 | @objc
78 | override open func action(forKey event: String) -> CAAction? {
79 | if event == keyPath(\LKLabelLayer.attributedText) {
80 | let action = textPresentationLayer?.currentTextDidChangeAnimation
81 | let fromText = action != nil ? action!.interpolatedFromAlpha > action!.interpolatedToAlpha ? action!.interpolatedFromAttributedText : action!.interpolatedToAttributedText : attributedText
82 | return LKTextDidChangeAction(from: fromText)
83 | }
84 | let superAction = super.action(forKey: event)
85 | guard event == keyPath(\CALayer.bounds),
86 | type(of: superAction) != LKCompositeAction.self,
87 | UIView.inheritedAnimationDuration > 0 else { return superAction }
88 | let textDrawingBoundsAction = LKBoundsDidChangeAction(fromBounds: bounds)
89 | let action: CAAction = superAction != nil ? LKCompositeAction(actions: [superAction!, textDrawingBoundsAction]) : textDrawingBoundsAction
90 | return action
91 | }
92 |
93 | @objc
94 | override open func preferredFrameSize() -> CGSize {
95 | let result = attributedText?.boundingRect(with: CGSize(width: bounds.width, height: CGFloat.greatestFiniteMagnitude), options: stringDrawingOptions, context: stringDrawingContext).size
96 | return CGSize(width: ceil((result?.width ?? 0) + 1), height: ceil((result?.height ?? 0) + 1))
97 | }
98 |
99 | @objc
100 | override open func draw(in ctx: CGContext) {
101 | drawText(in: ctx.boundingBoxOfClipPath)
102 | }
103 |
104 | fileprivate func drawText(in rect: CGRect) {
105 | guard let ctx = UIGraphicsGetCurrentContext() else { return }
106 | let textChangeAction = currentTextDidChangeAnimation
107 | let toAlpha = textChangeAction?.interpolatedToAlpha ?? 1.0
108 | let modelLayer = model()
109 | let toDrawingText = textChangeAction?.interpolatedToAttributedText ?? modelLayer.attributedText
110 | let fromAlpha = textChangeAction?.interpolatedFromAlpha ?? 0
111 | if fromAlpha > 0 || toAlpha > 0 {
112 | ctx.setAllowsAntialiasing(true)
113 | ctx.setAllowsFontSmoothing(true)
114 | ctx.setAllowsFontSubpixelPositioning(true)
115 | ctx.setAllowsFontSubpixelQuantization(false)
116 | ctx.setShouldAntialias(true)
117 | ctx.setShouldSmoothFonts(true)
118 | ctx.setShouldSubpixelPositionFonts(true)
119 | var stringDrawingOptions = self.stringDrawingOptions ?? []
120 | if modelLayer != self {
121 | stringDrawingOptions = [stringDrawingOptions, .truncatesLastVisibleLine]
122 | }
123 | if fromAlpha > 0 {
124 | let fromDrawingText = textChangeAction?.interpolatedFromAttributedText
125 | if fromAlpha < 1 {
126 | ctx.beginTransparencyLayer(auxiliaryInfo: nil)
127 | ctx.setAlpha(fromAlpha)
128 | }
129 | let fromRect = adjust(drawingRect: rect, for: fromDrawingText)
130 | fromDrawingText?.draw(with: fromRect, options: stringDrawingOptions, context: stringDrawingContext)
131 | if fromAlpha < 1 {
132 | ctx.endTransparencyLayer()
133 | }
134 | }
135 | if toAlpha > 0 {
136 | if toAlpha < 1 {
137 | ctx.beginTransparencyLayer(auxiliaryInfo: nil)
138 | ctx.setAlpha(toAlpha)
139 | }
140 | let toRect = adjust(drawingRect: rect, for: toDrawingText)
141 | toDrawingText?.draw(with: toRect, options: stringDrawingOptions, context: stringDrawingContext)
142 | if toAlpha < 1 {
143 | ctx.endTransparencyLayer()
144 | }
145 | }
146 | }
147 | }
148 |
149 | @objc
150 | override open class func needsDisplay(forKey key: String) -> Bool {
151 | var result = super.needsDisplay(forKey: key)
152 | result = result || key == keyPath(\LKLabelLayer.attributedText) ||
153 | key == keyPath(\LKLabelLayer.currentTextDidChangeAnimation) ||
154 | key == keyPath(\LKLabelLayer.currentBoundsDidChangeAnimation) ||
155 | key == keyPath(\LKLabelLayer.bounds)
156 | return result
157 | }
158 |
159 | private func adjust(drawingRect: CGRect, for text: NSAttributedString?) -> CGRect {
160 | guard let text = text, text.length > 0 else { return drawingRect }
161 | var result = drawingRect
162 | if stringDrawingOptions.contains(.usesLineFragmentOrigin) {
163 | if adjustsTextForCustomLineHeight, let lastParagraphStyle = text.attribute(.paragraphStyle, at: text.length - 1, effectiveRange: nil) as? NSParagraphStyle {
164 | result.origin.y = roundToPixels(result.origin.y + lastParagraphStyle.lineSpacing / 2.0, scale: contentsScale)
165 | }
166 | } else {
167 | if let firstParagraphStyle = text.attribute(.paragraphStyle, at: 0, effectiveRange: nil) as? NSParagraphStyle,
168 | let firstFont = text.attribute(.font, at: 0, effectiveRange: nil) as? UIFont {
169 | let lineHeightCompensation = adjustsTextForCustomLineHeight && firstParagraphStyle.lineHeightMultiple > 0 ? firstParagraphStyle.lineHeightMultiple * firstFont.lineHeight - firstFont.lineHeight : 0.0
170 | result.origin.y = roundToPixels(result.origin.y + lineHeightCompensation * 1.5 + firstFont.ascender, scale: contentsScale)
171 | }
172 | }
173 | return result
174 | }
175 | }
176 |
177 | extension LKLabel {
178 | override open func drawText(in rect: CGRect) {
179 | let layer = labelLayer?.presentation() ?? labelLayer
180 | layer?.drawText(in: rect)
181 | }
182 |
183 | @objc
184 | override open var minimumScaleFactor: CGFloat {
185 | didSet(previousValue) {
186 | labelLayer?.stringDrawingContext.minimumScaleFactor = adjustsFontSizeToFitWidth ? minimumScaleFactor : 1.0
187 | }
188 | }
189 |
190 | @objc
191 | override open var adjustsFontSizeToFitWidth: Bool {
192 | didSet(previousValue) {
193 | let minimumScaleFactor = self.minimumScaleFactor
194 | self.minimumScaleFactor = minimumScaleFactor
195 | }
196 | }
197 |
198 | @objc
199 | override open var lineBreakMode: NSLineBreakMode {
200 | didSet(previousValue) {
201 | if let labelLayer = self.labelLayer {
202 | if lineBreakMode == .byTruncatingTail {
203 | labelLayer.stringDrawingOptions = labelLayer.stringDrawingOptions.union(.truncatesLastVisibleLine)
204 | } else {
205 | _ = labelLayer.stringDrawingOptions.remove(.truncatesLastVisibleLine)
206 | }
207 | }
208 | }
209 | }
210 |
211 | @objc
212 | override open var numberOfLines: Int {
213 | didSet(previousValue) {
214 | if let labelLayer = self.labelLayer {
215 | if numberOfLines != 1 {
216 | labelLayer.stringDrawingOptions = labelLayer.stringDrawingOptions.union(.usesLineFragmentOrigin)
217 | } else {
218 | _ = labelLayer.stringDrawingOptions.remove(.usesLineFragmentOrigin)
219 | }
220 | }
221 | }
222 | }
223 |
224 | override open func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
225 | let actionsDisabled = CATransaction.disableActions()
226 | CATransaction.setDisableActions(true)
227 | super.traitCollectionDidChange(previousTraitCollection)
228 | CATransaction.setDisableActions(actionsDisabled)
229 | }
230 | }
231 |
--------------------------------------------------------------------------------
/Sources/LabelKit/LKTextDidChangeAction.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LKTextDidChangeAction.swift
3 | // LabelKit
4 | //
5 | // Copyright (c) 2019-2021 Eugene Dudnyk
6 | //
7 | // All rights reserved.
8 | //
9 | // Redistribution and use in source and binary forms, with or without
10 | // modification, are permitted provided that the following conditions are met:
11 | //
12 | // 1. Redistributions of source code must retain the above copyright notice, this
13 | // list of conditions and the following disclaimer.
14 | // 2. Redistributions in binary form must reproduce the above copyright notice,
15 | // this list of conditions and the following disclaimer in the documentation
16 | // and/or other materials provided with the distribution.
17 | //
18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 | //
29 | // The views and conclusions contained in the software and documentation are those
30 | // of the authors and should not be interpreted as representing official policies,
31 | // either expressed or implied, of the LabelKit project.
32 | //
33 |
34 | import QuartzCore
35 | import UIKit
36 |
37 | class LKTextDidChangeAction: CAAnimationGroup {
38 | private static let LabelLayerFromToAlphaSwapAnimationDuration: TimeInterval = 1.0
39 | private static let attributedStringKeys: [String] = [NSAttributedString.Key.font.rawValue,
40 | NSAttributedString.Key.foregroundColor.rawValue,
41 | NSAttributedString.Key.paragraphStyle.rawValue,
42 | NSAttributedString.Key.backgroundColor.rawValue,
43 | NSAttributedString.Key.strokeColor.rawValue,
44 | NSAttributedString.Key.strokeWidth.rawValue,
45 | NSAttributedString.Key.ligature.rawValue,
46 | NSAttributedString.Key.kern.rawValue,
47 | NSAttributedString.Key.strikethroughStyle.rawValue,
48 | NSAttributedString.Key.underlineStyle.rawValue,
49 | NSAttributedString.Key.textEffect.rawValue,
50 | NSAttributedString.Key.attachment.rawValue,
51 | NSAttributedString.Key.shadow.rawValue,
52 | NSAttributedString.Key.link.rawValue,
53 | NSAttributedString.Key.baselineOffset.rawValue,
54 | NSAttributedString.Key.underlineColor.rawValue,
55 | NSAttributedString.Key.strikethroughColor.rawValue,
56 | NSAttributedString.Key.obliqueness.rawValue,
57 | NSAttributedString.Key.expansion.rawValue,
58 | NSAttributedString.Key.writingDirection.rawValue,
59 | NSAttributedString.Key.verticalGlyphForm.rawValue]
60 | var interpolatedFromAttributedText: NSAttributedString? {
61 | if interpolatedFromAlpha > 0 {
62 | return fill(withInterpolatedAttributes: fromAttributedText)
63 | }
64 | return nil
65 | }
66 |
67 | var interpolatedToAttributedText: NSAttributedString? {
68 | if interpolatedToAlpha > 0, toAttributedText != nil {
69 | return fill(withInterpolatedAttributes: toAttributedText!)
70 | }
71 | return nil
72 | }
73 |
74 | @objc private(set) var interpolatedFromAlpha: CGFloat = 0
75 | @objc private(set) var interpolatedToAlpha: CGFloat = 0 {
76 | didSet(previousValue) {
77 | if previousValue == 0, interpolatedToAlpha == 1 {
78 | interpolatedToAlpha = 0
79 | interpolatedFromAlpha = 1
80 | }
81 | }
82 | }
83 |
84 | @objc var interpolatedAttributeStates: NSMutableDictionary!
85 |
86 | var fromAttributedText: NSAttributedString?
87 | var toAttributedText: NSAttributedString?
88 |
89 | override func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable: Any]?) {
90 | guard let textLayer = anObject as? LKLabelLayer, event == keyPath(\LKLabelLayer.attributedText) else { return }
91 | let duration = CATransaction.animationDuration()
92 | toAttributedText = textLayer.attributedText
93 | if fromAttributedText != toAttributedText {
94 | var animations = [CABasicAnimation]()
95 | let rootAttributesKey = keyPath(\LKLabelLayer.currentTextDidChangeAnimation?.interpolatedAttributeStates)
96 | let shouldAnimateAlpha = (fromAttributedText != nil || toAttributedText != nil) && fromAttributedText?.string != toAttributedText?.string
97 | var interpolatedFromAlpha: CGFloat = 0.0
98 | var interpolatedToAlpha: CGFloat = 1.0
99 | if shouldAnimateAlpha {
100 | interpolatedFromAlpha = 1.0
101 | interpolatedToAlpha = 0.0
102 | animations.append(contentsOf: type(of: self).animationsForAlphaSwap(forKeyPath: keyPath(\LKLabelLayer.currentTextDidChangeAnimation?.interpolatedFromAlpha), fromValue: interpolatedFromAlpha, toValue: interpolatedToAlpha))
103 | animations.append(contentsOf: type(of: self).animationsForAlphaSwap(forKeyPath: keyPath(\LKLabelLayer.currentTextDidChangeAnimation?.interpolatedToAlpha), fromValue: interpolatedToAlpha, toValue: interpolatedFromAlpha))
104 | }
105 | self.interpolatedFromAlpha = interpolatedFromAlpha
106 | self.interpolatedToAlpha = interpolatedToAlpha
107 | type(of: self).attributedStringKeys.forEach { attributeRawValue in
108 | var shorterTextValue: AnyObject?
109 | let attributedStringKey = NSAttributedString.Key(rawValue: attributeRawValue)
110 | let swapped = self.toAttributedText?.length ?? 0 > self.fromAttributedText?.length ?? 0
111 | let longerText = swapped ? self.toAttributedText : self.fromAttributedText
112 | let shorterText = swapped ? self.fromAttributedText : self.toAttributedText
113 | longerText?.enumerateAttribute(attributedStringKey, in: NSMakeRange(0, longerText?.length ?? 0), options: [], using: { longerTextValue, longerTextRange, _ in
114 | let isLastLongerTextRange = NSMaxRange(longerTextRange) == longerText?.length
115 | if shorterText != nil, longerTextRange.location < shorterText!.length {
116 | let toEnumerationRange = isLastLongerTextRange ? NSMakeRange(longerTextRange.location, shorterText!.length - longerTextRange.location) : longerTextRange
117 | shorterText!.enumerateAttribute(attributedStringKey, in: toEnumerationRange, options: [], using: { value, shorterTextRange, _ in
118 | shorterTextValue = value as AnyObject
119 | if longerTextValue != nil || shorterTextValue != nil, !(longerTextValue as AnyObject).isEqual(shorterTextValue) {
120 | let fromValue = swapped ? shorterTextValue : longerTextValue
121 | let toValue = swapped ? longerTextValue : shorterTextValue
122 | animations.append(contentsOf: type(of: self).animationsForAttributeDiff(forRootKey: rootAttributesKey, attributedStringKey: attributedStringKey, fromValue: fromValue, toValue: toValue, in: shorterTextRange, attributeStates: self.interpolatedAttributeStates, duration: duration))
123 | }
124 | })
125 | if shorterText == nil || NSMaxRange(longerTextRange) > shorterText!.length, longerTextValue != nil || shorterTextValue != nil, !(longerTextValue as AnyObject).isEqual(shorterTextValue) {
126 | let fromValue = swapped ? shorterTextValue : longerTextValue
127 | let toValue = swapped ? longerTextValue : shorterTextValue
128 | animations.append(contentsOf: type(of: self).animationsForAttributeDiff(forRootKey: rootAttributesKey, attributedStringKey: attributedStringKey, fromValue: fromValue, toValue: toValue, in: longerTextRange, attributeStates: self.interpolatedAttributeStates, duration: duration))
129 | }
130 | }
131 | })
132 | }
133 | if animations.count > 0 {
134 | self.animations = animations
135 | self.duration = duration
136 | timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
137 | textLayer.add(self, forKey: keyPath(\LKLabelLayer.attributedText))
138 | }
139 | }
140 | }
141 |
142 | private static func decodeAttribute(from object: AnyObject?, forAttributedStringKey key: NSAttributedString.Key) -> AnyObject? {
143 | guard let dictionary = object as? NSDictionary else { return object }
144 | switch key {
145 | case .font:
146 | return UIFont.lk_decode(dictionaryRepresentation: dictionary)
147 | case .foregroundColor,
148 | .backgroundColor,
149 | .strokeColor,
150 | .underlineColor,
151 | .strikethroughColor:
152 | return UIColor.lk_decode(dictionaryRepresentation: dictionary)
153 | case .paragraphStyle:
154 | return NSParagraphStyle.lk_decode(dictionaryRepresentation: dictionary)
155 | case .shadow:
156 | return NSShadow.lk_decode(dictionaryRepresentation: dictionary)
157 | default:
158 | return object
159 | }
160 | }
161 |
162 | override required init() {
163 | super.init()
164 | }
165 |
166 | required init(from text: NSAttributedString?) {
167 | super.init()
168 | fromAttributedText = text
169 | interpolatedAttributeStates = type(of: self).stringAttributesDictionary
170 | }
171 |
172 | @available(*, unavailable)
173 | required init?(coder: NSCoder) {
174 | fatalError("init(coder:) has not been implemented")
175 | }
176 |
177 | private static func animationsForAttributeDiff(forRootKey rootKey: String,
178 | attributedStringKey: NSAttributedString.Key,
179 | fromValue: Any?,
180 | toValue: Any?,
181 | in diffRange: NSRange,
182 | attributeStates: NSMutableDictionary,
183 | duration: TimeInterval) -> [CABasicAnimation]
184 | {
185 | var animations = [CABasicAnimation]()
186 | let appendAnimation: (_ keyPath: String, _ from: Any?, _ to: Any?) -> Void = { keyPath, from, to in
187 | let animation = CABasicAnimation(forKeyPath: keyPath, fromValue: from, toValue: to, duration: duration)
188 | animations.append(animation)
189 | }
190 | if attributeStates[attributedStringKey.rawValue] == nil {
191 | attributeStates[attributedStringKey.rawValue] = NSMutableDictionary()
192 | }
193 | let objectDictKey = keyPath(attributedStringKey.rawValue, range: diffRange)
194 | let objectRootKey = keyPath(rootKey, suffix: objectDictKey)
195 |
196 | var encodingType: LKNSDictionaryCoding.Type?
197 | if fromValue != nil, let fromValueType = type(of: fromValue!) as? LKNSDictionaryCoding.Type {
198 | encodingType = fromValueType
199 | } else if toValue != nil, let toValueType = type(of: toValue!) as? LKNSDictionaryCoding.Type {
200 | encodingType = toValueType
201 | }
202 | if encodingType != nil {
203 | let fromObjectAttrs = encodingType!.lk_encode(object: fromValue as AnyObject)
204 | let toObjectAttrs = encodingType!.lk_encode(object: toValue as AnyObject)
205 | attributeStates.setValue(fromObjectAttrs, forKeyPath: objectDictKey)
206 | enumerateDiffs(leftDictionary: fromObjectAttrs, rightDictionary: toObjectAttrs, using: { key, from, to in
207 | let kPath = keyPath(objectRootKey, suffix: key)
208 | appendAnimation(kPath, from, to)
209 | })
210 | } else if let attrDict = attributeStates[attributedStringKey.rawValue] as? NSMutableDictionary {
211 | attrDict[NSStringFromRange(diffRange)] = fromValue
212 | appendAnimation(objectRootKey, fromValue, toValue)
213 | }
214 | return animations
215 | }
216 |
217 | private static func enumerateDiffs(leftDictionary: NSDictionary,
218 | rightDictionary: NSDictionary,
219 | using block: (_ key: String, _ fromValue: Any?, _ toValue: Any?) -> Void)
220 | {
221 | let totalKeySet = NSMutableSet(array: leftDictionary.allKeys)
222 | totalKeySet.addObjects(from: rightDictionary.allKeys)
223 | totalKeySet.forEach { key in
224 | let fromValue = leftDictionary[key]
225 | let toValue = rightDictionary[key]
226 | if fromValue != nil || toValue != nil, !(fromValue as AnyObject).isEqual(toValue) {
227 | if let fromValueDict = fromValue as? NSDictionary, let toValueDict = toValue as? NSDictionary {
228 | self.enumerateDiffs(leftDictionary: fromValueDict, rightDictionary: toValueDict, using: { keySuffix, from, to in
229 | block(keyPath(key, suffix: keySuffix), from, to)
230 | })
231 | } else {
232 | block(key as! String, fromValue, toValue)
233 | }
234 | }
235 | }
236 | }
237 |
238 | private static func animationsForAlphaSwap(forKeyPath keyPath: String, fromValue: Any?, toValue: Any?) -> [CABasicAnimation] {
239 | let duration = CATransaction.animationDuration()
240 | guard duration > 0 else { return [CABasicAnimation]() }
241 | let alphaSwapDuration = min(LabelLayerFromToAlphaSwapAnimationDuration, 0.5 * duration)
242 | let alphaPersistDuration = (duration - alphaSwapDuration) / 2.0
243 | let persistFromAlphaAnimation = CABasicAnimation(forKeyPath: keyPath, fromValue: fromValue, toValue: fromValue, duration: alphaPersistDuration)
244 | let swapAlphaAnimation = CABasicAnimation(forKeyPath: keyPath, fromValue: fromValue, toValue: toValue, duration: alphaSwapDuration)
245 | let persistToAlphaAnimation = CABasicAnimation(forKeyPath: keyPath, fromValue: toValue, toValue: toValue, duration: alphaPersistDuration)
246 | persistFromAlphaAnimation.beginTime = 0
247 | swapAlphaAnimation.beginTime = alphaPersistDuration
248 | persistToAlphaAnimation.beginTime = alphaSwapDuration + alphaPersistDuration
249 |
250 | return [persistFromAlphaAnimation,
251 | swapAlphaAnimation,
252 | persistToAlphaAnimation]
253 | }
254 |
255 | private static var stringAttributesDictionary: NSMutableDictionary {
256 | return NSMutableDictionary(sharedKeySet: NSMutableDictionary.sharedKeySet(forKeys: attributedStringKeys as [NSCopying]))
257 | }
258 |
259 | private func fill(withInterpolatedAttributes attributedString: NSAttributedString?) -> NSAttributedString {
260 | let mutableInterpolatedAttributedText = attributedString != nil ? NSMutableAttributedString(attributedString: attributedString!) : NSMutableAttributedString()
261 | mutableInterpolatedAttributedText.beginEditing()
262 | interpolatedAttributeStates.enumerateKeysAndObjects { keyInAttrStates, objInAttrStates, _ in
263 | guard let attrValuePerRangeString = objInAttrStates as? NSDictionary, let attrStrKeyRawValue = keyInAttrStates as? String else { return }
264 | attrValuePerRangeString.enumerateKeysAndObjects { keyInRange, objInRange, _ in
265 | guard let rangeString = keyInRange as? String else { return }
266 | let range = NSRangeFromString(rangeString)
267 | if range.location < mutableInterpolatedAttributedText.length {
268 | let key = NSAttributedString.Key(rawValue: attrStrKeyRawValue)
269 | if let decodedObject = LKTextDidChangeAction.decodeAttribute(from: objInRange as AnyObject, forAttributedStringKey: key) {
270 | mutableInterpolatedAttributedText.addAttribute(key, value: decodedObject, range: NSMakeRange(range.location, min(mutableInterpolatedAttributedText.length - range.location, range.length)))
271 | } else {
272 | mutableInterpolatedAttributedText.removeAttribute(key, range: NSMakeRange(range.location, min(mutableInterpolatedAttributedText.length - range.location, range.length)))
273 | }
274 | }
275 | }
276 | }
277 |
278 | mutableInterpolatedAttributedText.endEditing()
279 | return mutableInterpolatedAttributedText
280 | }
281 |
282 | override func copy(with zone: NSZone? = nil) -> Any {
283 | let result = super.copy(with: zone)
284 | if let action = result as? LKTextDidChangeAction {
285 | action.fromAttributedText = fromAttributedText
286 | action.toAttributedText = toAttributedText
287 | action.interpolatedAttributeStates = interpolatedAttributeStates
288 | action.interpolatedFromAlpha = interpolatedFromAlpha
289 | action.interpolatedToAlpha = interpolatedToAlpha
290 | return action
291 | }
292 | return result
293 | }
294 | }
295 |
--------------------------------------------------------------------------------
/LabelKit.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 9E296FF62595A71B00CC573D /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E296FF52595A71B00CC573D /* Utils.swift */; };
11 | 9E296FF72595A71B00CC573D /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E296FF52595A71B00CC573D /* Utils.swift */; };
12 | 9E2970052595C78500CC573D /* TextGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2970042595C78500CC573D /* TextGenerator.swift */; };
13 | 9E2970122595CB3D00CC573D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9E9636F522DC32A200014F86 /* LaunchScreen.storyboard */; };
14 | 9E30381F22DEA7CF00E3C385 /* LabelKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9E9636AE22DB6CD400014F86 /* LabelKit.framework */; };
15 | 9E30382022DEA7CF00E3C385 /* LabelKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9E9636AE22DB6CD400014F86 /* LabelKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
16 | 9E30382222DEA7FF00E3C385 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9636EE22DC32A000014F86 /* ViewController.swift */; };
17 | 9E406D7E22E370C0001CB087 /* LabelKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 9E9636CD22DC2E6000014F86 /* LabelKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
18 | 9E406D7F22E370C0001CB087 /* LabelKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 9E9636CD22DC2E6000014F86 /* LabelKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
19 | 9E406D8722E37135001CB087 /* LKExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E406D8622E37135001CB087 /* LKExtensionsTests.swift */; };
20 | 9E406D8922E37135001CB087 /* LabelKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9E96369B22DB634A00014F86 /* LabelKit.framework */; };
21 | 9E406D9822E3714B001CB087 /* LabelKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9E9636AE22DB6CD400014F86 /* LabelKit.framework */; };
22 | 9E406D9E22E37170001CB087 /* LKExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E406D8622E37135001CB087 /* LKExtensionsTests.swift */; };
23 | 9E6245F125922EAB00CFA632 /* LabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6245F025922EAB00CFA632 /* LabelView.swift */; };
24 | 9E6245F225922EAB00CFA632 /* LabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6245F025922EAB00CFA632 /* LabelView.swift */; };
25 | 9E6245FF2592727500CFA632 /* Example_SwiftUIApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6245FE2592727500CFA632 /* Example_SwiftUIApp.swift */; };
26 | 9E6246012592727500CFA632 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6246002592727500CFA632 /* ContentView.swift */; };
27 | 9E6246032592727600CFA632 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9E6246022592727600CFA632 /* Assets.xcassets */; };
28 | 9E6246062592727600CFA632 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9E6246052592727600CFA632 /* Preview Assets.xcassets */; };
29 | 9E62461F2592731300CFA632 /* LabelKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9E96369B22DB634A00014F86 /* LabelKit.framework */; };
30 | 9E6246202592731300CFA632 /* LabelKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9E96369B22DB634A00014F86 /* LabelKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
31 | 9E6246482592FEDF00CFA632 /* NSAttributedString+Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6246472592FEDF00CFA632 /* NSAttributedString+Random.swift */; };
32 | 9E6246492592FEDF00CFA632 /* NSAttributedString+Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6246472592FEDF00CFA632 /* NSAttributedString+Random.swift */; };
33 | 9E62464A2592FEDF00CFA632 /* NSAttributedString+Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6246472592FEDF00CFA632 /* NSAttributedString+Random.swift */; };
34 | 9E9636C122DC2E2A00014F86 /* LKTextDidChangeAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9636BC22DC2E2A00014F86 /* LKTextDidChangeAction.swift */; };
35 | 9E9636C222DC2E2A00014F86 /* LKTextDidChangeAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9636BC22DC2E2A00014F86 /* LKTextDidChangeAction.swift */; };
36 | 9E9636C322DC2E2A00014F86 /* LKExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9636BD22DC2E2A00014F86 /* LKExtensions.swift */; };
37 | 9E9636C422DC2E2A00014F86 /* LKExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9636BD22DC2E2A00014F86 /* LKExtensions.swift */; };
38 | 9E9636C522DC2E2A00014F86 /* LKLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9636BE22DC2E2A00014F86 /* LKLabel.swift */; };
39 | 9E9636C622DC2E2A00014F86 /* LKLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9636BE22DC2E2A00014F86 /* LKLabel.swift */; };
40 | 9E9636C722DC2E2A00014F86 /* LKBoundsDidChangeAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9636BF22DC2E2A00014F86 /* LKBoundsDidChangeAction.swift */; };
41 | 9E9636C822DC2E2A00014F86 /* LKBoundsDidChangeAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9636BF22DC2E2A00014F86 /* LKBoundsDidChangeAction.swift */; };
42 | 9E9636C922DC2E2A00014F86 /* LKLabelLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9636C022DC2E2A00014F86 /* LKLabelLayer.swift */; };
43 | 9E9636CA22DC2E2A00014F86 /* LKLabelLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9636C022DC2E2A00014F86 /* LKLabelLayer.swift */; };
44 | 9E9636EB22DC32A000014F86 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9636EA22DC32A000014F86 /* AppDelegate.swift */; };
45 | 9E9636ED22DC32A000014F86 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9636EC22DC32A000014F86 /* SceneDelegate.swift */; };
46 | 9E9636EF22DC32A000014F86 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9636EE22DC32A000014F86 /* ViewController.swift */; };
47 | 9E9636F222DC32A000014F86 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9E9636F022DC32A000014F86 /* Main.storyboard */; };
48 | 9E9636F722DC32A200014F86 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9E9636F522DC32A200014F86 /* LaunchScreen.storyboard */; };
49 | 9EE0F66D22DD1F99002C9157 /* LabelKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9E96369B22DB634A00014F86 /* LabelKit.framework */; };
50 | 9EE0F66E22DD1F99002C9157 /* LabelKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9E96369B22DB634A00014F86 /* LabelKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
51 | 9EF4973A22DEA73600F5F260 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF4973922DEA73600F5F260 /* AppDelegate.swift */; };
52 | 9EF4973F22DEA73600F5F260 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9EF4973D22DEA73600F5F260 /* Main.storyboard */; };
53 | /* End PBXBuildFile section */
54 |
55 | /* Begin PBXContainerItemProxy section */
56 | 9E406D8A22E37135001CB087 /* PBXContainerItemProxy */ = {
57 | isa = PBXContainerItemProxy;
58 | containerPortal = 9E2B92CA22BAFC7400A007F9 /* Project object */;
59 | proxyType = 1;
60 | remoteGlobalIDString = 9E96369A22DB634A00014F86;
61 | remoteInfo = "LabelKit iOS";
62 | };
63 | 9E406D9922E3714B001CB087 /* PBXContainerItemProxy */ = {
64 | isa = PBXContainerItemProxy;
65 | containerPortal = 9E2B92CA22BAFC7400A007F9 /* Project object */;
66 | proxyType = 1;
67 | remoteGlobalIDString = 9E9636AD22DB6CD400014F86;
68 | remoteInfo = "LabelKit tvOS";
69 | };
70 | 9E6246212592731300CFA632 /* PBXContainerItemProxy */ = {
71 | isa = PBXContainerItemProxy;
72 | containerPortal = 9E2B92CA22BAFC7400A007F9 /* Project object */;
73 | proxyType = 1;
74 | remoteGlobalIDString = 9E96369A22DB634A00014F86;
75 | remoteInfo = "LabelKit iOS";
76 | };
77 | /* End PBXContainerItemProxy section */
78 |
79 | /* Begin PBXCopyFilesBuildPhase section */
80 | 9E30382122DEA7CF00E3C385 /* Embed Frameworks */ = {
81 | isa = PBXCopyFilesBuildPhase;
82 | buildActionMask = 2147483647;
83 | dstPath = "";
84 | dstSubfolderSpec = 10;
85 | files = (
86 | 9E30382022DEA7CF00E3C385 /* LabelKit.framework in Embed Frameworks */,
87 | );
88 | name = "Embed Frameworks";
89 | runOnlyForDeploymentPostprocessing = 0;
90 | };
91 | 9E6246232592731300CFA632 /* Embed Frameworks */ = {
92 | isa = PBXCopyFilesBuildPhase;
93 | buildActionMask = 2147483647;
94 | dstPath = "";
95 | dstSubfolderSpec = 10;
96 | files = (
97 | 9E6246202592731300CFA632 /* LabelKit.framework in Embed Frameworks */,
98 | );
99 | name = "Embed Frameworks";
100 | runOnlyForDeploymentPostprocessing = 0;
101 | };
102 | 9EE0F66F22DD1F99002C9157 /* Embed Frameworks */ = {
103 | isa = PBXCopyFilesBuildPhase;
104 | buildActionMask = 2147483647;
105 | dstPath = "";
106 | dstSubfolderSpec = 10;
107 | files = (
108 | 9EE0F66E22DD1F99002C9157 /* LabelKit.framework in Embed Frameworks */,
109 | );
110 | name = "Embed Frameworks";
111 | runOnlyForDeploymentPostprocessing = 0;
112 | };
113 | /* End PBXCopyFilesBuildPhase section */
114 |
115 | /* Begin PBXFileReference section */
116 | 9E296FF52595A71B00CC573D /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = ""; };
117 | 9E2970042595C78500CC573D /* TextGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextGenerator.swift; sourceTree = ""; };
118 | 9E2970172595CB7C00CC573D /* Example SwiftUI.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Example SwiftUI.entitlements"; sourceTree = ""; };
119 | 9E29701C2595CB8300CC573D /* Example iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Example iOS.entitlements"; sourceTree = ""; };
120 | 9E30382322DEAC3D00E3C385 /* INSTALL.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = INSTALL.md; sourceTree = ""; };
121 | 9E406D7D22E37036001CB087 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; name = module.modulemap; path = Sources/module.modulemap; sourceTree = ""; };
122 | 9E406D8422E37135001CB087 /* LabelKit iOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "LabelKit iOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
123 | 9E406D8622E37135001CB087 /* LKExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LKExtensionsTests.swift; sourceTree = ""; };
124 | 9E406D8822E37135001CB087 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
125 | 9E406D9322E3714B001CB087 /* LabelKit tvOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "LabelKit tvOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
126 | 9E406D9722E3714B001CB087 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
127 | 9E478D3E25EC1A9B00BA51A8 /* Config.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; };
128 | 9E6245F025922EAB00CFA632 /* LabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelView.swift; sourceTree = ""; };
129 | 9E6245FC2592727500CFA632 /* Example SwiftUI.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Example SwiftUI.app"; sourceTree = BUILT_PRODUCTS_DIR; };
130 | 9E6245FE2592727500CFA632 /* Example_SwiftUIApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Example_SwiftUIApp.swift; sourceTree = ""; };
131 | 9E6246002592727500CFA632 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
132 | 9E6246022592727600CFA632 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
133 | 9E6246052592727600CFA632 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
134 | 9E6246072592727600CFA632 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
135 | 9E6246472592FEDF00CFA632 /* NSAttributedString+Random.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+Random.swift"; sourceTree = ""; };
136 | 9E96369B22DB634A00014F86 /* LabelKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = LabelKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
137 | 9E9636AE22DB6CD400014F86 /* LabelKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = LabelKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
138 | 9E9636BC22DC2E2A00014F86 /* LKTextDidChangeAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LKTextDidChangeAction.swift; sourceTree = ""; };
139 | 9E9636BD22DC2E2A00014F86 /* LKExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LKExtensions.swift; sourceTree = ""; };
140 | 9E9636BE22DC2E2A00014F86 /* LKLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LKLabel.swift; sourceTree = ""; };
141 | 9E9636BF22DC2E2A00014F86 /* LKBoundsDidChangeAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LKBoundsDidChangeAction.swift; sourceTree = ""; };
142 | 9E9636C022DC2E2A00014F86 /* LKLabelLayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LKLabelLayer.swift; sourceTree = ""; };
143 | 9E9636CC22DC2E6000014F86 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Sources/Info.plist; sourceTree = ""; };
144 | 9E9636CD22DC2E6000014F86 /* LabelKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = LabelKit.h; path = Sources/LabelKit.h; sourceTree = ""; };
145 | 9E9636CE22DC2E7400014F86 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; };
146 | 9E9636CF22DC2E7400014F86 /* LabelKit.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = LabelKit.podspec; sourceTree = ""; };
147 | 9E9636D022DC2E7400014F86 /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; };
148 | 9E9636E822DC32A000014F86 /* Example iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Example iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
149 | 9E9636EA22DC32A000014F86 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
150 | 9E9636EC22DC32A000014F86 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
151 | 9E9636EE22DC32A000014F86 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
152 | 9E9636F122DC32A000014F86 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
153 | 9E9636F622DC32A200014F86 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
154 | 9E9636F822DC32A200014F86 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
155 | 9EF4973122DEA55F00F5F260 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; };
156 | 9EF4973222DEA61600F5F260 /* .travis.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = .travis.yml; sourceTree = ""; };
157 | 9EF4973722DEA73600F5F260 /* Example tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Example tvOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
158 | 9EF4973922DEA73600F5F260 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
159 | 9EF4973E22DEA73600F5F260 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
160 | 9EF4974222DEA73800F5F260 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
161 | /* End PBXFileReference section */
162 |
163 | /* Begin PBXFrameworksBuildPhase section */
164 | 9E406D8122E37135001CB087 /* Frameworks */ = {
165 | isa = PBXFrameworksBuildPhase;
166 | buildActionMask = 2147483647;
167 | files = (
168 | 9E406D8922E37135001CB087 /* LabelKit.framework in Frameworks */,
169 | );
170 | runOnlyForDeploymentPostprocessing = 0;
171 | };
172 | 9E406D9022E3714B001CB087 /* Frameworks */ = {
173 | isa = PBXFrameworksBuildPhase;
174 | buildActionMask = 2147483647;
175 | files = (
176 | 9E406D9822E3714B001CB087 /* LabelKit.framework in Frameworks */,
177 | );
178 | runOnlyForDeploymentPostprocessing = 0;
179 | };
180 | 9E6245F92592727500CFA632 /* Frameworks */ = {
181 | isa = PBXFrameworksBuildPhase;
182 | buildActionMask = 2147483647;
183 | files = (
184 | 9E62461F2592731300CFA632 /* LabelKit.framework in Frameworks */,
185 | );
186 | runOnlyForDeploymentPostprocessing = 0;
187 | };
188 | 9E96369822DB634A00014F86 /* Frameworks */ = {
189 | isa = PBXFrameworksBuildPhase;
190 | buildActionMask = 2147483647;
191 | files = (
192 | );
193 | runOnlyForDeploymentPostprocessing = 0;
194 | };
195 | 9E9636AB22DB6CD400014F86 /* Frameworks */ = {
196 | isa = PBXFrameworksBuildPhase;
197 | buildActionMask = 2147483647;
198 | files = (
199 | );
200 | runOnlyForDeploymentPostprocessing = 0;
201 | };
202 | 9E9636E522DC32A000014F86 /* Frameworks */ = {
203 | isa = PBXFrameworksBuildPhase;
204 | buildActionMask = 2147483647;
205 | files = (
206 | 9EE0F66D22DD1F99002C9157 /* LabelKit.framework in Frameworks */,
207 | );
208 | runOnlyForDeploymentPostprocessing = 0;
209 | };
210 | 9EF4973422DEA73600F5F260 /* Frameworks */ = {
211 | isa = PBXFrameworksBuildPhase;
212 | buildActionMask = 2147483647;
213 | files = (
214 | 9E30381F22DEA7CF00E3C385 /* LabelKit.framework in Frameworks */,
215 | );
216 | runOnlyForDeploymentPostprocessing = 0;
217 | };
218 | /* End PBXFrameworksBuildPhase section */
219 |
220 | /* Begin PBXGroup section */
221 | 9E2B92C922BAFC7400A007F9 = {
222 | isa = PBXGroup;
223 | children = (
224 | 9E9636BB22DC2E2A00014F86 /* LabelKit */,
225 | 9E9636CB22DC2E3F00014F86 /* Supporting Files */,
226 | 9E9636E922DC32A000014F86 /* Example iOS */,
227 | 9EF4973822DEA73600F5F260 /* Example tvOS */,
228 | 9E6245FD2592727500CFA632 /* Example SwiftUI */,
229 | 9E406D8522E37135001CB087 /* LabelKit iOS Tests */,
230 | 9E406D9422E3714B001CB087 /* LabelKit tvOS Tests */,
231 | 9E2B92D322BAFC7400A007F9 /* Products */,
232 | 9E9636FC22DC335600014F86 /* Frameworks */,
233 | );
234 | sourceTree = "";
235 | };
236 | 9E2B92D322BAFC7400A007F9 /* Products */ = {
237 | isa = PBXGroup;
238 | children = (
239 | 9E96369B22DB634A00014F86 /* LabelKit.framework */,
240 | 9E9636AE22DB6CD400014F86 /* LabelKit.framework */,
241 | 9E9636E822DC32A000014F86 /* Example iOS.app */,
242 | 9EF4973722DEA73600F5F260 /* Example tvOS.app */,
243 | 9E406D8422E37135001CB087 /* LabelKit iOS Tests.xctest */,
244 | 9E406D9322E3714B001CB087 /* LabelKit tvOS Tests.xctest */,
245 | 9E6245FC2592727500CFA632 /* Example SwiftUI.app */,
246 | );
247 | name = Products;
248 | sourceTree = "";
249 | };
250 | 9E406D8522E37135001CB087 /* LabelKit iOS Tests */ = {
251 | isa = PBXGroup;
252 | children = (
253 | 9E406D8622E37135001CB087 /* LKExtensionsTests.swift */,
254 | 9E406D8822E37135001CB087 /* Info.plist */,
255 | );
256 | path = "LabelKit iOS Tests";
257 | sourceTree = "";
258 | };
259 | 9E406D9422E3714B001CB087 /* LabelKit tvOS Tests */ = {
260 | isa = PBXGroup;
261 | children = (
262 | 9E406D9722E3714B001CB087 /* Info.plist */,
263 | );
264 | path = "LabelKit tvOS Tests";
265 | sourceTree = "";
266 | };
267 | 9E6245FD2592727500CFA632 /* Example SwiftUI */ = {
268 | isa = PBXGroup;
269 | children = (
270 | 9E2970172595CB7C00CC573D /* Example SwiftUI.entitlements */,
271 | 9E6245FE2592727500CFA632 /* Example_SwiftUIApp.swift */,
272 | 9E2970042595C78500CC573D /* TextGenerator.swift */,
273 | 9E6246002592727500CFA632 /* ContentView.swift */,
274 | 9E6246022592727600CFA632 /* Assets.xcassets */,
275 | 9E6246072592727600CFA632 /* Info.plist */,
276 | 9E6246042592727600CFA632 /* Preview Content */,
277 | );
278 | path = "Example SwiftUI";
279 | sourceTree = "";
280 | };
281 | 9E6246042592727600CFA632 /* Preview Content */ = {
282 | isa = PBXGroup;
283 | children = (
284 | 9E6246052592727600CFA632 /* Preview Assets.xcassets */,
285 | );
286 | path = "Preview Content";
287 | sourceTree = "";
288 | };
289 | 9E9636BB22DC2E2A00014F86 /* LabelKit */ = {
290 | isa = PBXGroup;
291 | children = (
292 | 9E9636BE22DC2E2A00014F86 /* LKLabel.swift */,
293 | 9E9636C022DC2E2A00014F86 /* LKLabelLayer.swift */,
294 | 9E9636BF22DC2E2A00014F86 /* LKBoundsDidChangeAction.swift */,
295 | 9E9636BC22DC2E2A00014F86 /* LKTextDidChangeAction.swift */,
296 | 9E9636BD22DC2E2A00014F86 /* LKExtensions.swift */,
297 | 9E6245F025922EAB00CFA632 /* LabelView.swift */,
298 | 9E296FF52595A71B00CC573D /* Utils.swift */,
299 | 9E478D3E25EC1A9B00BA51A8 /* Config.xcconfig */,
300 | );
301 | name = LabelKit;
302 | path = Sources/LabelKit;
303 | sourceTree = "";
304 | };
305 | 9E9636CB22DC2E3F00014F86 /* Supporting Files */ = {
306 | isa = PBXGroup;
307 | children = (
308 | 9EF4973222DEA61600F5F260 /* .travis.yml */,
309 | 9EF4973122DEA55F00F5F260 /* LICENSE */,
310 | 9E9636CF22DC2E7400014F86 /* LabelKit.podspec */,
311 | 9E9636D022DC2E7400014F86 /* Package.swift */,
312 | 9E9636CE22DC2E7400014F86 /* README.md */,
313 | 9E30382322DEAC3D00E3C385 /* INSTALL.md */,
314 | 9E9636CC22DC2E6000014F86 /* Info.plist */,
315 | 9E9636CD22DC2E6000014F86 /* LabelKit.h */,
316 | 9E406D7D22E37036001CB087 /* module.modulemap */,
317 | );
318 | name = "Supporting Files";
319 | sourceTree = "";
320 | };
321 | 9E9636E922DC32A000014F86 /* Example iOS */ = {
322 | isa = PBXGroup;
323 | children = (
324 | 9E29701C2595CB8300CC573D /* Example iOS.entitlements */,
325 | 9E9636EA22DC32A000014F86 /* AppDelegate.swift */,
326 | 9E9636EC22DC32A000014F86 /* SceneDelegate.swift */,
327 | 9E9636EE22DC32A000014F86 /* ViewController.swift */,
328 | 9E6246472592FEDF00CFA632 /* NSAttributedString+Random.swift */,
329 | 9E9636F022DC32A000014F86 /* Main.storyboard */,
330 | 9E9636F522DC32A200014F86 /* LaunchScreen.storyboard */,
331 | 9E9636F822DC32A200014F86 /* Info.plist */,
332 | );
333 | path = "Example iOS";
334 | sourceTree = "";
335 | };
336 | 9E9636FC22DC335600014F86 /* Frameworks */ = {
337 | isa = PBXGroup;
338 | children = (
339 | );
340 | name = Frameworks;
341 | sourceTree = "";
342 | };
343 | 9EF4973822DEA73600F5F260 /* Example tvOS */ = {
344 | isa = PBXGroup;
345 | children = (
346 | 9EF4973922DEA73600F5F260 /* AppDelegate.swift */,
347 | 9EF4973D22DEA73600F5F260 /* Main.storyboard */,
348 | 9EF4974222DEA73800F5F260 /* Info.plist */,
349 | );
350 | path = "Example tvOS";
351 | sourceTree = "";
352 | };
353 | /* End PBXGroup section */
354 |
355 | /* Begin PBXHeadersBuildPhase section */
356 | 9E96369622DB634A00014F86 /* Headers */ = {
357 | isa = PBXHeadersBuildPhase;
358 | buildActionMask = 2147483647;
359 | files = (
360 | 9E406D7F22E370C0001CB087 /* LabelKit.h in Headers */,
361 | );
362 | runOnlyForDeploymentPostprocessing = 0;
363 | };
364 | 9E9636A922DB6CD400014F86 /* Headers */ = {
365 | isa = PBXHeadersBuildPhase;
366 | buildActionMask = 2147483647;
367 | files = (
368 | 9E406D7E22E370C0001CB087 /* LabelKit.h in Headers */,
369 | );
370 | runOnlyForDeploymentPostprocessing = 0;
371 | };
372 | /* End PBXHeadersBuildPhase section */
373 |
374 | /* Begin PBXNativeTarget section */
375 | 9E406D8322E37135001CB087 /* LabelKit iOS Tests */ = {
376 | isa = PBXNativeTarget;
377 | buildConfigurationList = 9E406D8C22E37135001CB087 /* Build configuration list for PBXNativeTarget "LabelKit iOS Tests" */;
378 | buildPhases = (
379 | 9E406D8022E37135001CB087 /* Sources */,
380 | 9E406D8122E37135001CB087 /* Frameworks */,
381 | 9E406D8222E37135001CB087 /* Resources */,
382 | );
383 | buildRules = (
384 | );
385 | dependencies = (
386 | 9E406D8B22E37135001CB087 /* PBXTargetDependency */,
387 | );
388 | name = "LabelKit iOS Tests";
389 | productName = "LabelKit iOS Tests";
390 | productReference = 9E406D8422E37135001CB087 /* LabelKit iOS Tests.xctest */;
391 | productType = "com.apple.product-type.bundle.unit-test";
392 | };
393 | 9E406D9222E3714B001CB087 /* LabelKit tvOS Tests */ = {
394 | isa = PBXNativeTarget;
395 | buildConfigurationList = 9E406D9B22E3714B001CB087 /* Build configuration list for PBXNativeTarget "LabelKit tvOS Tests" */;
396 | buildPhases = (
397 | 9E406D8F22E3714B001CB087 /* Sources */,
398 | 9E406D9022E3714B001CB087 /* Frameworks */,
399 | 9E406D9122E3714B001CB087 /* Resources */,
400 | );
401 | buildRules = (
402 | );
403 | dependencies = (
404 | 9E406D9A22E3714B001CB087 /* PBXTargetDependency */,
405 | );
406 | name = "LabelKit tvOS Tests";
407 | productName = "LabelKit tvOS Tests";
408 | productReference = 9E406D9322E3714B001CB087 /* LabelKit tvOS Tests.xctest */;
409 | productType = "com.apple.product-type.bundle.unit-test";
410 | };
411 | 9E6245FB2592727500CFA632 /* Example SwiftUI */ = {
412 | isa = PBXNativeTarget;
413 | buildConfigurationList = 9E6246082592727600CFA632 /* Build configuration list for PBXNativeTarget "Example SwiftUI" */;
414 | buildPhases = (
415 | 9E6245F82592727500CFA632 /* Sources */,
416 | 9E6245F92592727500CFA632 /* Frameworks */,
417 | 9E6245FA2592727500CFA632 /* Resources */,
418 | 9E6246232592731300CFA632 /* Embed Frameworks */,
419 | );
420 | buildRules = (
421 | );
422 | dependencies = (
423 | 9E6246222592731300CFA632 /* PBXTargetDependency */,
424 | );
425 | name = "Example SwiftUI";
426 | productName = "Example SwiftUI";
427 | productReference = 9E6245FC2592727500CFA632 /* Example SwiftUI.app */;
428 | productType = "com.apple.product-type.application";
429 | };
430 | 9E96369A22DB634A00014F86 /* LabelKit iOS */ = {
431 | isa = PBXNativeTarget;
432 | buildConfigurationList = 9E9636A022DB634A00014F86 /* Build configuration list for PBXNativeTarget "LabelKit iOS" */;
433 | buildPhases = (
434 | 9E96369622DB634A00014F86 /* Headers */,
435 | 9E96369722DB634A00014F86 /* Sources */,
436 | 9E96369822DB634A00014F86 /* Frameworks */,
437 | 9E96369922DB634A00014F86 /* Resources */,
438 | );
439 | buildRules = (
440 | );
441 | dependencies = (
442 | );
443 | name = "LabelKit iOS";
444 | productName = LabelKit;
445 | productReference = 9E96369B22DB634A00014F86 /* LabelKit.framework */;
446 | productType = "com.apple.product-type.framework";
447 | };
448 | 9E9636AD22DB6CD400014F86 /* LabelKit tvOS */ = {
449 | isa = PBXNativeTarget;
450 | buildConfigurationList = 9E9636B322DB6CD400014F86 /* Build configuration list for PBXNativeTarget "LabelKit tvOS" */;
451 | buildPhases = (
452 | 9E9636A922DB6CD400014F86 /* Headers */,
453 | 9E9636AA22DB6CD400014F86 /* Sources */,
454 | 9E9636AB22DB6CD400014F86 /* Frameworks */,
455 | 9E9636AC22DB6CD400014F86 /* Resources */,
456 | );
457 | buildRules = (
458 | );
459 | dependencies = (
460 | );
461 | name = "LabelKit tvOS";
462 | productName = "LabelKit-tvOS";
463 | productReference = 9E9636AE22DB6CD400014F86 /* LabelKit.framework */;
464 | productType = "com.apple.product-type.framework";
465 | };
466 | 9E9636E722DC32A000014F86 /* Example iOS */ = {
467 | isa = PBXNativeTarget;
468 | buildConfigurationList = 9E9636F922DC32A200014F86 /* Build configuration list for PBXNativeTarget "Example iOS" */;
469 | buildPhases = (
470 | 9EB0BB4D25EA0A160030CE50 /* ShellScript */,
471 | 9E9636E422DC32A000014F86 /* Sources */,
472 | 9E9636E522DC32A000014F86 /* Frameworks */,
473 | 9E9636E622DC32A000014F86 /* Resources */,
474 | 9EE0F66F22DD1F99002C9157 /* Embed Frameworks */,
475 | );
476 | buildRules = (
477 | );
478 | dependencies = (
479 | );
480 | name = "Example iOS";
481 | productName = "Example iOS";
482 | productReference = 9E9636E822DC32A000014F86 /* Example iOS.app */;
483 | productType = "com.apple.product-type.application";
484 | };
485 | 9EF4973622DEA73600F5F260 /* Example tvOS */ = {
486 | isa = PBXNativeTarget;
487 | buildConfigurationList = 9EF4974322DEA73800F5F260 /* Build configuration list for PBXNativeTarget "Example tvOS" */;
488 | buildPhases = (
489 | 9EF4973322DEA73600F5F260 /* Sources */,
490 | 9EF4973422DEA73600F5F260 /* Frameworks */,
491 | 9EF4973522DEA73600F5F260 /* Resources */,
492 | 9E30382122DEA7CF00E3C385 /* Embed Frameworks */,
493 | );
494 | buildRules = (
495 | );
496 | dependencies = (
497 | );
498 | name = "Example tvOS";
499 | productName = "Example tvOS";
500 | productReference = 9EF4973722DEA73600F5F260 /* Example tvOS.app */;
501 | productType = "com.apple.product-type.application";
502 | };
503 | /* End PBXNativeTarget section */
504 |
505 | /* Begin PBXProject section */
506 | 9E2B92CA22BAFC7400A007F9 /* Project object */ = {
507 | isa = PBXProject;
508 | attributes = {
509 | LastSwiftUpdateCheck = 1230;
510 | LastUpgradeCheck = 1230;
511 | ORGANIZATIONNAME = "Imaginarium Works";
512 | TargetAttributes = {
513 | 9E406D8322E37135001CB087 = {
514 | CreatedOnToolsVersion = 11.0;
515 | };
516 | 9E406D9222E3714B001CB087 = {
517 | CreatedOnToolsVersion = 11.0;
518 | };
519 | 9E6245FB2592727500CFA632 = {
520 | CreatedOnToolsVersion = 12.3;
521 | };
522 | 9E96369A22DB634A00014F86 = {
523 | CreatedOnToolsVersion = 11.0;
524 | };
525 | 9E9636AD22DB6CD400014F86 = {
526 | CreatedOnToolsVersion = 11.0;
527 | };
528 | 9E9636E722DC32A000014F86 = {
529 | CreatedOnToolsVersion = 11.0;
530 | };
531 | 9EF4973622DEA73600F5F260 = {
532 | CreatedOnToolsVersion = 11.0;
533 | };
534 | };
535 | };
536 | buildConfigurationList = 9E2B92CD22BAFC7400A007F9 /* Build configuration list for PBXProject "LabelKit" */;
537 | compatibilityVersion = "Xcode 9.3";
538 | developmentRegion = en;
539 | hasScannedForEncodings = 0;
540 | knownRegions = (
541 | en,
542 | Base,
543 | );
544 | mainGroup = 9E2B92C922BAFC7400A007F9;
545 | productRefGroup = 9E2B92D322BAFC7400A007F9 /* Products */;
546 | projectDirPath = "";
547 | projectRoot = "";
548 | targets = (
549 | 9E96369A22DB634A00014F86 /* LabelKit iOS */,
550 | 9E9636AD22DB6CD400014F86 /* LabelKit tvOS */,
551 | 9E9636E722DC32A000014F86 /* Example iOS */,
552 | 9EF4973622DEA73600F5F260 /* Example tvOS */,
553 | 9E6245FB2592727500CFA632 /* Example SwiftUI */,
554 | 9E406D8322E37135001CB087 /* LabelKit iOS Tests */,
555 | 9E406D9222E3714B001CB087 /* LabelKit tvOS Tests */,
556 | );
557 | };
558 | /* End PBXProject section */
559 |
560 | /* Begin PBXResourcesBuildPhase section */
561 | 9E406D8222E37135001CB087 /* Resources */ = {
562 | isa = PBXResourcesBuildPhase;
563 | buildActionMask = 2147483647;
564 | files = (
565 | );
566 | runOnlyForDeploymentPostprocessing = 0;
567 | };
568 | 9E406D9122E3714B001CB087 /* Resources */ = {
569 | isa = PBXResourcesBuildPhase;
570 | buildActionMask = 2147483647;
571 | files = (
572 | );
573 | runOnlyForDeploymentPostprocessing = 0;
574 | };
575 | 9E6245FA2592727500CFA632 /* Resources */ = {
576 | isa = PBXResourcesBuildPhase;
577 | buildActionMask = 2147483647;
578 | files = (
579 | 9E2970122595CB3D00CC573D /* LaunchScreen.storyboard in Resources */,
580 | 9E6246062592727600CFA632 /* Preview Assets.xcassets in Resources */,
581 | 9E6246032592727600CFA632 /* Assets.xcassets in Resources */,
582 | );
583 | runOnlyForDeploymentPostprocessing = 0;
584 | };
585 | 9E96369922DB634A00014F86 /* Resources */ = {
586 | isa = PBXResourcesBuildPhase;
587 | buildActionMask = 2147483647;
588 | files = (
589 | );
590 | runOnlyForDeploymentPostprocessing = 0;
591 | };
592 | 9E9636AC22DB6CD400014F86 /* Resources */ = {
593 | isa = PBXResourcesBuildPhase;
594 | buildActionMask = 2147483647;
595 | files = (
596 | );
597 | runOnlyForDeploymentPostprocessing = 0;
598 | };
599 | 9E9636E622DC32A000014F86 /* Resources */ = {
600 | isa = PBXResourcesBuildPhase;
601 | buildActionMask = 2147483647;
602 | files = (
603 | 9E9636F722DC32A200014F86 /* LaunchScreen.storyboard in Resources */,
604 | 9E9636F222DC32A000014F86 /* Main.storyboard in Resources */,
605 | );
606 | runOnlyForDeploymentPostprocessing = 0;
607 | };
608 | 9EF4973522DEA73600F5F260 /* Resources */ = {
609 | isa = PBXResourcesBuildPhase;
610 | buildActionMask = 2147483647;
611 | files = (
612 | 9EF4973F22DEA73600F5F260 /* Main.storyboard in Resources */,
613 | );
614 | runOnlyForDeploymentPostprocessing = 0;
615 | };
616 | /* End PBXResourcesBuildPhase section */
617 |
618 | /* Begin PBXShellScriptBuildPhase section */
619 | 9EB0BB4D25EA0A160030CE50 /* ShellScript */ = {
620 | isa = PBXShellScriptBuildPhase;
621 | buildActionMask = 2147483647;
622 | files = (
623 | );
624 | inputFileListPaths = (
625 | );
626 | inputPaths = (
627 | );
628 | outputFileListPaths = (
629 | );
630 | outputPaths = (
631 | );
632 | runOnlyForDeploymentPostprocessing = 0;
633 | shellPath = /bin/sh;
634 | shellScript = "# Type a script or drag a script file from your workspace to insert its path.\nSUFFIX=\"arm[v6][74]\"\nif [[ ! -z \"$LLVM_TARGET_TRIPLE_SUFFIX\" ]]; then\n SUFFIX=$LLVM_TARGET_TRIPLE_SUFFIX\nfi\necho $SUFFIX\necho $PATHCONFIG_RESOLVED\n";
635 | };
636 | /* End PBXShellScriptBuildPhase section */
637 |
638 | /* Begin PBXSourcesBuildPhase section */
639 | 9E406D8022E37135001CB087 /* Sources */ = {
640 | isa = PBXSourcesBuildPhase;
641 | buildActionMask = 2147483647;
642 | files = (
643 | 9E406D8722E37135001CB087 /* LKExtensionsTests.swift in Sources */,
644 | );
645 | runOnlyForDeploymentPostprocessing = 0;
646 | };
647 | 9E406D8F22E3714B001CB087 /* Sources */ = {
648 | isa = PBXSourcesBuildPhase;
649 | buildActionMask = 2147483647;
650 | files = (
651 | 9E406D9E22E37170001CB087 /* LKExtensionsTests.swift in Sources */,
652 | );
653 | runOnlyForDeploymentPostprocessing = 0;
654 | };
655 | 9E6245F82592727500CFA632 /* Sources */ = {
656 | isa = PBXSourcesBuildPhase;
657 | buildActionMask = 2147483647;
658 | files = (
659 | 9E6246012592727500CFA632 /* ContentView.swift in Sources */,
660 | 9E6245FF2592727500CFA632 /* Example_SwiftUIApp.swift in Sources */,
661 | 9E2970052595C78500CC573D /* TextGenerator.swift in Sources */,
662 | 9E62464A2592FEDF00CFA632 /* NSAttributedString+Random.swift in Sources */,
663 | );
664 | runOnlyForDeploymentPostprocessing = 0;
665 | };
666 | 9E96369722DB634A00014F86 /* Sources */ = {
667 | isa = PBXSourcesBuildPhase;
668 | buildActionMask = 2147483647;
669 | files = (
670 | 9E9636C922DC2E2A00014F86 /* LKLabelLayer.swift in Sources */,
671 | 9E9636C322DC2E2A00014F86 /* LKExtensions.swift in Sources */,
672 | 9E9636C122DC2E2A00014F86 /* LKTextDidChangeAction.swift in Sources */,
673 | 9E296FF62595A71B00CC573D /* Utils.swift in Sources */,
674 | 9E9636C522DC2E2A00014F86 /* LKLabel.swift in Sources */,
675 | 9E9636C722DC2E2A00014F86 /* LKBoundsDidChangeAction.swift in Sources */,
676 | 9E6245F125922EAB00CFA632 /* LabelView.swift in Sources */,
677 | );
678 | runOnlyForDeploymentPostprocessing = 0;
679 | };
680 | 9E9636AA22DB6CD400014F86 /* Sources */ = {
681 | isa = PBXSourcesBuildPhase;
682 | buildActionMask = 2147483647;
683 | files = (
684 | 9E9636CA22DC2E2A00014F86 /* LKLabelLayer.swift in Sources */,
685 | 9E9636C422DC2E2A00014F86 /* LKExtensions.swift in Sources */,
686 | 9E9636C222DC2E2A00014F86 /* LKTextDidChangeAction.swift in Sources */,
687 | 9E296FF72595A71B00CC573D /* Utils.swift in Sources */,
688 | 9E9636C622DC2E2A00014F86 /* LKLabel.swift in Sources */,
689 | 9E9636C822DC2E2A00014F86 /* LKBoundsDidChangeAction.swift in Sources */,
690 | 9E6245F225922EAB00CFA632 /* LabelView.swift in Sources */,
691 | );
692 | runOnlyForDeploymentPostprocessing = 0;
693 | };
694 | 9E9636E422DC32A000014F86 /* Sources */ = {
695 | isa = PBXSourcesBuildPhase;
696 | buildActionMask = 2147483647;
697 | files = (
698 | 9E9636EF22DC32A000014F86 /* ViewController.swift in Sources */,
699 | 9E6246482592FEDF00CFA632 /* NSAttributedString+Random.swift in Sources */,
700 | 9E9636EB22DC32A000014F86 /* AppDelegate.swift in Sources */,
701 | 9E9636ED22DC32A000014F86 /* SceneDelegate.swift in Sources */,
702 | );
703 | runOnlyForDeploymentPostprocessing = 0;
704 | };
705 | 9EF4973322DEA73600F5F260 /* Sources */ = {
706 | isa = PBXSourcesBuildPhase;
707 | buildActionMask = 2147483647;
708 | files = (
709 | 9E30382222DEA7FF00E3C385 /* ViewController.swift in Sources */,
710 | 9EF4973A22DEA73600F5F260 /* AppDelegate.swift in Sources */,
711 | 9E6246492592FEDF00CFA632 /* NSAttributedString+Random.swift in Sources */,
712 | );
713 | runOnlyForDeploymentPostprocessing = 0;
714 | };
715 | /* End PBXSourcesBuildPhase section */
716 |
717 | /* Begin PBXTargetDependency section */
718 | 9E406D8B22E37135001CB087 /* PBXTargetDependency */ = {
719 | isa = PBXTargetDependency;
720 | target = 9E96369A22DB634A00014F86 /* LabelKit iOS */;
721 | targetProxy = 9E406D8A22E37135001CB087 /* PBXContainerItemProxy */;
722 | };
723 | 9E406D9A22E3714B001CB087 /* PBXTargetDependency */ = {
724 | isa = PBXTargetDependency;
725 | target = 9E9636AD22DB6CD400014F86 /* LabelKit tvOS */;
726 | targetProxy = 9E406D9922E3714B001CB087 /* PBXContainerItemProxy */;
727 | };
728 | 9E6246222592731300CFA632 /* PBXTargetDependency */ = {
729 | isa = PBXTargetDependency;
730 | target = 9E96369A22DB634A00014F86 /* LabelKit iOS */;
731 | targetProxy = 9E6246212592731300CFA632 /* PBXContainerItemProxy */;
732 | };
733 | /* End PBXTargetDependency section */
734 |
735 | /* Begin PBXVariantGroup section */
736 | 9E9636F022DC32A000014F86 /* Main.storyboard */ = {
737 | isa = PBXVariantGroup;
738 | children = (
739 | 9E9636F122DC32A000014F86 /* Base */,
740 | );
741 | name = Main.storyboard;
742 | sourceTree = "";
743 | };
744 | 9E9636F522DC32A200014F86 /* LaunchScreen.storyboard */ = {
745 | isa = PBXVariantGroup;
746 | children = (
747 | 9E9636F622DC32A200014F86 /* Base */,
748 | );
749 | name = LaunchScreen.storyboard;
750 | sourceTree = "";
751 | };
752 | 9EF4973D22DEA73600F5F260 /* Main.storyboard */ = {
753 | isa = PBXVariantGroup;
754 | children = (
755 | 9EF4973E22DEA73600F5F260 /* Base */,
756 | );
757 | name = Main.storyboard;
758 | sourceTree = "";
759 | };
760 | /* End PBXVariantGroup section */
761 |
762 | /* Begin XCBuildConfiguration section */
763 | 9E2B92D722BAFC7400A007F9 /* Debug */ = {
764 | isa = XCBuildConfiguration;
765 | baseConfigurationReference = 9E478D3E25EC1A9B00BA51A8 /* Config.xcconfig */;
766 | buildSettings = {
767 | ALWAYS_SEARCH_USER_PATHS = NO;
768 | CLANG_ANALYZER_NONNULL = YES;
769 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
770 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
771 | CLANG_CXX_LIBRARY = "libc++";
772 | CLANG_ENABLE_MODULES = YES;
773 | CLANG_ENABLE_OBJC_ARC = YES;
774 | CLANG_ENABLE_OBJC_WEAK = YES;
775 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
776 | CLANG_WARN_BOOL_CONVERSION = YES;
777 | CLANG_WARN_COMMA = YES;
778 | CLANG_WARN_CONSTANT_CONVERSION = YES;
779 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
780 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
781 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
782 | CLANG_WARN_EMPTY_BODY = YES;
783 | CLANG_WARN_ENUM_CONVERSION = YES;
784 | CLANG_WARN_INFINITE_RECURSION = YES;
785 | CLANG_WARN_INT_CONVERSION = YES;
786 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
787 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
788 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
789 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
790 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
791 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
792 | CLANG_WARN_STRICT_PROTOTYPES = YES;
793 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
794 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
795 | CLANG_WARN_UNREACHABLE_CODE = YES;
796 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
797 | CODE_SIGN_IDENTITY = "iPhone Developer";
798 | COPY_PHASE_STRIP = NO;
799 | DEBUG_INFORMATION_FORMAT = dwarf;
800 | ENABLE_STRICT_OBJC_MSGSEND = YES;
801 | ENABLE_TESTABILITY = YES;
802 | GCC_C_LANGUAGE_STANDARD = gnu11;
803 | GCC_DYNAMIC_NO_PIC = NO;
804 | GCC_NO_COMMON_BLOCKS = YES;
805 | GCC_OPTIMIZATION_LEVEL = 0;
806 | GCC_PREPROCESSOR_DEFINITIONS = (
807 | "DEBUG=1",
808 | "$(inherited)",
809 | );
810 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
811 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
812 | GCC_WARN_UNDECLARED_SELECTOR = YES;
813 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
814 | GCC_WARN_UNUSED_FUNCTION = YES;
815 | GCC_WARN_UNUSED_VARIABLE = YES;
816 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
817 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
818 | MTL_FAST_MATH = YES;
819 | ONLY_ACTIVE_ARCH = YES;
820 | SDKROOT = iphoneos;
821 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
822 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
823 | SWIFT_VERSION = 5.0;
824 | };
825 | name = Debug;
826 | };
827 | 9E2B92D822BAFC7400A007F9 /* Release */ = {
828 | isa = XCBuildConfiguration;
829 | buildSettings = {
830 | ALWAYS_SEARCH_USER_PATHS = NO;
831 | CLANG_ANALYZER_NONNULL = YES;
832 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
833 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
834 | CLANG_CXX_LIBRARY = "libc++";
835 | CLANG_ENABLE_MODULES = YES;
836 | CLANG_ENABLE_OBJC_ARC = YES;
837 | CLANG_ENABLE_OBJC_WEAK = YES;
838 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
839 | CLANG_WARN_BOOL_CONVERSION = YES;
840 | CLANG_WARN_COMMA = YES;
841 | CLANG_WARN_CONSTANT_CONVERSION = YES;
842 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
843 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
844 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
845 | CLANG_WARN_EMPTY_BODY = YES;
846 | CLANG_WARN_ENUM_CONVERSION = YES;
847 | CLANG_WARN_INFINITE_RECURSION = YES;
848 | CLANG_WARN_INT_CONVERSION = YES;
849 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
850 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
851 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
852 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
853 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
854 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
855 | CLANG_WARN_STRICT_PROTOTYPES = YES;
856 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
857 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
858 | CLANG_WARN_UNREACHABLE_CODE = YES;
859 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
860 | CODE_SIGN_IDENTITY = "iPhone Developer";
861 | COPY_PHASE_STRIP = NO;
862 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
863 | ENABLE_NS_ASSERTIONS = NO;
864 | ENABLE_STRICT_OBJC_MSGSEND = YES;
865 | GCC_C_LANGUAGE_STANDARD = gnu11;
866 | GCC_NO_COMMON_BLOCKS = YES;
867 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
868 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
869 | GCC_WARN_UNDECLARED_SELECTOR = YES;
870 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
871 | GCC_WARN_UNUSED_FUNCTION = YES;
872 | GCC_WARN_UNUSED_VARIABLE = YES;
873 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
874 | MTL_ENABLE_DEBUG_INFO = NO;
875 | MTL_FAST_MATH = YES;
876 | SDKROOT = iphoneos;
877 | SWIFT_COMPILATION_MODE = wholemodule;
878 | SWIFT_OPTIMIZATION_LEVEL = "-O";
879 | SWIFT_VERSION = 5.0;
880 | VALIDATE_PRODUCT = YES;
881 | };
882 | name = Release;
883 | };
884 | 9E406D8D22E37135001CB087 /* Debug */ = {
885 | isa = XCBuildConfiguration;
886 | buildSettings = {
887 | CODE_SIGN_STYLE = Automatic;
888 | INFOPLIST_FILE = "LabelKit iOS Tests/Info.plist";
889 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
890 | LD_RUNPATH_SEARCH_PATHS = (
891 | "$(inherited)",
892 | "@executable_path/Frameworks",
893 | "@loader_path/Frameworks",
894 | );
895 | PRODUCT_BUNDLE_IDENTIFIER = "com.labelkit.LabelKit-iOS-Tests";
896 | PRODUCT_NAME = "$(TARGET_NAME)";
897 | SWIFT_VERSION = 5.0;
898 | TARGETED_DEVICE_FAMILY = "1,2";
899 | TVOS_DEPLOYMENT_TARGET = 12.0;
900 | };
901 | name = Debug;
902 | };
903 | 9E406D8E22E37135001CB087 /* Release */ = {
904 | isa = XCBuildConfiguration;
905 | buildSettings = {
906 | CODE_SIGN_STYLE = Automatic;
907 | INFOPLIST_FILE = "LabelKit iOS Tests/Info.plist";
908 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
909 | LD_RUNPATH_SEARCH_PATHS = (
910 | "$(inherited)",
911 | "@executable_path/Frameworks",
912 | "@loader_path/Frameworks",
913 | );
914 | PRODUCT_BUNDLE_IDENTIFIER = "com.labelkit.LabelKit-iOS-Tests";
915 | PRODUCT_NAME = "$(TARGET_NAME)";
916 | SWIFT_VERSION = 5.0;
917 | TARGETED_DEVICE_FAMILY = "1,2";
918 | TVOS_DEPLOYMENT_TARGET = 12.0;
919 | };
920 | name = Release;
921 | };
922 | 9E406D9C22E3714B001CB087 /* Debug */ = {
923 | isa = XCBuildConfiguration;
924 | buildSettings = {
925 | CODE_SIGN_STYLE = Automatic;
926 | INFOPLIST_FILE = "LabelKit tvOS Tests/Info.plist";
927 | LD_RUNPATH_SEARCH_PATHS = (
928 | "$(inherited)",
929 | "@executable_path/Frameworks",
930 | "@loader_path/Frameworks",
931 | );
932 | PRODUCT_BUNDLE_IDENTIFIER = "com.labelkit.LabelKit-tvOS-Tests";
933 | PRODUCT_NAME = "$(TARGET_NAME)";
934 | SDKROOT = appletvos;
935 | SWIFT_VERSION = 5.0;
936 | TARGETED_DEVICE_FAMILY = 3;
937 | TVOS_DEPLOYMENT_TARGET = 12.0;
938 | };
939 | name = Debug;
940 | };
941 | 9E406D9D22E3714B001CB087 /* Release */ = {
942 | isa = XCBuildConfiguration;
943 | buildSettings = {
944 | CODE_SIGN_STYLE = Automatic;
945 | INFOPLIST_FILE = "LabelKit tvOS Tests/Info.plist";
946 | LD_RUNPATH_SEARCH_PATHS = (
947 | "$(inherited)",
948 | "@executable_path/Frameworks",
949 | "@loader_path/Frameworks",
950 | );
951 | PRODUCT_BUNDLE_IDENTIFIER = "com.labelkit.LabelKit-tvOS-Tests";
952 | PRODUCT_NAME = "$(TARGET_NAME)";
953 | SDKROOT = appletvos;
954 | SWIFT_VERSION = 5.0;
955 | TARGETED_DEVICE_FAMILY = 3;
956 | TVOS_DEPLOYMENT_TARGET = 12.0;
957 | };
958 | name = Release;
959 | };
960 | 9E6246092592727600CFA632 /* Debug */ = {
961 | isa = XCBuildConfiguration;
962 | buildSettings = {
963 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
964 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
965 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
966 | CODE_SIGN_ENTITLEMENTS = "Example SwiftUI/Example SwiftUI.entitlements";
967 | CODE_SIGN_STYLE = Automatic;
968 | DEVELOPMENT_ASSET_PATHS = "\"Example SwiftUI/Preview Content\"";
969 | DEVELOPMENT_TEAM = UP8HTB5ZA2;
970 | ENABLE_PREVIEWS = YES;
971 | INFOPLIST_FILE = "Example SwiftUI/Info.plist";
972 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
973 | LD_RUNPATH_SEARCH_PATHS = (
974 | "$(inherited)",
975 | "@executable_path/Frameworks",
976 | );
977 | PRODUCT_BUNDLE_IDENTIFIER = "com.labelkit.Example-SwiftUI";
978 | PRODUCT_NAME = "$(TARGET_NAME)";
979 | SUPPORTS_MACCATALYST = YES;
980 | SWIFT_VERSION = 5.0;
981 | TARGETED_DEVICE_FAMILY = "1,2";
982 | };
983 | name = Debug;
984 | };
985 | 9E62460A2592727600CFA632 /* Release */ = {
986 | isa = XCBuildConfiguration;
987 | buildSettings = {
988 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
989 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
990 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
991 | CODE_SIGN_ENTITLEMENTS = "Example SwiftUI/Example SwiftUI.entitlements";
992 | CODE_SIGN_STYLE = Automatic;
993 | DEVELOPMENT_ASSET_PATHS = "\"Example SwiftUI/Preview Content\"";
994 | DEVELOPMENT_TEAM = UP8HTB5ZA2;
995 | ENABLE_PREVIEWS = YES;
996 | INFOPLIST_FILE = "Example SwiftUI/Info.plist";
997 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
998 | LD_RUNPATH_SEARCH_PATHS = (
999 | "$(inherited)",
1000 | "@executable_path/Frameworks",
1001 | );
1002 | PRODUCT_BUNDLE_IDENTIFIER = "com.labelkit.Example-SwiftUI";
1003 | PRODUCT_NAME = "$(TARGET_NAME)";
1004 | SUPPORTS_MACCATALYST = YES;
1005 | SWIFT_VERSION = 5.0;
1006 | TARGETED_DEVICE_FAMILY = "1,2";
1007 | };
1008 | name = Release;
1009 | };
1010 | 9E9636A122DB634A00014F86 /* Debug */ = {
1011 | isa = XCBuildConfiguration;
1012 | buildSettings = {
1013 | BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
1014 | CODE_SIGN_STYLE = Automatic;
1015 | CURRENT_PROJECT_VERSION = 2;
1016 | DEFINES_MODULE = YES;
1017 | DYLIB_COMPATIBILITY_VERSION = 1;
1018 | DYLIB_CURRENT_VERSION = 1;
1019 | DYLIB_INSTALL_NAME_BASE = "@rpath";
1020 | INFOPLIST_FILE = Sources/Info.plist;
1021 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
1022 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
1023 | LD_RUNPATH_SEARCH_PATHS = (
1024 | "$(inherited)",
1025 | "@executable_path/Frameworks",
1026 | "@loader_path/Frameworks",
1027 | );
1028 | MACH_O_TYPE = mh_dylib;
1029 | MARKETING_VERSION = 1.0;
1030 | MODULEMAP_FILE = Sources/module.modulemap;
1031 | PRODUCT_BUNDLE_IDENTIFIER = "com.labelkit.LabelKit-iOS";
1032 | PRODUCT_NAME = LabelKit;
1033 | SKIP_INSTALL = YES;
1034 | SWIFT_VERSION = 5.0;
1035 | TARGETED_DEVICE_FAMILY = "1,2";
1036 | VERSIONING_SYSTEM = "apple-generic";
1037 | VERSION_INFO_BUILDER = edudnyk;
1038 | VERSION_INFO_PREFIX = "";
1039 | };
1040 | name = Debug;
1041 | };
1042 | 9E9636A222DB634A00014F86 /* Release */ = {
1043 | isa = XCBuildConfiguration;
1044 | buildSettings = {
1045 | BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
1046 | CODE_SIGN_STYLE = Automatic;
1047 | CURRENT_PROJECT_VERSION = 2;
1048 | DEFINES_MODULE = YES;
1049 | DYLIB_COMPATIBILITY_VERSION = 1;
1050 | DYLIB_CURRENT_VERSION = 1;
1051 | DYLIB_INSTALL_NAME_BASE = "@rpath";
1052 | INFOPLIST_FILE = Sources/Info.plist;
1053 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
1054 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
1055 | LD_RUNPATH_SEARCH_PATHS = (
1056 | "$(inherited)",
1057 | "@executable_path/Frameworks",
1058 | "@loader_path/Frameworks",
1059 | );
1060 | MACH_O_TYPE = mh_dylib;
1061 | MARKETING_VERSION = 1.0;
1062 | MODULEMAP_FILE = Sources/module.modulemap;
1063 | PRODUCT_BUNDLE_IDENTIFIER = "com.labelkit.LabelKit-iOS";
1064 | PRODUCT_NAME = LabelKit;
1065 | SKIP_INSTALL = YES;
1066 | SWIFT_VERSION = 5.0;
1067 | TARGETED_DEVICE_FAMILY = "1,2";
1068 | VERSIONING_SYSTEM = "apple-generic";
1069 | VERSION_INFO_BUILDER = edudnyk;
1070 | VERSION_INFO_PREFIX = "";
1071 | };
1072 | name = Release;
1073 | };
1074 | 9E9636B422DB6CD400014F86 /* Debug */ = {
1075 | isa = XCBuildConfiguration;
1076 | buildSettings = {
1077 | CODE_SIGN_STYLE = Automatic;
1078 | CURRENT_PROJECT_VERSION = 2;
1079 | DEFINES_MODULE = YES;
1080 | DYLIB_COMPATIBILITY_VERSION = 1;
1081 | DYLIB_CURRENT_VERSION = 1;
1082 | DYLIB_INSTALL_NAME_BASE = "@rpath";
1083 | INFOPLIST_FILE = Sources/Info.plist;
1084 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
1085 | LD_RUNPATH_SEARCH_PATHS = (
1086 | "$(inherited)",
1087 | "@executable_path/Frameworks",
1088 | "@loader_path/Frameworks",
1089 | );
1090 | MACH_O_TYPE = mh_dylib;
1091 | MARKETING_VERSION = 1.0;
1092 | MODULEMAP_FILE = Sources/module.modulemap;
1093 | PRODUCT_BUNDLE_IDENTIFIER = "com.labelkit.LabelKit-tvOS";
1094 | PRODUCT_NAME = LabelKit;
1095 | SDKROOT = appletvos;
1096 | SKIP_INSTALL = YES;
1097 | SWIFT_VERSION = 5.0;
1098 | TARGETED_DEVICE_FAMILY = 3;
1099 | TVOS_DEPLOYMENT_TARGET = 12.0;
1100 | VERSIONING_SYSTEM = "apple-generic";
1101 | VERSION_INFO_BUILDER = edudnyk;
1102 | VERSION_INFO_PREFIX = "";
1103 | };
1104 | name = Debug;
1105 | };
1106 | 9E9636B522DB6CD400014F86 /* Release */ = {
1107 | isa = XCBuildConfiguration;
1108 | buildSettings = {
1109 | CODE_SIGN_STYLE = Automatic;
1110 | CURRENT_PROJECT_VERSION = 2;
1111 | DEFINES_MODULE = YES;
1112 | DYLIB_COMPATIBILITY_VERSION = 1;
1113 | DYLIB_CURRENT_VERSION = 1;
1114 | DYLIB_INSTALL_NAME_BASE = "@rpath";
1115 | INFOPLIST_FILE = Sources/Info.plist;
1116 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
1117 | LD_RUNPATH_SEARCH_PATHS = (
1118 | "$(inherited)",
1119 | "@executable_path/Frameworks",
1120 | "@loader_path/Frameworks",
1121 | );
1122 | MACH_O_TYPE = mh_dylib;
1123 | MARKETING_VERSION = 1.0;
1124 | MODULEMAP_FILE = Sources/module.modulemap;
1125 | PRODUCT_BUNDLE_IDENTIFIER = "com.labelkit.LabelKit-tvOS";
1126 | PRODUCT_NAME = LabelKit;
1127 | SDKROOT = appletvos;
1128 | SKIP_INSTALL = YES;
1129 | SWIFT_VERSION = 5.0;
1130 | TARGETED_DEVICE_FAMILY = 3;
1131 | TVOS_DEPLOYMENT_TARGET = 12.0;
1132 | VERSIONING_SYSTEM = "apple-generic";
1133 | VERSION_INFO_BUILDER = edudnyk;
1134 | VERSION_INFO_PREFIX = "";
1135 | };
1136 | name = Release;
1137 | };
1138 | 9E9636FA22DC32A200014F86 /* Debug */ = {
1139 | isa = XCBuildConfiguration;
1140 | buildSettings = {
1141 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
1142 | CODE_SIGN_ENTITLEMENTS = "Example iOS/Example iOS.entitlements";
1143 | CODE_SIGN_STYLE = Automatic;
1144 | CURRENT_PROJECT_VERSION = 2;
1145 | DEVELOPMENT_TEAM = UP8HTB5ZA2;
1146 | INFOPLIST_FILE = "Example iOS/Info.plist";
1147 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
1148 | LD_RUNPATH_SEARCH_PATHS = (
1149 | "$(inherited)",
1150 | "@executable_path/Frameworks",
1151 | );
1152 | PRODUCT_BUNDLE_IDENTIFIER = "com.labelkit.Example-iOS";
1153 | PRODUCT_NAME = "$(TARGET_NAME)";
1154 | SUPPORTS_MACCATALYST = YES;
1155 | SWIFT_VERSION = 5.0;
1156 | TARGETED_DEVICE_FAMILY = "1,2";
1157 | TVOS_DEPLOYMENT_TARGET = 12.0;
1158 | VERSIONING_SYSTEM = "apple-generic";
1159 | VERSION_INFO_BUILDER = edudnyk;
1160 | };
1161 | name = Debug;
1162 | };
1163 | 9E9636FB22DC32A200014F86 /* Release */ = {
1164 | isa = XCBuildConfiguration;
1165 | buildSettings = {
1166 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
1167 | CODE_SIGN_ENTITLEMENTS = "Example iOS/Example iOS.entitlements";
1168 | CODE_SIGN_STYLE = Automatic;
1169 | CURRENT_PROJECT_VERSION = 2;
1170 | DEVELOPMENT_TEAM = W6T3BTXAHJ;
1171 | INFOPLIST_FILE = "Example iOS/Info.plist";
1172 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
1173 | LD_RUNPATH_SEARCH_PATHS = (
1174 | "$(inherited)",
1175 | "@executable_path/Frameworks",
1176 | );
1177 | PRODUCT_BUNDLE_IDENTIFIER = "com.labelkit.Example-iOS";
1178 | PRODUCT_NAME = "$(TARGET_NAME)";
1179 | SUPPORTS_MACCATALYST = YES;
1180 | SWIFT_VERSION = 5.0;
1181 | TARGETED_DEVICE_FAMILY = "1,2";
1182 | TVOS_DEPLOYMENT_TARGET = 12.0;
1183 | VERSIONING_SYSTEM = "apple-generic";
1184 | VERSION_INFO_BUILDER = edudnyk;
1185 | };
1186 | name = Release;
1187 | };
1188 | 9EF4974422DEA73800F5F260 /* Debug */ = {
1189 | isa = XCBuildConfiguration;
1190 | buildSettings = {
1191 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
1192 | CODE_SIGN_STYLE = Automatic;
1193 | CURRENT_PROJECT_VERSION = 2;
1194 | DEVELOPMENT_TEAM = W6T3BTXAHJ;
1195 | INFOPLIST_FILE = "Example tvOS/Info.plist";
1196 | LD_RUNPATH_SEARCH_PATHS = (
1197 | "$(inherited)",
1198 | "@executable_path/Frameworks",
1199 | );
1200 | PRODUCT_BUNDLE_IDENTIFIER = "com.labelkit.Example-tvOS";
1201 | PRODUCT_NAME = "$(TARGET_NAME)";
1202 | SDKROOT = appletvos;
1203 | SWIFT_VERSION = 5.0;
1204 | TARGETED_DEVICE_FAMILY = 3;
1205 | TVOS_DEPLOYMENT_TARGET = 12.0;
1206 | VERSIONING_SYSTEM = "apple-generic";
1207 | VERSION_INFO_BUILDER = edudnyk;
1208 | };
1209 | name = Debug;
1210 | };
1211 | 9EF4974522DEA73800F5F260 /* Release */ = {
1212 | isa = XCBuildConfiguration;
1213 | buildSettings = {
1214 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
1215 | CODE_SIGN_STYLE = Automatic;
1216 | CURRENT_PROJECT_VERSION = 2;
1217 | DEVELOPMENT_TEAM = W6T3BTXAHJ;
1218 | INFOPLIST_FILE = "Example tvOS/Info.plist";
1219 | LD_RUNPATH_SEARCH_PATHS = (
1220 | "$(inherited)",
1221 | "@executable_path/Frameworks",
1222 | );
1223 | PRODUCT_BUNDLE_IDENTIFIER = "com.labelkit.Example-tvOS";
1224 | PRODUCT_NAME = "$(TARGET_NAME)";
1225 | SDKROOT = appletvos;
1226 | SWIFT_VERSION = 5.0;
1227 | TARGETED_DEVICE_FAMILY = 3;
1228 | TVOS_DEPLOYMENT_TARGET = 12.0;
1229 | VERSIONING_SYSTEM = "apple-generic";
1230 | VERSION_INFO_BUILDER = edudnyk;
1231 | };
1232 | name = Release;
1233 | };
1234 | /* End XCBuildConfiguration section */
1235 |
1236 | /* Begin XCConfigurationList section */
1237 | 9E2B92CD22BAFC7400A007F9 /* Build configuration list for PBXProject "LabelKit" */ = {
1238 | isa = XCConfigurationList;
1239 | buildConfigurations = (
1240 | 9E2B92D722BAFC7400A007F9 /* Debug */,
1241 | 9E2B92D822BAFC7400A007F9 /* Release */,
1242 | );
1243 | defaultConfigurationIsVisible = 0;
1244 | defaultConfigurationName = Release;
1245 | };
1246 | 9E406D8C22E37135001CB087 /* Build configuration list for PBXNativeTarget "LabelKit iOS Tests" */ = {
1247 | isa = XCConfigurationList;
1248 | buildConfigurations = (
1249 | 9E406D8D22E37135001CB087 /* Debug */,
1250 | 9E406D8E22E37135001CB087 /* Release */,
1251 | );
1252 | defaultConfigurationIsVisible = 0;
1253 | defaultConfigurationName = Release;
1254 | };
1255 | 9E406D9B22E3714B001CB087 /* Build configuration list for PBXNativeTarget "LabelKit tvOS Tests" */ = {
1256 | isa = XCConfigurationList;
1257 | buildConfigurations = (
1258 | 9E406D9C22E3714B001CB087 /* Debug */,
1259 | 9E406D9D22E3714B001CB087 /* Release */,
1260 | );
1261 | defaultConfigurationIsVisible = 0;
1262 | defaultConfigurationName = Release;
1263 | };
1264 | 9E6246082592727600CFA632 /* Build configuration list for PBXNativeTarget "Example SwiftUI" */ = {
1265 | isa = XCConfigurationList;
1266 | buildConfigurations = (
1267 | 9E6246092592727600CFA632 /* Debug */,
1268 | 9E62460A2592727600CFA632 /* Release */,
1269 | );
1270 | defaultConfigurationIsVisible = 0;
1271 | defaultConfigurationName = Release;
1272 | };
1273 | 9E9636A022DB634A00014F86 /* Build configuration list for PBXNativeTarget "LabelKit iOS" */ = {
1274 | isa = XCConfigurationList;
1275 | buildConfigurations = (
1276 | 9E9636A122DB634A00014F86 /* Debug */,
1277 | 9E9636A222DB634A00014F86 /* Release */,
1278 | );
1279 | defaultConfigurationIsVisible = 0;
1280 | defaultConfigurationName = Release;
1281 | };
1282 | 9E9636B322DB6CD400014F86 /* Build configuration list for PBXNativeTarget "LabelKit tvOS" */ = {
1283 | isa = XCConfigurationList;
1284 | buildConfigurations = (
1285 | 9E9636B422DB6CD400014F86 /* Debug */,
1286 | 9E9636B522DB6CD400014F86 /* Release */,
1287 | );
1288 | defaultConfigurationIsVisible = 0;
1289 | defaultConfigurationName = Release;
1290 | };
1291 | 9E9636F922DC32A200014F86 /* Build configuration list for PBXNativeTarget "Example iOS" */ = {
1292 | isa = XCConfigurationList;
1293 | buildConfigurations = (
1294 | 9E9636FA22DC32A200014F86 /* Debug */,
1295 | 9E9636FB22DC32A200014F86 /* Release */,
1296 | );
1297 | defaultConfigurationIsVisible = 0;
1298 | defaultConfigurationName = Release;
1299 | };
1300 | 9EF4974322DEA73800F5F260 /* Build configuration list for PBXNativeTarget "Example tvOS" */ = {
1301 | isa = XCConfigurationList;
1302 | buildConfigurations = (
1303 | 9EF4974422DEA73800F5F260 /* Debug */,
1304 | 9EF4974522DEA73800F5F260 /* Release */,
1305 | );
1306 | defaultConfigurationIsVisible = 0;
1307 | defaultConfigurationName = Release;
1308 | };
1309 | /* End XCConfigurationList section */
1310 | };
1311 | rootObject = 9E2B92CA22BAFC7400A007F9 /* Project object */;
1312 | }
1313 |
--------------------------------------------------------------------------------