├── .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 | # ![LabelKit](https://media.githubusercontent.com/media/edudnyk/LabelKit/master/LabelKit.gif) 2 | 3 | [![build status](https://travis-ci.org/edudnyk/LabelKit.svg)](https://travis-ci.org/edudnyk/LabelKit) 4 | [![cocoapods compatible](https://img.shields.io/badge/cocoapods-compatible-brightgreen.svg)](https://cocoapods.org/pods/LabelKit) 5 | [![carthage compatible](https://img.shields.io/badge/carthage-compatible-brightgreen.svg)](https://github.com/Carthage/Carthage) 6 | [![language](https://img.shields.io/badge/spm-compatible-brightgreen.svg)](https://swift.org) 7 | [![swift](https://img.shields.io/badge/swift-5.3-green.svg)](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 | ![xcode](https://img.shields.io/badge/xcode-12%2b-lightgrey.svg) 26 | ![ios](https://img.shields.io/badge/ios-12.0%2b-lightgrey.svg) 27 | ![tvos](https://img.shields.io/badge/tvos-12.0%2b-lightgrey.svg) 28 | ![mac os](https://img.shields.io/badge/mac%20os-10.15%2b-lightgrey.svg) 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 | --------------------------------------------------------------------------------