├── Logo.png ├── .spi.yml ├── Tests ├── HandyUIKitTests │ ├── Resources │ │ └── Images.xcassets │ │ │ ├── Contents.json │ │ │ └── matt-lamers-261639-unsplash.imageset │ │ │ ├── matt-lamers-261639-unsplash.jpg │ │ │ └── Contents.json │ └── Extensions │ │ ├── UIImageExtTests.swift │ │ ├── StringExtTests.swift │ │ ├── ColorExtTests.swift │ │ └── CoreGraphicsExtTests.swift └── SupportingFiles │ └── Info.plist ├── UsageExamples.playground ├── Resources │ └── matt-lamers-261639-unsplash.jpg ├── contents.xcplayground └── Contents.swift ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── HandyUIKit.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── HandyUIKit.xcscmblueprint ├── xcshareddata │ ├── IDETemplateMacros.plist │ ├── xcbaselines │ │ └── A14E0AC41E1F986A00DFC788.xcbaseline │ │ │ ├── 3D80E656-87F8-4EF7-BE38-E1CEF9029979.plist │ │ │ ├── C3ECA4DA-1F82-4ABC-A31B-85A51942BB0D.plist │ │ │ └── Info.plist │ └── xcschemes │ │ ├── HandyUIKit-iOS.xcscheme │ │ └── HandyUIKit-tvOS.xcscheme ├── xcuserdata │ └── Privat.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── project.pbxproj ├── HandyUIKit.xcworkspace ├── xcshareddata │ ├── IDEWorkspaceChecks.plist │ └── HandyUIKit.xcscmblueprint └── contents.xcworkspacedata ├── Frameworks ├── HandyUIKit │ ├── IBDesignables │ │ ├── TemplateButton.swift │ │ ├── TemplateImageView.swift │ │ └── RoundableView.swift │ ├── Extensions │ │ ├── CGPointExt.swift │ │ ├── CGSizeExt.swift │ │ ├── UIImageExt.swift │ │ ├── CGRectExt.swift │ │ ├── UIWindowExt.swift │ │ ├── UITableViewExt.swift │ │ ├── StringExt.swift │ │ ├── UIViewExt.swift │ │ ├── NSAttributedStringExt.swift │ │ └── UIColorExt.swift │ ├── NibLoadable.swift │ └── ColorSpaces.swift └── SupportingFiles │ └── Info.plist ├── Package.swift ├── .github └── workflows │ └── main.yml ├── LICENSE ├── CONTRIBUTING.md ├── HandyUIKit.podspec ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── .swiftlint.yml └── README.md /Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlineDev/HandyUIKit/HEAD/Logo.png -------------------------------------------------------------------------------- /.spi.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | builder: 3 | configs: 4 | - platform: ios 5 | documentation_targets: [HandyUIKit] 6 | -------------------------------------------------------------------------------- /Tests/HandyUIKitTests/Resources/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /UsageExamples.playground/Resources/matt-lamers-261639-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlineDev/HandyUIKit/HEAD/UsageExamples.playground/Resources/matt-lamers-261639-unsplash.jpg -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /UsageExamples.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /HandyUIKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Tests/HandyUIKitTests/Resources/Images.xcassets/matt-lamers-261639-unsplash.imageset/matt-lamers-261639-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FlineDev/HandyUIKit/HEAD/Tests/HandyUIKitTests/Resources/Images.xcassets/matt-lamers-261639-unsplash.imageset/matt-lamers-261639-unsplash.jpg -------------------------------------------------------------------------------- /HandyUIKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Tests/HandyUIKitTests/Resources/Images.xcassets/matt-lamers-261639-unsplash.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "matt-lamers-261639-unsplash.jpg" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /HandyUIKit.xcodeproj/xcshareddata/IDETemplateMacros.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | FILEHEADER 6 | ___COPYRIGHT___ 7 | 8 | 9 | -------------------------------------------------------------------------------- /HandyUIKit.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Frameworks/HandyUIKit/IBDesignables/TemplateButton.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Flinesoft. All rights reserved. 2 | 3 | import UIKit 4 | 5 | @IBDesignable 6 | open class TemplateButton: UIButton { 7 | override public func setImage(_ image: UIImage?, for state: UIControl.State) { 8 | super.setImage(image?.withRenderingMode(.alwaysTemplate), for: state) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Frameworks/HandyUIKit/IBDesignables/TemplateImageView.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Flinesoft. All rights reserved. 2 | 3 | import UIKit 4 | 5 | @IBDesignable 6 | open class TemplateImageView: UIImageView { 7 | override public var image: UIImage? { 8 | get { 9 | super.image 10 | } 11 | 12 | set { 13 | super.image = newValue?.withRenderingMode(.alwaysTemplate) 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "HandyUIKit", 6 | platforms: [.iOS(.v9), .tvOS(.v9)], 7 | products: [ 8 | .library(name: "HandyUIKit", targets: ["HandyUIKit"]) 9 | ], 10 | targets: [ 11 | .target( 12 | name: "HandyUIKit", 13 | path: "Frameworks/HandyUIKit" 14 | ), 15 | .testTarget( 16 | name: "HandyUIKitTests", 17 | dependencies: ["HandyUIKit"] 18 | ) 19 | ] 20 | ) 21 | -------------------------------------------------------------------------------- /Frameworks/HandyUIKit/Extensions/CGPointExt.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Flinesoft. All rights reserved. 2 | 3 | import UIKit 4 | 5 | extension CGPoint { 6 | /// Returns a new CGPoint object with the x and y converted to true pixels on the main screen. 7 | public var inPixels: CGPoint { inPixels(.main) } 8 | 9 | /// Returns a new CGPoint object with the x and y converted to true pixels on the given screen. 10 | /// 11 | /// - Parameters: 12 | /// - screen: The target screen to convert to pixels for. 13 | public func inPixels(_ screen: UIScreen) -> CGPoint { 14 | CGPoint(x: x * screen.scale, y: y * screen.scale) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Frameworks/HandyUIKit/Extensions/CGSizeExt.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Flinesoft. All rights reserved. 2 | 3 | import UIKit 4 | 5 | extension CGSize { 6 | /// Returns a new CGSize object with the width and height converted to true pixels on the main screen. 7 | public var inPixels: CGSize { inPixels(.main) } 8 | 9 | /// Returns a new CGSize object with the width and height converted to true pixels on the given screen. 10 | /// 11 | /// - Parameters: 12 | /// - screen: The target screen to convert to pixels for. 13 | public func inPixels(_ screen: UIScreen) -> CGSize { 14 | CGSize(width: width * screen.scale, height: height * screen.scale) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /HandyUIKit.xcodeproj/xcshareddata/xcbaselines/A14E0AC41E1F986A00DFC788.xcbaseline/3D80E656-87F8-4EF7-BE38-E1CEF9029979.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | classNames 6 | 7 | UIImageExtensionTests 8 | 9 | testPerformanceToGrayscale() 10 | 11 | com.apple.XCTPerformanceMetric_WallClockTime 12 | 13 | baselineAverage 14 | 0.49135 15 | baselineIntegrationDisplayName 16 | Local Baseline 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /HandyUIKit.xcodeproj/xcshareddata/xcbaselines/A14E0AC41E1F986A00DFC788.xcbaseline/C3ECA4DA-1F82-4ABC-A31B-85A51942BB0D.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | classNames 6 | 7 | UIImageExtensionTests 8 | 9 | testPerformanceToGrayscale() 10 | 11 | com.apple.XCTPerformanceMetric_WallClockTime 12 | 13 | baselineAverage 14 | 0.49576 15 | baselineIntegrationDisplayName 16 | Local Baseline 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Tests/SupportingFiles/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Tests/HandyUIKitTests/Extensions/UIImageExtTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Flinesoft. All rights reserved. 2 | 3 | @testable import HandyUIKit 4 | import XCTest 5 | 6 | class UIImageExtTests: XCTestCase { 7 | func testToGrayscale() { 8 | let image = UIImage(named: "matt-lamers-261639-unsplash", in: Bundle(for: UIImageExtTests.self), compatibleWith: nil)! 9 | let grayscaleImage = image.toGrayscale() 10 | 11 | XCTAssert(grayscaleImage != nil) 12 | } 13 | 14 | func testPerformanceToGrayscale() { 15 | let image = UIImage(named: "matt-lamers-261639-unsplash", in: Bundle(for: UIImageExtTests.self), compatibleWith: nil)! 16 | 17 | measure { 18 | (0 ..< 5).forEach { _ in 19 | _ = image.toGrayscale() 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Frameworks/SupportingFiles/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.9.3 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main, versions] 6 | 7 | pull_request: 8 | branches: [main] 9 | 10 | jobs: 11 | cancel-previous-runs: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Cancel previous runs of this workflow on same branch 16 | uses: rokroskar/workflow-run-cleanup-action@v0.2.2 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 | 20 | swiftlint: 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - uses: actions/checkout@v2 25 | 26 | - name: Run SwiftLint 27 | uses: norio-nomura/action-swiftlint@3.1.0 28 | with: 29 | args: --strict 30 | 31 | test: 32 | runs-on: macos-11 33 | 34 | steps: 35 | - uses: actions/checkout@v2 36 | 37 | - name: Run tests 38 | run: xcodebuild test -scheme 'HandyUIKit-iOS' -destination 'platform=iOS Simulator,name=iPhone 12,OS=latest' 39 | -------------------------------------------------------------------------------- /HandyUIKit.xcodeproj/xcuserdata/Privat.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | HandyUIKit-iOS.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | HandyUIKit-tvOS.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 1 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | A14E0ABB1E1F986A00DFC788 21 | 22 | primary 23 | 24 | 25 | A14E0AC41E1F986A00DFC788 26 | 27 | primary 28 | 29 | 30 | A14E0ADA1E1F987C00DFC788 31 | 32 | primary 33 | 34 | 35 | A14E0AE21E1F987C00DFC788 36 | 37 | primary 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Frameworks/HandyUIKit/IBDesignables/RoundableView.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Flinesoft. All rights reserved. 2 | 3 | import UIKit 4 | 5 | @IBDesignable 6 | open class RoundableView: UIView { 7 | @IBInspectable open var cornerRadius: CGFloat = 0 { 8 | didSet { 9 | update() 10 | } 11 | } 12 | 13 | override public init(frame: CGRect) { 14 | super.init(frame: frame) 15 | setup() 16 | } 17 | 18 | public required init?(coder aDecoder: NSCoder) { 19 | super.init(coder: aDecoder) 20 | setup() 21 | } 22 | 23 | override public func prepareForInterfaceBuilder() { 24 | super.prepareForInterfaceBuilder() 25 | setup() 26 | update() 27 | } 28 | 29 | override public func awakeFromNib() { 30 | super.awakeFromNib() 31 | update() 32 | } 33 | 34 | override public func layoutSubviews() { 35 | super.layoutSubviews() 36 | update() 37 | } 38 | 39 | private func setup() { 40 | clipsToBounds = true 41 | } 42 | 43 | private func update() { 44 | layer.cornerRadius = cornerRadius 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2015-2018 Flinesoft 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Frameworks/HandyUIKit/Extensions/UIImageExt.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Flinesoft. All rights reserved. 2 | 3 | import UIKit 4 | 5 | extension UIImage { 6 | /// Creates a grayscale version of the image. 7 | /// 8 | /// - Returns: The grayscale image. 9 | public func toGrayscale() -> UIImage? { 10 | let imageRect = CGRect(x: 0, y: 0, width: size.width * scale, height: size.height * scale) 11 | let colorSpace = CGColorSpaceCreateDeviceGray() 12 | let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue) 13 | let contextOptional = CGContext( 14 | data: nil, 15 | width: Int(size.width * scale), 16 | height: Int(size.height * scale), 17 | bitsPerComponent: 8, 18 | bytesPerRow: 0, 19 | space: colorSpace, 20 | bitmapInfo: bitmapInfo.rawValue 21 | ) 22 | 23 | guard let context = contextOptional else { return nil } 24 | context.draw(cgImage!, in: imageRect) 25 | 26 | guard let grayscaleCgImage = context.makeImage() else { return nil } 27 | return UIImage(cgImage: grayscaleCgImage) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Bug reports and pull requests are welcome on GitHub at https://github.com/Flinesoft/HandyUIKit. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. 4 | 5 | Pull requests with new features will only be accepted when the following are given: 6 | - The UI feature is **handy** but not (yet) part of UIKit. 7 | - **Tests** for the new feature exist and all tests pass successfully. 8 | - **Usage examples** of the new feature are given in the Playgrounds. 9 | 10 | ## Getting Started 11 | 12 | This section will tell you how you can get started contributing to HandyUIKit. 13 | 14 | ### Prerequisites 15 | 16 | Before you start developing, please make sure you have the following tools installed on your machine: 17 | 18 | - Xcode 10.0+ 19 | - [SwiftLint](https://github.com/realm/SwiftLint) 20 | - [ProjLint](https://github.com/JamitLabs/ProjLint) 21 | 22 | ### Commit Messages 23 | 24 | Please also try to follow the same syntax and semantic in your **commit messages** (see rationale [here](http://chris.beams.io/posts/git-commit/)). 25 | -------------------------------------------------------------------------------- /Tests/HandyUIKitTests/Extensions/StringExtTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Flinesoft. All rights reserved. 2 | 3 | @testable import HandyUIKit 4 | import XCTest 5 | 6 | class StringExtTests: XCTestCase { 7 | let loremIpsum: String = "Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." // swiftlint:disable:this line_length 8 | 9 | func testHeight() { 10 | let font = UIFont(name: "Helvetica Neue", size: 16)! 11 | let calculatedHeight = loremIpsum.height(forFixedWidth: 300, font: font) 12 | 13 | XCTAssertEqual(calculatedHeight, 206, accuracy: 0.001) 14 | } 15 | 16 | func testWidth() { 17 | let font = UIFont(name: "Helvetica Neue", size: 16)! 18 | let calculatedHeight = loremIpsum.width(forFixedHeight: 21, font: font) 19 | 20 | XCTAssertEqual(calculatedHeight, 3_015, accuracy: 0.001) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /HandyUIKit.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | s.name = "HandyUIKit" 4 | s.version = "1.9.3" 5 | s.summary = "Handy UI features that should have been part of UIKit in the first place." 6 | 7 | s.description = <<-DESC 8 | The goal of this library is to provide handy UI related features that we feel like they should have been 9 | part of the UIKit frameworks themselves. Therefore this library is intended to inherit solutions for common 10 | tasks that appear in daily programming and tries to comply to the same naming conventions as already used 11 | in the Apple frameworks. 12 | DESC 13 | 14 | s.homepage = "https://github.com/Flinesoft/HandyUIKit" 15 | s.license = { :type => "MIT", :file => "LICENSE" } 16 | 17 | s.author = { "Cihat Gündüz" => "cocoapods@cihatguenduez.de" } 18 | s.social_media_url = "https://twitter.com/Jeehut" 19 | 20 | s.ios.deployment_target = "8.0" 21 | s.tvos.deployment_target = "9.0" 22 | 23 | s.source = { :git => "https://github.com/Flinesoft/HandyUIKit.git", :tag => "#{s.version}" } 24 | s.source_files = "Frameworks/HandyUIKit/**/*.swift" 25 | s.framework = "UIKit" 26 | s.swift_version = "5.0" 27 | 28 | end 29 | -------------------------------------------------------------------------------- /Frameworks/HandyUIKit/Extensions/CGRectExt.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Flinesoft. All rights reserved. 2 | 3 | import UIKit 4 | 5 | extension CGRect { 6 | /// Returns a new CGRect object with the origin and size converted to true pixels on the main screen. 7 | public var inPixels: CGRect { inPixels(.main) } 8 | 9 | /// Creates a new CGRect object from origin zero with given size. 10 | /// 11 | /// - Parameters: 12 | /// - size: The size of the new rect. 13 | public init(size: CGSize) { self.init(origin: .zero, size: size) } 14 | 15 | /// Creates a new CGRect object from origin zero with given size. 16 | /// 17 | /// - Parameters: 18 | /// - width: The width of the new rect. 19 | /// - height: The height of the new rect. 20 | public init(width: CGFloat, height: CGFloat) { 21 | self.init(origin: .zero, size: CGSize(width: width, height: height)) 22 | } 23 | 24 | /// Returns a new CGRect object with the origin and size converted to true pixels on the given screen. 25 | /// 26 | /// - Parameters: 27 | /// - screen: The target screen to convert to pixels for. 28 | public func inPixels(_ screen: UIScreen) -> CGRect { 29 | CGRect(origin: origin.inPixels(screen), size: size.inPixels(screen)) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Frameworks/HandyUIKit/Extensions/UIWindowExt.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Flinesoft. All rights reserved. 2 | 3 | import UIKit 4 | 5 | extension UIWindow { 6 | /// Returns the currently visible view controller if any reachable within the window. 7 | public var visibleViewController: UIViewController? { 8 | UIWindow.visibleViewController(from: rootViewController) 9 | } 10 | 11 | /// Recursively follows navigation controllers, tab bar controllers and modal presented view controllers starting 12 | /// from the given view controller to find the currently visible view controller. 13 | /// 14 | /// - Parameters: 15 | /// - viewController: The view controller to start the recursive search from. 16 | /// - Returns: The view controller that is most probably visible on screen right now. 17 | public static func visibleViewController(from viewController: UIViewController?) -> UIViewController? { 18 | switch viewController { 19 | case let navigationController as UINavigationController: 20 | return UIWindow.visibleViewController(from: navigationController.visibleViewController ?? navigationController.topViewController) 21 | 22 | case let tabBarController as UITabBarController: 23 | return UIWindow.visibleViewController(from: tabBarController.selectedViewController) 24 | 25 | case let presentingViewController where viewController?.presentedViewController != nil: 26 | return UIWindow.visibleViewController(from: presentingViewController?.presentedViewController) 27 | 28 | default: 29 | return viewController 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Frameworks/HandyUIKit/NibLoadable.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Flinesoft. All rights reserved. 2 | 3 | import UIKit 4 | 5 | public enum NibLoadableError: Error { 6 | case xibNotFound 7 | case xibHasNoViews 8 | case xibHasMultipleRootViews 9 | } 10 | 11 | public protocol NibLoadable: AnyObject { 12 | static var nibName: String { get } 13 | 14 | func nibDidLoad() 15 | } 16 | 17 | extension NibLoadable where Self: UIView { 18 | /// The name of the nib file. Defaults to the same name as the class. 19 | public static var nibName: String { 20 | String(describing: self) 21 | } 22 | 23 | /// Loads the contents of this view from the corresponding Nib file. 24 | /// 25 | /// NOTE: This view must be the 'File's Owner', not the 'View' within the Nib file. 26 | public func loadFromNib(bundle: Bundle) throws { 27 | let nibName = Self.nibName 28 | let nib = UINib(nibName: nibName, bundle: bundle) 29 | 30 | guard let views = nib.instantiate(withOwner: self, options: nil) as? [UIView] else { 31 | throw NibLoadableError.xibNotFound 32 | } 33 | 34 | guard !views.isEmpty else { 35 | throw NibLoadableError.xibHasNoViews 36 | } 37 | 38 | guard views.count <= 1 else { 39 | throw NibLoadableError.xibHasMultipleRootViews 40 | } 41 | 42 | let viewFromNib = views[0] 43 | addSubview(viewFromNib) 44 | viewFromNib.bindEdgesToSuperview() 45 | 46 | nibDidLoad() 47 | } 48 | 49 | /// An empty implementation of nibDidLoad so subclasses don't necessarily need to override it. 50 | public func nibDidLoad() { /* no-op */ } 51 | } 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xcuserstate 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | # Packages/ 38 | .build/ 39 | 40 | # CocoaPods 41 | # 42 | # We recommend against adding the Pods directory to your .gitignore. However 43 | # you should judge for yourself, the pros and cons are mentioned at: 44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 45 | # 46 | # Pods/ 47 | 48 | # Carthage 49 | # 50 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 51 | # Carthage/Checkouts 52 | 53 | Carthage/Build 54 | 55 | # fastlane 56 | # 57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 58 | # screenshots whenever they are needed. 59 | # For more information about the recommended setup visit: 60 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 61 | 62 | fastlane/report.xml 63 | fastlane/Preview.html 64 | fastlane/screenshots 65 | fastlane/test_output 66 | 67 | .DS_Store 68 | -------------------------------------------------------------------------------- /HandyUIKit.xcworkspace/xcshareddata/HandyUIKit.xcscmblueprint: -------------------------------------------------------------------------------- 1 | { 2 | "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "00D3D5312C073D1DF2E15F5B96147F173BA8E73B", 3 | "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { 4 | 5 | }, 6 | "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { 7 | "00D3D5312C073D1DF2E15F5B96147F173BA8E73B" : 9223372036854775807, 8 | "C0F6F4A9FFA90C8B085DCED78C3E68E1B6169F5F" : 9223372036854775807 9 | }, 10 | "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "1F0FFDE4-B7F4-451D-8950-B4DEBED08ACB", 11 | "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { 12 | "00D3D5312C073D1DF2E15F5B96147F173BA8E73B" : "HandyUIKit\/", 13 | "C0F6F4A9FFA90C8B085DCED78C3E68E1B6169F5F" : "HandySwift\/" 14 | }, 15 | "DVTSourceControlWorkspaceBlueprintNameKey" : "HandyUIKit", 16 | "DVTSourceControlWorkspaceBlueprintVersion" : 204, 17 | "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "HandyUIKit.xcworkspace", 18 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ 19 | { 20 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:Dschee\/HandyUIKit.git", 21 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 22 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "00D3D5312C073D1DF2E15F5B96147F173BA8E73B" 23 | }, 24 | { 25 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:Flinesoft\/HandySwift.git", 26 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 27 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "C0F6F4A9FFA90C8B085DCED78C3E68E1B6169F5F" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /HandyUIKit.xcodeproj/project.xcworkspace/xcshareddata/HandyUIKit.xcscmblueprint: -------------------------------------------------------------------------------- 1 | { 2 | "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "00D3D5312C073D1DF2E15F5B96147F173BA8E73B", 3 | "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { 4 | 5 | }, 6 | "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { 7 | "00D3D5312C073D1DF2E15F5B96147F173BA8E73B" : 9223372036854775807, 8 | "C0F6F4A9FFA90C8B085DCED78C3E68E1B6169F5F" : 9223372036854775807 9 | }, 10 | "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "1F0FFDE4-B7F4-451D-8950-B4DEBED08ACB", 11 | "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { 12 | "00D3D5312C073D1DF2E15F5B96147F173BA8E73B" : "HandyUIKit\/", 13 | "C0F6F4A9FFA90C8B085DCED78C3E68E1B6169F5F" : "HandySwift\/" 14 | }, 15 | "DVTSourceControlWorkspaceBlueprintNameKey" : "HandyUIKit", 16 | "DVTSourceControlWorkspaceBlueprintVersion" : 204, 17 | "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "HandyUIKit.xcodeproj", 18 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ 19 | { 20 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:Dschee\/HandyUIKit.git", 21 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 22 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "00D3D5312C073D1DF2E15F5B96147F173BA8E73B" 23 | }, 24 | { 25 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:Flinesoft\/HandySwift.git", 26 | "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", 27 | "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "C0F6F4A9FFA90C8B085DCED78C3E68E1B6169F5F" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /Frameworks/HandyUIKit/Extensions/UITableViewExt.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Flinesoft. All rights reserved. 2 | 3 | import UIKit 4 | 5 | extension UITableView { 6 | /// Returns a reusable table view cell of type `cellType` with the name of its type as reuse identifier and adds it to the table. 7 | public func dequeueCell(ofType cellType: Cell.Type, for indexPath: IndexPath) -> Cell? { 8 | dequeueReusableCell(withIdentifier: String(describing: cellType), for: indexPath) as? Cell 9 | } 10 | 11 | /// Returns a reusable header or footer view of type `viewType` with the name of its type as reuse identifier and adds it to the table. 12 | public func dequeueHeaderFooterView(ofType viewType: View.Type) -> View? { 13 | dequeueReusableHeaderFooterView(withIdentifier: String(describing: viewType)) as? View 14 | } 15 | 16 | /// Registers a nib with the name of `cellType` if it exists or registers the class of type `cellType` as reusable cell. 17 | public func registerCell(ofType cellType: Cell.Type) { 18 | let cellTypeName = String(describing: cellType) 19 | 20 | if Bundle.main.path(forResource: cellTypeName, ofType: "nib") != nil { 21 | register(UINib(nibName: cellTypeName, bundle: .main), forCellReuseIdentifier: cellTypeName) 22 | } else { 23 | register(cellType, forCellReuseIdentifier: cellTypeName) 24 | } 25 | } 26 | 27 | /// Registers a nib with the name of `viewType` if it exists or registers the class of type `viewType` as reusable header footer view. 28 | public func registerHeaderFooterView(ofType viewType: View.Type) { 29 | let viewTypeName = String(describing: viewType) 30 | 31 | if Bundle.main.path(forResource: viewTypeName, ofType: "nib") != nil { 32 | register(UINib(nibName: viewTypeName, bundle: .main), forHeaderFooterViewReuseIdentifier: viewTypeName) 33 | } else { 34 | register(viewType, forHeaderFooterViewReuseIdentifier: viewTypeName) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /HandyUIKit.xcodeproj/xcshareddata/xcbaselines/A14E0AC41E1F986A00DFC788.xcbaseline/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | runDestinationsByUUID 6 | 7 | 3D80E656-87F8-4EF7-BE38-E1CEF9029979 8 | 9 | localComputer 10 | 11 | busSpeedInMHz 12 | 100 13 | cpuCount 14 | 1 15 | cpuKind 16 | Intel Core i7 17 | cpuSpeedInMHz 18 | 2900 19 | logicalCPUCoresPerPackage 20 | 8 21 | modelCode 22 | MacBookPro14,3 23 | physicalCPUCoresPerPackage 24 | 4 25 | platformIdentifier 26 | com.apple.platform.macosx 27 | 28 | targetArchitecture 29 | x86_64 30 | targetDevice 31 | 32 | modelCode 33 | iPhone10,4 34 | platformIdentifier 35 | com.apple.platform.iphonesimulator 36 | 37 | 38 | C3ECA4DA-1F82-4ABC-A31B-85A51942BB0D 39 | 40 | localComputer 41 | 42 | busSpeedInMHz 43 | 100 44 | cpuCount 45 | 1 46 | cpuKind 47 | Intel Core i7 48 | cpuSpeedInMHz 49 | 2900 50 | logicalCPUCoresPerPackage 51 | 8 52 | modelCode 53 | MacBookPro14,3 54 | physicalCPUCoresPerPackage 55 | 4 56 | platformIdentifier 57 | com.apple.platform.macosx 58 | 59 | targetArchitecture 60 | x86_64 61 | targetDevice 62 | 63 | modelCode 64 | iPhone10,5 65 | platformIdentifier 66 | com.apple.platform.iphonesimulator 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). 5 | 6 |
7 | Formatting Rules for Entries 8 | Each entry should use the following format: 9 | 10 | ```markdown 11 | - Summary of what was changed in a single line using past tense & followed by two whitespaces. 12 | Issue: [#0](https://github.com/Flinesoft/HandySwift/issues/0) | PR: [#0](https://github.com/Flinesoft/HandySwift/pull/0) | Author: [Cihat Gündüz](https://github.com/Jeehut) 13 | ``` 14 | 15 | Note that at the end of the summary line, you need to add two whitespaces (` `) for correct rendering on GitHub. 16 | 17 | If needed, pluralize to `Tasks`, `PRs` or `Authors` and list multiple entries separated by `, `. Also, remove entries not needed in the second line. 18 |
19 | 20 | ## [Unreleased] 21 | ### Added 22 | - None. 23 | ### Changed 24 | - None. 25 | ### Deprecated 26 | - None. 27 | ### Removed 28 | - None. 29 | ### Fixed 30 | - None. 31 | ### Security 32 | - None. 33 | 34 | ## [1.9.3] - 2021-10-15 35 | ### Fixed 36 | - Fixed a warning due to a wrong path in the Swift package manifest. 37 | 38 | ## [1.9.2] - 2020-05-07 39 | ### Added 40 | - Made `cornerRadius` and `IBDesignable`s available for override. 41 | ### Changed 42 | - Updated code to Xcode 11.4 & Swift 5. 43 | 44 | ## [1.9.0] - 2019-02-11 45 | ### Added 46 | - New `NibLoadable` protocol for loading `UIView` subclasses from XIB files both from code and IB files. 47 | - New `RoundableView` IBDesignable with `cornerRadius` defined for use right within Interface Builder files. 48 | - New `TemplateButton` IBDesignable with `image` always rendered as `.alwaysTemplate` for use right within IB files. 49 | - New `TemplateImageView` IBDesignable with `image` always rendered as `.alwaysTemplate` for use right within IB files. 50 | - New `visibleViewController` properties on `UIWindow` to get the currently presented view controller globally. 51 | ### Changed 52 | - The `bindEdgesToSuperview` method now supports an optional `insets` parameter of type `UIEdgeInsets` 53 | ### Deprecated 54 | - None. 55 | ### Removed 56 | - None. 57 | ### Fixed 58 | - None. 59 | ### Security 60 | - None. 61 | 62 | 63 | ## [1.8.0] - 2018-10-31 64 | ### Added 65 | - UITableViewExtension to dequeue cells, headers & footers with static typing. 66 | via [#10](https://github.com/Flinesoft/HandyUIKit/pull/10) by [Murat Yilmaz](https://github.com/roccx) 67 | ### Changed 68 | - Updated code to Swift 4.2 & Xcode 10. 69 | ### Deprecated 70 | - None. 71 | ### Removed 72 | - None. 73 | ### Fixed 74 | - Fixed an issue where the `Info.plist` file could not be found when executing tests. 75 | via [#9](https://github.com/Flinesoft/HandyUIKit/pull/9) by [Andreas Link](https://github.com/LinkAndreas) 76 | - Fixes an issue in `toGrayscale()` method – did not consider image's `scale`. 77 | via [#8](https://github.com/Flinesoft/HandyUIKit/pull/8) by [Andreas Link](https://github.com/LinkAndreas) 78 | ### Security 79 | - None. 80 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at github [at] cihatguenduez [dot] de. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /Frameworks/HandyUIKit/Extensions/StringExt.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Flinesoft. All rights reserved. 2 | 3 | import UIKit 4 | 5 | extension String { 6 | /// Calculates and returns the height needed to fit the text into a width-constrained rect. 7 | /// 8 | /// - Parameters: 9 | /// - fixedWidth: The fixed width of the rect. 10 | /// - font: The font of the text to calculate for. 11 | /// - Returns: The height needed to fit the text into a width-constrained rect. 12 | public func height(forFixedWidth fixedWidth: CGFloat, font: UIFont) -> CGFloat { 13 | let constraintSize = CGSize(width: fixedWidth, height: .greatestFiniteMagnitude) 14 | return ceil(rect(for: constraintSize, font: font).height) 15 | } 16 | 17 | /// Calculates and returns the width needed to fit the text into a height-constrained rect. 18 | /// 19 | /// - Parameters: 20 | /// - fixedHeight: The fixed height of the rect. 21 | /// - font: The font of the text to calculate for. 22 | /// - Returns: The width needed to fit the text into a height-constrained rect. 23 | public func width(forFixedHeight fixedHeight: CGFloat, font: UIFont) -> CGFloat { 24 | let constraintSize = CGSize(width: .greatestFiniteMagnitude, height: fixedHeight) 25 | return ceil(rect(for: constraintSize, font: font).width) 26 | } 27 | 28 | private func rect(for constraintSize: CGSize, font: UIFont) -> CGRect { 29 | let attributes = [NSAttributedString.Key.font: font] 30 | return (self as NSString).boundingRect(with: constraintSize, options: .usesLineFragmentOrigin, attributes: attributes, context: nil) 31 | } 32 | 33 | /// - Returns: A hyphenated NSAttributedString with justified alignment and word wrapping line break mode. 34 | public func hyphenated() -> NSAttributedString { 35 | let paragraphStyle = NSMutableParagraphStyle() 36 | paragraphStyle.hyphenationFactor = 1.0 37 | paragraphStyle.alignment = .justified 38 | paragraphStyle.lineBreakMode = .byWordWrapping 39 | 40 | return NSAttributedString(string: self, attributes: [NSAttributedString.Key.paragraphStyle: paragraphStyle]) 41 | } 42 | 43 | /// Superscripts substrings of structure ^{substring} and subscripts substrings of structure _{substring}. 44 | /// 45 | /// - Parameters: 46 | /// - font: The base font size for the resulting attributed string. 47 | /// 48 | /// - Returns: The resulting attributed string with superscripted and subscripted substrings. 49 | public func superAndSubscripted(font: UIFont) -> NSAttributedString { 50 | NSAttributedString(string: self).superAndSubscripted(font: font) 51 | } 52 | 53 | /// Superscripts substrings of structure ^{substring}. 54 | /// 55 | /// - Parameters: 56 | /// - font: The base font size for the resulting attributed string. 57 | /// 58 | /// - Returns: The resulting attributed string with superscripted substrings. 59 | public func superscripted(font: UIFont) -> NSAttributedString { 60 | NSAttributedString(string: self).superscripted(font: font) 61 | } 62 | 63 | /// Subscripts substrings of structure _{substring}. 64 | /// 65 | /// - Parameters: 66 | /// - font: The base font size for the resulting attributed string. 67 | /// 68 | /// - Returns: The resulting attributed string with subscripted substrings. 69 | public func subscripted(font: UIFont) -> NSAttributedString { 70 | NSAttributedString(string: self).subscripted(font: font) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Tests/HandyUIKitTests/Extensions/ColorExtTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Flinesoft. All rights reserved. 2 | 3 | // swiftlint:disable object_literal 4 | 5 | @testable import HandyUIKit 6 | import XCTest 7 | 8 | class ColorExtTests: XCTestCase { 9 | func testHlca() { 10 | let color = UIColor(hue: 0.1, luminance: 0.2, chroma: 0.3, alpha: 0.4) 11 | 12 | XCTAssertEqual(color.hlca.hue, 0.1, accuracy: 0.001) 13 | XCTAssertEqual(color.hlca.luminance, 0.2, accuracy: 0.001) 14 | XCTAssertEqual(color.hlca.chroma, 0.3, accuracy: 0.001) 15 | XCTAssertEqual(color.hlca.alpha, 0.4, accuracy: 0.001) 16 | } 17 | 18 | func testRgba() { 19 | let color = UIColor(red: 0.1, green: 0.2, blue: 0.3, alpha: 0.4) 20 | 21 | XCTAssertEqual(color.rgba.red, 0.1, accuracy: 0.001) 22 | XCTAssertEqual(color.rgba.green, 0.2, accuracy: 0.001) 23 | XCTAssertEqual(color.rgba.blue, 0.3, accuracy: 0.001) 24 | XCTAssertEqual(color.rgba.alpha, 0.4, accuracy: 0.001) 25 | } 26 | 27 | func testHsba() { 28 | let color = UIColor(hue: 0.1, saturation: 0.2, brightness: 0.3, alpha: 0.4) 29 | 30 | XCTAssertEqual(color.hsba.hue, 0.1, accuracy: 0.001) 31 | XCTAssertEqual(color.hsba.saturation, 0.2, accuracy: 0.001) 32 | XCTAssertEqual(color.hsba.brightness, 0.3, accuracy: 0.001) 33 | XCTAssertEqual(color.hsba.alpha, 0.4, accuracy: 0.001) 34 | } 35 | 36 | func testChangeAttributeBy() { 37 | let rgbaColor = UIColor(red: 0.1, green: 0.2, blue: 0.3, alpha: 0.4) 38 | let changedRgbaColor = rgbaColor.change(.red, by: 0.1).change(.green, by: 0.1).change(.blue, by: 0.1).change(.alpha, by: 0.1) 39 | 40 | XCTAssertEqual(changedRgbaColor.rgba.red, 0.2, accuracy: 0.001) 41 | XCTAssertEqual(changedRgbaColor.rgba.green, 0.3, accuracy: 0.001) 42 | XCTAssertEqual(changedRgbaColor.rgba.blue, 0.4, accuracy: 0.001) 43 | XCTAssertEqual(changedRgbaColor.rgba.alpha, 0.5, accuracy: 0.001) 44 | 45 | let hsbaColor = UIColor(hue: 0.1, saturation: 0.2, brightness: 0.3, alpha: 0.4) 46 | let changedHsbaColor = hsbaColor.change(.hueHSB, by: 0.1).change(.saturation, by: 0.1).change(.brightness, by: 0.1).change(.alpha, by: 0.1) 47 | 48 | XCTAssertEqual(changedHsbaColor.hsba.hue, 0.2, accuracy: 0.001) 49 | XCTAssertEqual(changedHsbaColor.hsba.saturation, 0.3, accuracy: 0.001) 50 | XCTAssertEqual(changedHsbaColor.hsba.brightness, 0.4, accuracy: 0.001) 51 | XCTAssertEqual(changedHsbaColor.hsba.alpha, 0.5, accuracy: 0.001) 52 | } 53 | 54 | func testChangeAttributeTo() { 55 | let rgbaColor = UIColor(red: 0.1, green: 0.2, blue: 0.3, alpha: 0.4) 56 | let changedRgbaColor = rgbaColor.change(.red, to: 1.0).change(.green, to: 0.9).change(.blue, to: 0.8).change(.alpha, to: 0.7) 57 | 58 | XCTAssertEqual(changedRgbaColor.rgba.red, 1.0, accuracy: 0.001) 59 | XCTAssertEqual(changedRgbaColor.rgba.green, 0.9, accuracy: 0.001) 60 | XCTAssertEqual(changedRgbaColor.rgba.blue, 0.8, accuracy: 0.001) 61 | XCTAssertEqual(changedRgbaColor.rgba.alpha, 0.7, accuracy: 0.001) 62 | 63 | let hsbaColor = UIColor(hue: 0.1, saturation: 0.2, brightness: 0.3, alpha: 0.4) 64 | let changedHsbaColor = hsbaColor.change(.hueHSB, to: 1.0).change(.saturation, to: 0.9).change(.brightness, to: 0.8).change(.alpha, to: 0.7) 65 | 66 | XCTAssertEqual(changedHsbaColor.hsba.hue, 0.0, accuracy: 0.001) 67 | XCTAssertEqual(changedHsbaColor.hsba.saturation, 0.9, accuracy: 0.001) 68 | XCTAssertEqual(changedHsbaColor.hsba.brightness, 0.8, accuracy: 0.001) 69 | XCTAssertEqual(changedHsbaColor.hsba.alpha, 0.7, accuracy: 0.001) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /HandyUIKit.xcodeproj/xcshareddata/xcschemes/HandyUIKit-iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 42 | 48 | 49 | 50 | 51 | 52 | 62 | 63 | 69 | 70 | 71 | 72 | 78 | 79 | 85 | 86 | 87 | 88 | 90 | 91 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /HandyUIKit.xcodeproj/xcshareddata/xcschemes/HandyUIKit-tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 42 | 48 | 49 | 50 | 51 | 52 | 62 | 63 | 69 | 70 | 71 | 72 | 78 | 79 | 85 | 86 | 87 | 88 | 90 | 91 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /Tests/HandyUIKitTests/Extensions/CoreGraphicsExtTests.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Flinesoft. All rights reserved. 2 | 3 | @testable import HandyUIKit 4 | import XCTest 5 | 6 | // Note: The tests in here work best if run on a Retina device (e.g. iPhone 6s Plus Simulator). 7 | class CoreGraphicsExtTests: XCTestCase { 8 | let size: CGFloat = 22 9 | 10 | func testCGSizeInPixels() { 11 | let testSize = CGSize(width: size, height: size) 12 | 13 | let expectedPixelSize = size * UIScreen.main.scale 14 | let testSizeInPixels = testSize.inPixels 15 | 16 | XCTAssertEqual(testSizeInPixels.width, expectedPixelSize, accuracy: 0.001) 17 | XCTAssertEqual(testSizeInPixels.height, expectedPixelSize, accuracy: 0.001) 18 | } 19 | 20 | func testCGSizeInPixelsScreen() { 21 | let testSize = CGSize(width: size, height: size) 22 | 23 | let testScreen = UIScreen() 24 | let expectedPixelSize = size * testScreen.scale 25 | 26 | let testSizeInPixels = testSize.inPixels(testScreen) 27 | 28 | XCTAssertEqual(testSizeInPixels.width, expectedPixelSize, accuracy: 0.001) 29 | XCTAssertEqual(testSizeInPixels.height, expectedPixelSize, accuracy: 0.001) 30 | } 31 | 32 | func testCGPointInPixels() { 33 | let testPoint = CGPoint(x: size, y: size) 34 | 35 | let expectedPixelPointSizes = size * UIScreen.main.scale 36 | let testPointInPixels = testPoint.inPixels 37 | 38 | XCTAssertEqual(testPointInPixels.x, expectedPixelPointSizes, accuracy: 0.001) 39 | XCTAssertEqual(testPointInPixels.y, expectedPixelPointSizes, accuracy: 0.001) 40 | } 41 | 42 | func testCGPointInPixelsScreen() { 43 | let testPoint = CGPoint(x: size, y: size) 44 | 45 | let testScreen = UIScreen() 46 | let expectedPixelPointSizes = size * testScreen.scale 47 | 48 | let testPointInPixels = testPoint.inPixels(testScreen) 49 | 50 | XCTAssertEqual(testPointInPixels.x, expectedPixelPointSizes, accuracy: 0.001) 51 | XCTAssertEqual(testPointInPixels.y, expectedPixelPointSizes, accuracy: 0.001) 52 | } 53 | 54 | func testCGRectInPixels() { 55 | let testRect = CGRect(x: size, y: size, width: size, height: size) 56 | 57 | let expectedPixelRectSizes = size * UIScreen.main.scale 58 | let testRectInPixels = testRect.inPixels 59 | 60 | XCTAssertEqual(testRectInPixels.origin.x, expectedPixelRectSizes, accuracy: 0.001) 61 | XCTAssertEqual(testRectInPixels.origin.y, expectedPixelRectSizes, accuracy: 0.001) 62 | XCTAssertEqual(testRectInPixels.size.width, expectedPixelRectSizes, accuracy: 0.001) 63 | XCTAssertEqual(testRectInPixels.size.height, expectedPixelRectSizes, accuracy: 0.001) 64 | } 65 | 66 | func testCGRectInPixelsScreen() { 67 | let testRect = CGRect(x: size, y: size, width: size, height: size) 68 | 69 | let testScreen = UIScreen() 70 | let expectedPixelRectSizes = size * testScreen.scale 71 | 72 | let testRectInPixels = testRect.inPixels(testScreen) 73 | 74 | XCTAssertEqual(testRectInPixels.origin.x, expectedPixelRectSizes, accuracy: 0.001) 75 | XCTAssertEqual(testRectInPixels.origin.y, expectedPixelRectSizes, accuracy: 0.001) 76 | XCTAssertEqual(testRectInPixels.size.width, expectedPixelRectSizes, accuracy: 0.001) 77 | XCTAssertEqual(testRectInPixels.size.height, expectedPixelRectSizes, accuracy: 0.001) 78 | } 79 | 80 | func testCGRectInitSize() { 81 | let testSize = CGSize(width: size, height: size) 82 | let testRect = CGRect(size: testSize) 83 | 84 | XCTAssertEqual(testRect.origin.x, 0.0, accuracy: 0.001) 85 | XCTAssertEqual(testRect.origin.y, 0.0, accuracy: 0.001) 86 | XCTAssertEqual(testRect.size.width, testSize.width, accuracy: 0.001) 87 | XCTAssertEqual(testRect.size.height, testSize.height, accuracy: 0.001) 88 | } 89 | 90 | func testCGRectInitWidthHeight() { 91 | let testRect = CGRect(width: size, height: size) 92 | 93 | XCTAssertEqual(testRect.origin.x, 0.0, accuracy: 0.001) 94 | XCTAssertEqual(testRect.origin.y, 0.0, accuracy: 0.001) 95 | XCTAssertEqual(testRect.size.width, size, accuracy: 0.001) 96 | XCTAssertEqual(testRect.size.height, size, accuracy: 0.001) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Frameworks/HandyUIKit/Extensions/UIViewExt.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Flinesoft. All rights reserved. 2 | 3 | import UIKit 4 | 5 | extension UIView { 6 | /// Finds all superviews in the view hierarchy. 7 | /// 8 | /// - Returns: A list of all superview starting with the superview of this view if any. 9 | public var superviews: [UIView] { 10 | guard let superview = superview else { return [] } 11 | 12 | var superviews: [UIView] = [superview] 13 | while let nextLevelSuperview = superviews.last!.superview { 14 | superviews.append(nextLevelSuperview) 15 | } 16 | 17 | return superviews 18 | } 19 | 20 | /// Finds the firstResponder in this view hierarchy by traversing its subviews recursively. 21 | /// 22 | /// NOTE: Uses DFS (depth first search). 23 | /// 24 | /// - Returns: The firstResponder view in the subview hierarchy. 25 | public var firstResponder: UIView? { 26 | guard !isFirstResponder else { return self } 27 | 28 | for subview in subviews { 29 | guard let firstResponder = subview.firstResponder else { continue } 30 | return firstResponder 31 | } 32 | 33 | return nil 34 | } 35 | 36 | #if IOS 37 | 38 | /// Animates view changes alongside keyboard animation using the same duration and animation curve. 39 | /// 40 | /// - Parameters: 41 | /// - notification: The `Notification` object received from the NotificationCenter alongside the `.keyboardWillShow` notification. 42 | /// - animations: The changes to animate. 43 | /// - completion: Any work to be done after animations has completed. 44 | static func animateAlongsideKeyboard(_ notification: Notification, animations: @escaping () -> Void, completion: ((Bool) -> Void)? = nil) { 45 | guard 46 | let userInfo = notification.userInfo, 47 | let durationNumber = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber, 48 | let curveNumber = userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber, 49 | let curve = UIView.AnimationCurve(rawValue: curveNumber.intValue) 50 | else { 51 | animations() 52 | return 53 | } 54 | 55 | UIView.animate( 56 | withDuration: durationNumber.doubleValue, 57 | delay: 0, 58 | options: curve.toOptions(), 59 | animations: animations, 60 | completion: completion 61 | ) 62 | } 63 | 64 | #endif 65 | 66 | /// Renders the current content of the view using its bounds to an image. 67 | /// 68 | /// - Parameters: 69 | /// - rect: The rect to draw into the image or `nil` to use views bounds. Defaults to `nil`. 70 | /// - Returns: The rendered image. 71 | public func toImage(size: CGSize? = nil) -> UIImage { 72 | let rect = size != nil ? CGRect(size: size!) : bounds 73 | UIGraphicsBeginImageContextWithOptions(rect.size, isOpaque, UIScreen.main.scale) 74 | defer { UIGraphicsEndImageContext() } 75 | 76 | drawHierarchy(in: rect, afterScreenUpdates: true) 77 | return UIGraphicsGetImageFromCurrentImageContext()! 78 | } 79 | 80 | /// Adds constraints to the superview so that this view has same size and position. 81 | /// Note: This fails the build if the `superview` is `nil` – add it as a subview before calling this. 82 | /// 83 | /// - Parameters: 84 | /// - edgeInsets: Insets from the edge. Defaults to 0 on all edges. 85 | public func bindEdgesToSuperview(insets: UIEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)) { 86 | guard let superview = superview else { 87 | preconditionFailure("`superview` was nil – call `addSubview(view: UIView)` before calling `bindEdgesToSuperview()` to fix this.") 88 | } 89 | 90 | translatesAutoresizingMaskIntoConstraints = false 91 | ["H:|-(\(insets.left))-[subview]-(\(insets.right))-|", "V:|-(\(insets.top))-[subview]-(\(insets.bottom))-|"].forEach { visualFormat in 92 | superview.addConstraints( 93 | NSLayoutConstraint.constraints( 94 | withVisualFormat: visualFormat, 95 | options: .directionLeadingToTrailing, 96 | metrics: nil, 97 | views: ["subview": self] 98 | ) 99 | ) 100 | } 101 | } 102 | 103 | /// Finds the first superview of all superviews matching a predicate. 104 | /// 105 | /// - Parameters: 106 | /// - predicate: The predicate to match superviews against. 107 | /// - Returns: The first matching superview or `nil`. 108 | public func firstSuperview(where predicate: (UIView) -> Bool) -> UIView? { 109 | var nextSuperview: UIView? = superview 110 | 111 | while nextSuperview != nil { 112 | guard !predicate(nextSuperview!) else { return nextSuperview } 113 | nextSuperview = nextSuperview?.superview 114 | } 115 | 116 | return nil 117 | } 118 | 119 | /// Finds the subview in the view hierarchy matching the given predicate. 120 | /// 121 | /// NOTE: Uses DFS (depth first search). 122 | /// 123 | /// - Returns: The first subview in the viewhierarchy matching the given predicate or `nil` if not found. 124 | public func firstSubviewInHierarchy(matching predicate: (UIView) -> Bool) -> UIView? { 125 | subviews.first(where: predicate) ?? subviews.first { $0.firstSubviewInHierarchy(matching: predicate) != nil } 126 | } 127 | } 128 | 129 | extension UIView.AnimationCurve { 130 | fileprivate func toOptions() -> UIView.AnimationOptions { 131 | UIView.AnimationOptions(rawValue: UInt(rawValue << 16)) 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /UsageExamples.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import HandyUIKit 3 | import PlaygroundSupport 4 | 5 | //: ## UIColorExtension 6 | //: ### init(hue:luminance:chroma:) 7 | //: Initializes a UIColor with given HLC (LCh) colors normed to ranges from 0 to 1. 8 | 9 | let hlcaColor = UIColor(hue: 180/360, luminance: 30/100, chroma: 125/128, alpha: 1) 10 | 11 | //: ### .hlca 12 | //: Returns a tuple with named HLCA parameters for easy access. 13 | 14 | hlcaColor.hlca.hue 15 | hlcaColor.hlca.luminance 16 | hlcaColor.hlca.chroma 17 | hlcaColor.hlca.alpha 18 | 19 | //: ### .rgba 20 | //: Returns a tuple with named RGBA parameters for easy access. 21 | 22 | let rgbaColor = UIColor(red: 0.1, green: 0.2, blue: 0.3, alpha: 0.4) 23 | rgbaColor.rgba.red 24 | rgbaColor.rgba.green 25 | rgbaColor.rgba.blue 26 | rgbaColor.rgba.alpha 27 | 28 | //: ### .hsba 29 | //: Returns a tuple with named HSBA parameters for easy access. 30 | 31 | let hsbaColor = UIColor(hue: 0.1, saturation: 0.2, brightness: 0.3, alpha: 0.4) 32 | hsbaColor.hsba.hue 33 | hsbaColor.hsba.saturation 34 | hsbaColor.hsba.brightness 35 | hsbaColor.hsba.alpha 36 | 37 | //: ### .change(ChangeableAttribute, by:) 38 | //: Creates a new `UIColor` object with a single attribute changed by a given difference using addition. 39 | 40 | hlcaColor.hlca.luminance 41 | let newHlcaColor = hlcaColor.change(.luminance, by: 0.5) 42 | newHlcaColor.hlca.luminance 43 | 44 | rgbaColor.rgba.blue 45 | let newRgbaColor = rgbaColor.change(.blue, by: 0.2) 46 | newRgbaColor.rgba.blue 47 | 48 | //: ### .change(ChangeableAttribute, to:) 49 | //: Creates a new `UIColor` object with the value of a single attribute set to a given value. 50 | 51 | hsbaColor.hsba.brightness 52 | let newHsbaColor = hsbaColor.change(.brightness, to: 0.8) 53 | newHsbaColor.hsba.brightness 54 | 55 | 56 | //: ## UIViewExtension 57 | //: ### .toImage(size:) 58 | //: Takes a screenshot of the UIView's content optionally resizing the result to a given size. 59 | 60 | let view = UIView(frame: CGRect(width: 500, height: 500)) 61 | let subview = UIView(frame: CGRect(width: 200, height: 200)) 62 | subview.center = view.center 63 | view.addSubview(subview) 64 | 65 | view.backgroundColor = .blue 66 | subview.backgroundColor = .red 67 | 68 | let fullSizeContent = view.toImage() 69 | let downSizedContent = view.toImage(size: CGSize(width: 50, height: 50)) 70 | 71 | //: ### .bindEdgesToSuperview() 72 | //: Adds constraints to the subview so it always has the same size and position as the superview. 73 | 74 | view.frame 75 | subview.frame 76 | subview.bindEdgesToSuperview() 77 | view.layoutIfNeeded() 78 | subview.frame 79 | 80 | 81 | //: ## CoreGraphicsExtensions 82 | //: ### CGSize.inPixels / CGSize.inPixels(screen:) 83 | //: Returns a new CGSize object with the width and height converted to true pixels on screen. 84 | 85 | let size = CGSize(width: 100, height: 50) 86 | size.inPixels // test this with a Retina screen target 87 | size.inPixels(UIScreen.screens.last!) // pass a different screen 88 | 89 | //: ### CGPoint.inPixels / CGPoint.inPixels(screen:) 90 | //: Returns a new CGPoint object with the x and y converted to true pixels on screen. 91 | 92 | let point = CGPoint(x: 100, y: 50) 93 | point.inPixels // test this with a Retina screen target 94 | point.inPixels(UIScreen.screens.last!) // pass a different screen 95 | 96 | //: ### CGRect.inPixels / CGRect.inPixels(screen:) 97 | //: Returns a new CGRect object with the origin and size converted to true pixels on screen. 98 | 99 | let rect = CGRect(x: 10, y: 20, width: 100, height: 50) 100 | rect.inPixels // test this with a Retina screen target 101 | rect.inPixels(UIScreen.screens.last!) // pass a different screen 102 | 103 | //: ### CGRect.init(size:) / CGRect.init(width:height:) 104 | //: Creates a new CGRect object from origin zero with given size. 105 | 106 | let someSize = CGSize(width: 100, height: 50) 107 | 108 | let originZeroRect1 = CGRect(size: someSize) 109 | let originZeroRect2 = CGRect(width: 100, height: 50) 110 | 111 | 112 | //: ## StringExtension 113 | //: ### .width(for fixedHeight:font:) 114 | //: Calculates and returns the height needed to fit the text into a width-constrained rect. 115 | 116 | let loremIpsum = "Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." 117 | 118 | loremIpsum.height(forFixedWidth: 300, font: UIFont.systemFont(ofSize: 14, weight: .bold)) 119 | 120 | //: ### .height(for fixedWidth:font:) 121 | //: Calculates and returns the width needed to fit the text into a height-constrained rect. 122 | 123 | loremIpsum.width(forFixedHeight: 21, font: UIFont.systemFont(ofSize: 12, weight: .ultraLight)) 124 | 125 | //: ### .hyphenated() 126 | //: A hyphenated NSAttributedString with justified alignment and word wrapping line break mode. 127 | 128 | loremIpsum.hyphenated() 129 | 130 | //: ### .superscripted(font:) / .subscripted(font:) / .superAndSubscripted(font:) 131 | //: Superscript and/or subscript part of your strings with the structures `^{superscripted text}` and `_{subscripted text}`. 132 | 133 | "x^{2}".superscripted(font: UIFont.systemFont(ofSize: 20, weight: .medium)) 134 | 135 | "CO_{2}".subscripted(font: UIFont.systemFont(ofSize: 20, weight: .medium)) 136 | 137 | "_{20}Ca^{1,0}".superAndSubscripted(font: UIFont.systemFont(ofSize: 20, weight: .regular)) 138 | 139 | 140 | //: ## UIImageExtension 141 | //: ### .toGrayscale() 142 | //: Creates a grayscale version of the image. 143 | 144 | let image = UIImage(named: "matt-lamers-261639-unsplash.jpg")! 145 | let grayscaleImage = image.toGrayscale() 146 | 147 | -------------------------------------------------------------------------------- /Frameworks/HandyUIKit/Extensions/NSAttributedStringExt.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Flinesoft. All rights reserved. 2 | 3 | import UIKit 4 | 5 | private let scriptedTextSizeRatio: CGFloat = 0.618 6 | 7 | extension NSAttributedString { 8 | /// Calculates and returns the height needed to fit the text into a width-constrained rect. 9 | /// 10 | /// - Parameters: 11 | /// - fixedWidth: The fixed width of the rect. 12 | /// - font: The font of the text to calculate for. 13 | /// - Returns: The height needed to fit the text into a width-constrained rect. 14 | public func height(forFixedWidth fixedWidth: CGFloat, font: UIFont) -> CGFloat { 15 | let constraintSize = CGSize(width: fixedWidth, height: .greatestFiniteMagnitude) 16 | return rect(for: constraintSize, font: font).height 17 | } 18 | 19 | /// Calculates and returns the width needed to fit the text into a height-constrained rect. 20 | /// 21 | /// - Parameters: 22 | /// - fixedHeight: The fixed height of the rect. 23 | /// - font: The font of the text to calculate for. 24 | /// - Returns: The width needed to fit the text into a height-constrained rect. 25 | public func width(forFixedHeight fixedHeight: CGFloat, font: UIFont) -> CGFloat { 26 | let constraintSize = CGSize(width: .greatestFiniteMagnitude, height: fixedHeight) 27 | return rect(for: constraintSize, font: font).width 28 | } 29 | 30 | private func rect(for constraintSize: CGSize, font: UIFont) -> CGRect { 31 | let copy = mutableCopy() as! NSMutableAttributedString // swiftlint:disable:this force_cast 32 | copy.addAttribute(NSAttributedString.Key.font, value: font, range: NSRange(location: 0, length: length)) 33 | 34 | return copy.boundingRect(with: constraintSize, options: .usesLineFragmentOrigin, context: nil) 35 | } 36 | 37 | /// Superscripts substrings of structure ^{substring} and subscripts substrings of structure _{substring}. 38 | /// 39 | /// - Parameters: 40 | /// - font: The base font size for the resulting attributed string. 41 | /// - applyFont: Specify if the font shall be applied to the resulting attributed string. Defaults to `true`. 42 | /// 43 | /// - Returns: The resulting attributed string with superscripted and subscripted substrings. 44 | public func superAndSubscripted(font: UIFont, applyFont: Bool = true) -> NSAttributedString { 45 | subscripted(font: font).superscripted(font: font, applyFont: false) 46 | } 47 | 48 | /// Superscripts substrings of structure ^{substring}. 49 | /// 50 | /// - Parameters: 51 | /// - font: The base font size for the resulting attributed string. 52 | /// - applyFont: Specify if the font shall be applied to the resulting attributed string. Defaults to `true`. 53 | /// 54 | /// - Returns: The resulting attributed string with superscripted substrings. 55 | public func superscripted(font: UIFont, applyFont: Bool = true) -> NSAttributedString { 56 | scripted( 57 | font: font, 58 | regex: try! NSRegularExpression(pattern: "\\^\\{([^\\}]*)\\}"), // swiftlint:disable:this force_try 59 | captureBaselineOffset: font.pointSize * (1.0 - scriptedTextSizeRatio), 60 | applyFont: applyFont 61 | ) 62 | } 63 | 64 | /// Subscripts substrings of structure _{substring}. 65 | /// 66 | /// - Parameters: 67 | /// - font: The base font size for the resulting attributed string. 68 | /// - applyFont: Specify if the font shall be applied to the resulting attributed string. Defaults to `true`. 69 | /// 70 | /// - Returns: The resulting attributed string with subscripted substrings. 71 | public func subscripted(font: UIFont, applyFont: Bool = true) -> NSAttributedString { 72 | scripted( 73 | font: font, 74 | regex: try! NSRegularExpression(pattern: "\\_\\{([^\\}]*)\\}"), // swiftlint:disable:this force_try 75 | captureBaselineOffset: font.pointSize * -(scriptedTextSizeRatio / 5), 76 | applyFont: applyFont 77 | ) 78 | } 79 | 80 | // swiftlint:disable force_cast 81 | private func scripted(font: UIFont, regex: NSRegularExpression, captureBaselineOffset: CGFloat, applyFont: Bool = true) -> NSAttributedString { 82 | // apply font to entire string 83 | let unprocessedString = self.mutableCopy() as! NSMutableAttributedString 84 | 85 | if applyFont { 86 | unprocessedString.addAttribute(.font, value: font, range: NSRange(location: 0, length: unprocessedString.length)) 87 | } 88 | 89 | // start reading in the string part by part 90 | let attributedString = NSMutableAttributedString() 91 | 92 | while let match = regex.firstMatch( 93 | in: unprocessedString.string, 94 | options: .reportCompletion, 95 | range: NSRange(location: 0, length: unprocessedString.length) 96 | ) { 97 | // add substring before match 98 | let substringBeforeMatch = unprocessedString.attributedSubstring(from: NSRange(location: 0, length: match.range.location)) 99 | attributedString.append(substringBeforeMatch) 100 | 101 | // add match with subscripted style 102 | let capturedSubstring = unprocessedString.attributedSubstring(from: match.range(at: 1)).mutableCopy() as! NSMutableAttributedString 103 | let captureFullRange = NSRange(location: 0, length: capturedSubstring.length) 104 | capturedSubstring.addAttribute(.font, value: font.withSize(font.pointSize * scriptedTextSizeRatio), range: captureFullRange) 105 | capturedSubstring.addAttribute(.baselineOffset, value: captureBaselineOffset, range: captureFullRange) 106 | attributedString.append(capturedSubstring) 107 | 108 | // strip off the processed part 109 | unprocessedString.deleteCharacters(in: NSRange(location: 0, length: match.range.location + match.range.length)) 110 | } 111 | 112 | // add substring after last match 113 | attributedString.append(unprocessedString) 114 | 115 | return attributedString.copy() as! NSAttributedString 116 | } // swiftlint:enable force_cast 117 | } 118 | -------------------------------------------------------------------------------- /Frameworks/HandyUIKit/ColorSpaces.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2015 Flinesoft. All rights reserved. 2 | // 3 | // Original source: https://github.com/timrwood/ColorSpaces 4 | 5 | // swiftlint:disable all 6 | 7 | import UIKit 8 | 9 | private struct ColorSpaces {} 10 | 11 | // MARK: - Constants 12 | private let RAD_TO_DEG = 180 / CGFloat.pi 13 | 14 | private let LAB_E: CGFloat = 0.008856 15 | private let LAB_16_116: CGFloat = 0.1379310 16 | private let LAB_K_116: CGFloat = 7.787036 17 | private let LAB_X: CGFloat = 0.95047 18 | private let LAB_Y: CGFloat = 1 19 | private let LAB_Z: CGFloat = 1.088_83 20 | 21 | // MARK: - RGB 22 | struct RGBColor { 23 | let r: CGFloat // 0..1 24 | let g: CGFloat // 0..1 25 | let b: CGFloat // 0..1 26 | let alpha: CGFloat // 0..1 27 | 28 | init (r: CGFloat, g: CGFloat, b: CGFloat, alpha: CGFloat) { 29 | self.r = r 30 | self.g = g 31 | self.b = b 32 | self.alpha = alpha 33 | } 34 | 35 | fileprivate func sRGBCompand(_ v: CGFloat) -> CGFloat { 36 | let absV = abs(v) 37 | let out = absV > 0.040_45 ? pow((absV + 0.055) / 1.055, 2.4) : absV / 12.92 38 | return v > 0 ? out : -out 39 | } 40 | 41 | func toXYZ() -> XYZColor { 42 | let R = sRGBCompand(r) 43 | let G = sRGBCompand(g) 44 | let B = sRGBCompand(b) 45 | let x: CGFloat = (R * 0.412_456_4) + (G * 0.357_576_1) + (B * 0.180_437_5) 46 | let y: CGFloat = (R * 0.212_672_9) + (G * 0.715_152_2) + (B * 0.072_175_0) 47 | let z: CGFloat = (R * 0.019_333_9) + (G * 0.119_192_0) + (B * 0.950_304_1) 48 | return XYZColor(x: x, y: y, z: z, alpha: alpha) 49 | } 50 | 51 | func toLAB() -> LABColor { 52 | return toXYZ().toLAB() 53 | } 54 | 55 | func toLCH() -> LCHColor { 56 | return toXYZ().toLCH() 57 | } 58 | 59 | func color() -> UIColor { 60 | return UIColor(red: r, green: g, blue: b, alpha: alpha) 61 | } 62 | 63 | func lerp(_ other: RGBColor, t: CGFloat) -> RGBColor { 64 | return RGBColor( 65 | r: r + (other.r - r) * t, 66 | g: g + (other.g - g) * t, 67 | b: b + (other.b - b) * t, 68 | alpha: alpha + (other.alpha - alpha) * t 69 | ) 70 | } 71 | } 72 | 73 | extension UIColor { 74 | func rgbColor() -> RGBColor { 75 | return RGBColor(r: rgba.red, g: rgba.green, b: rgba.blue, alpha: rgba.alpha) 76 | } 77 | } 78 | 79 | // MARK: - XYZ 80 | 81 | struct XYZColor { 82 | let x: CGFloat // 0..0.95047 83 | let y: CGFloat // 0..1 84 | let z: CGFloat // 0..1.08883 85 | let alpha: CGFloat // 0..1 86 | 87 | init (x: CGFloat, y: CGFloat, z: CGFloat, alpha: CGFloat) { 88 | self.x = x 89 | self.y = y 90 | self.z = z 91 | self.alpha = alpha 92 | } 93 | 94 | fileprivate func sRGBCompand(_ v: CGFloat) -> CGFloat { 95 | let absV = abs(v) 96 | let out = absV > 0.003_130_8 ? 1.055 * pow(absV, 1 / 2.4) - 0.055 : absV * 12.92 97 | return v > 0 ? out : -out 98 | } 99 | 100 | func toRGB() -> RGBColor { 101 | let r = (x * 3.240_454_2) + (y * -1.537_138_5) + (z * -0.498_531_4) 102 | let g = (x * -0.969_266_0) + (y * 1.876_010_8) + (z * 0.041_556_0) 103 | let b = (x * 0.055_643_4) + (y * -0.204_025_9) + (z * 1.057_225_2) 104 | let R = sRGBCompand(r) 105 | let G = sRGBCompand(g) 106 | let B = sRGBCompand(b) 107 | return RGBColor(r: R, g: G, b: B, alpha: alpha) 108 | } 109 | 110 | fileprivate func labCompand(_ v: CGFloat) -> CGFloat { 111 | return v > LAB_E ? pow(v, 1.0 / 3.0) : (LAB_K_116 * v) + LAB_16_116 112 | } 113 | 114 | func toLAB() -> LABColor { 115 | let fx = labCompand(x / LAB_X) 116 | let fy = labCompand(y / LAB_Y) 117 | let fz = labCompand(z / LAB_Z) 118 | return LABColor( 119 | l: 116 * fy - 16, 120 | a: 500 * (fx - fy), 121 | b: 200 * (fy - fz), 122 | alpha: alpha 123 | ) 124 | } 125 | 126 | func toLCH() -> LCHColor { 127 | return toLAB().toLCH() 128 | } 129 | 130 | func lerp(_ other: XYZColor, t: CGFloat) -> XYZColor { 131 | return XYZColor( 132 | x: x + (other.x - x) * t, 133 | y: y + (other.y - y) * t, 134 | z: z + (other.z - z) * t, 135 | alpha: alpha + (other.alpha - alpha) * t 136 | ) 137 | } 138 | } 139 | 140 | // MARK: - LAB 141 | 142 | struct LABColor { 143 | let l: CGFloat // 0..100 144 | let a: CGFloat // -128..128 145 | let b: CGFloat // -128..128 146 | let alpha: CGFloat // 0..1 147 | 148 | init (l: CGFloat, a: CGFloat, b: CGFloat, alpha: CGFloat) { 149 | self.l = l 150 | self.a = a 151 | self.b = b 152 | self.alpha = alpha 153 | } 154 | 155 | fileprivate func xyzCompand(_ v: CGFloat) -> CGFloat { 156 | let v3 = v * v * v 157 | return v3 > LAB_E ? v3 : (v - LAB_16_116) / LAB_K_116 158 | } 159 | 160 | func toXYZ() -> XYZColor { 161 | let y = (l + 16) / 116 162 | let x = y + (a / 500) 163 | let z = y - (b / 200) 164 | return XYZColor( 165 | x: xyzCompand(x) * LAB_X, 166 | y: xyzCompand(y) * LAB_Y, 167 | z: xyzCompand(z) * LAB_Z, 168 | alpha: alpha 169 | ) 170 | } 171 | 172 | func toLCH() -> LCHColor { 173 | let c = sqrt(a * a + b * b) 174 | let angle = atan2(b, a) * RAD_TO_DEG 175 | let h = angle < 0 ? angle + 360 : angle 176 | return LCHColor(l: l, c: c, h: h, alpha: alpha) 177 | } 178 | 179 | func toRGB() -> RGBColor { 180 | return toXYZ().toRGB() 181 | } 182 | 183 | func lerp(_ other: LABColor, t: CGFloat) -> LABColor { 184 | return LABColor( 185 | l: l + (other.l - l) * t, 186 | a: a + (other.a - a) * t, 187 | b: b + (other.b - b) * t, 188 | alpha: alpha + (other.alpha - alpha) * t 189 | ) 190 | } 191 | } 192 | 193 | // MARK: - LCH 194 | 195 | struct LCHColor { 196 | let l: CGFloat // 0..100 197 | let c: CGFloat // 0..128 198 | let h: CGFloat // 0..360 199 | let alpha: CGFloat // 0..1 200 | 201 | init (l: CGFloat, c: CGFloat, h: CGFloat, alpha: CGFloat) { 202 | self.l = l 203 | self.c = c 204 | self.h = h 205 | self.alpha = alpha 206 | } 207 | 208 | func toLAB() -> LABColor { 209 | let rad = h / RAD_TO_DEG 210 | let a = cos(rad) * c 211 | let b = sin(rad) * c 212 | return LABColor(l: l, a: a, b: b, alpha: alpha) 213 | } 214 | 215 | func toXYZ() -> XYZColor { 216 | return toLAB().toXYZ() 217 | } 218 | 219 | func toRGB() -> RGBColor { 220 | return toXYZ().toRGB() 221 | } 222 | 223 | func lerp(_ other: LCHColor, t: CGFloat) -> LCHColor { 224 | let angle = (((((other.h - h).truncatingRemainder(dividingBy: 360)) + 540).truncatingRemainder(dividingBy: 360)) - 180) * t 225 | return LCHColor( 226 | l: l + (other.l - l) * t, 227 | c: c + (other.c - c) * t, 228 | h: (h + angle + 360).truncatingRemainder(dividingBy: 360), 229 | alpha: alpha + (other.alpha - alpha) * t 230 | ) 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /Frameworks/HandyUIKit/Extensions/UIColorExt.swift: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Flinesoft. All rights reserved. 2 | 3 | import UIKit 4 | 5 | extension UIColor { 6 | /// A list of changeable attributes of the UIColor. 7 | /// 8 | /// - Red: 9 | /// - Green: 10 | /// - Blue: 11 | /// - Alpha: 12 | /// - Hue: 13 | /// - Saturation: 14 | /// - Brightness: 15 | /// 16 | public enum ChangeableAttribute { 17 | /// The red color part of RGB. 18 | case red 19 | /// The green color part of RGB. 20 | case green 21 | /// The blue color part of RGB. 22 | case blue 23 | /// The hue color part of HSB. 24 | case hueHSB 25 | /// The saturation color part of HSB. 26 | case saturation 27 | /// The brightness color part of HSB. 28 | case brightness 29 | /// The hue color part of HLC. 30 | case hueHLC 31 | /// The luminance color part of HLC. 32 | case luminance 33 | /// The chroma color part of HLC. 34 | case chroma 35 | /// The alpha color part of RGB / HSB / HLC. 36 | case alpha 37 | } 38 | 39 | // MARK: - Computed Properties 40 | /// The HLC & alpha attributes of the `UIColor` instance. 41 | public var hlca: (hue: CGFloat, luminance: CGFloat, chroma: CGFloat, alpha: CGFloat) { // swiftlint:disable:this large_tuple 42 | let lch = rgbColor().toLCH() 43 | return (hue: lch.h / 360, luminance: lch.l / 100, chroma: lch.c / 128, alpha: lch.alpha) 44 | } 45 | 46 | /// The HSB & alpha attributes of the `UIColor` instance. 47 | public var hsba: (hue: CGFloat, saturation: CGFloat, brightness: CGFloat, alpha: CGFloat) { // swiftlint:disable:this large_tuple 48 | var hue: CGFloat = 0, saturation: CGFloat = 0, brightness: CGFloat = 0, alpha: CGFloat = 0 49 | getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha) 50 | 51 | return (hue: hue, saturation: saturation, brightness: brightness, alpha: alpha) 52 | } 53 | 54 | /// The RGB & alpha attributes of the `UIColor` instance. 55 | public var rgba: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) { // swiftlint:disable:this large_tuple 56 | var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0 57 | getRed(&red, green: &green, blue: &blue, alpha: &alpha) 58 | 59 | return (red: red, green: green, blue: blue, alpha: alpha) 60 | } 61 | 62 | /// Initializes and returns a color with the given HLCA values. 63 | /// 64 | /// - Parameters: 65 | /// - hue: The hue. A value between 0 and 1. 66 | /// - luminance: The luminance. A value between 0 and 1. 67 | /// - chroma: The chroma. A value between 0 and 1. 68 | /// - alpha: The alpha. A value between 0 and 1. 69 | public convenience init(hue: CGFloat, luminance: CGFloat, chroma: CGFloat, alpha: CGFloat) { 70 | let rgb = LCHColor(l: luminance * 100, c: chroma * 128, h: hue * 360, alpha: alpha).toRGB() 71 | self.init(red: rgb.r, green: rgb.g, blue: rgb.b, alpha: rgb.alpha) 72 | } 73 | 74 | // MARK: - Methods 75 | /// Creates a new `UIColor` object with a single attribute changed by a given difference using addition. 76 | /// 77 | /// - Parameters: 78 | /// - attribute: The attribute to change. 79 | /// - by: The addition to be added to the current value of the attribute. 80 | /// - Returns: The resulting new `UIColor` with the specified change applied. 81 | public func change(_ attribute: ChangeableAttribute, by addition: CGFloat) -> UIColor { 82 | switch attribute { 83 | case .red: 84 | return change(attribute, to: rgba.red + addition) 85 | 86 | case .green: 87 | return change(attribute, to: rgba.green + addition) 88 | 89 | case .blue: 90 | return change(attribute, to: rgba.blue + addition) 91 | 92 | case .hueHSB: 93 | return change(attribute, to: hsba.hue + addition) 94 | 95 | case .saturation: 96 | return change(attribute, to: hsba.saturation + addition) 97 | 98 | case .brightness: 99 | return change(attribute, to: hsba.brightness + addition) 100 | 101 | case .hueHLC: 102 | return change(attribute, to: hlca.hue + addition) 103 | 104 | case .luminance: 105 | return change(attribute, to: hlca.luminance + addition) 106 | 107 | case .chroma: 108 | return change(attribute, to: hlca.chroma + addition) 109 | 110 | case .alpha: 111 | return change(attribute, to: hlca.alpha + addition) 112 | } 113 | } 114 | 115 | /// Creates a new `UIColor` object with the value of a single attribute set to a given value. 116 | /// 117 | /// - Parameters: 118 | /// - attribute: The attribute to change. 119 | /// - to: The new value to be set for the attribute. 120 | /// - Returns: The resulting new `UIColor` with the specified change applied. 121 | public func change(_ attribute: ChangeableAttribute, to newValue: CGFloat) -> UIColor { 122 | switch attribute { 123 | case .red, .green, .blue: 124 | return newRgbaColor(attribute, newValue) 125 | 126 | case .hueHSB, .saturation, .brightness: 127 | return newHsbaColor(attribute, newValue) 128 | 129 | case .hueHLC, .luminance, .chroma, .alpha: 130 | return newHlcaColor(attribute, newValue) 131 | } 132 | } 133 | 134 | private func newHlcaColor(_ attribute: UIColor.ChangeableAttribute, _ newValue: CGFloat) -> UIColor { 135 | var newHlca = hlca 136 | 137 | switch attribute { 138 | case .hueHLC: 139 | newHlca.hue = newValue 140 | 141 | case .luminance: 142 | newHlca.luminance = newValue 143 | 144 | case .chroma: 145 | newHlca.chroma = newValue 146 | 147 | case .alpha: 148 | newHlca.alpha = newValue 149 | 150 | default: 151 | break 152 | } 153 | 154 | return UIColor(hue: newHlca.hue, luminance: newHlca.luminance, chroma: newHlca.chroma, alpha: newHlca.alpha) 155 | } 156 | 157 | private func newHsbaColor(_ attribute: UIColor.ChangeableAttribute, _ newValue: CGFloat) -> UIColor { 158 | var newHsba = hsba 159 | 160 | switch attribute { 161 | case .hueHSB: 162 | newHsba.hue = newValue 163 | 164 | case .saturation: 165 | newHsba.saturation = newValue 166 | 167 | case .brightness: 168 | newHsba.brightness = newValue 169 | 170 | default: 171 | break 172 | } 173 | 174 | return UIColor(hue: newHsba.hue, saturation: newHsba.saturation, brightness: newHsba.brightness, alpha: newHsba.alpha) 175 | } 176 | 177 | private func newRgbaColor(_ attribute: UIColor.ChangeableAttribute, _ newValue: CGFloat) -> UIColor { 178 | var newRgba = rgba 179 | 180 | switch attribute { 181 | case .red: 182 | newRgba.red = newValue 183 | 184 | case .green: 185 | newRgba.green = newValue 186 | 187 | case .blue: 188 | newRgba.blue = newValue 189 | 190 | default: 191 | break 192 | } 193 | 194 | return UIColor(red: newRgba.red, green: newRgba.green, blue: newRgba.blue, alpha: newRgba.alpha) 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | # Basic Configuration 2 | opt_in_rules: 3 | - anyobject_protocol 4 | - array_init 5 | - attributes 6 | - closure_end_indentation 7 | - closure_spacing 8 | - collection_alignment 9 | - conditional_returns_on_newline 10 | - contains_over_filter_count 11 | - contains_over_filter_is_empty 12 | - contains_over_first_not_nil 13 | - contains_over_range_nil_comparison 14 | - convenience_type 15 | - empty_collection_literal 16 | - empty_count 17 | - empty_string 18 | - empty_xctest_method 19 | - explicit_init 20 | - explicit_type_interface 21 | - fallthrough 22 | - fatal_error_message 23 | - file_header 24 | - file_name 25 | - file_name_no_space 26 | - file_types_order 27 | - first_where 28 | - flatmap_over_map_reduce 29 | - function_default_parameter_at_end 30 | - identical_operands 31 | - implicit_return 32 | - implicitly_unwrapped_optional 33 | - indentation_width 34 | - joined_default_parameter 35 | - last_where 36 | - legacy_multiple 37 | - legacy_random 38 | - let_var_whitespace 39 | - literal_expression_end_indentation 40 | - lower_acl_than_parent 41 | - missing_docs 42 | - modifier_order 43 | - multiline_arguments 44 | - multiline_arguments_brackets 45 | - multiline_literal_brackets 46 | - multiline_parameters 47 | - multiline_parameters_brackets 48 | - nslocalizedstring_key 49 | - number_separator 50 | - object_literal 51 | - operator_usage_whitespace 52 | - optional_enum_case_matching 53 | - overridden_super_call 54 | - override_in_extension 55 | - pattern_matching_keywords 56 | - prefer_self_type_over_type_of_self 57 | - private_action 58 | - private_outlet 59 | - prohibited_super_call 60 | - reduce_into 61 | - redundant_nil_coalescing 62 | - redundant_type_annotation 63 | - single_test_class 64 | - sorted_first_last 65 | - sorted_imports 66 | - static_operator 67 | - strong_iboutlet 68 | - switch_case_on_newline 69 | - toggle_bool 70 | - trailing_closure 71 | - type_contents_order 72 | - unavailable_function 73 | - unneeded_parentheses_in_closure_argument 74 | - untyped_error_in_catch 75 | - unused_declaration 76 | - unused_import 77 | - vertical_parameter_alignment_on_call 78 | - vertical_whitespace_between_cases 79 | - vertical_whitespace_closing_braces 80 | - vertical_whitespace_opening_braces 81 | - xct_specific_matcher 82 | - yoda_condition 83 | 84 | included: 85 | - Frameworks 86 | - Tests 87 | 88 | excluded: 89 | - Tests/LinuxMain.swift 90 | 91 | disabled_rules: 92 | - todo 93 | 94 | # Rule Configurations 95 | conditional_returns_on_newline: 96 | if_only: true 97 | 98 | explicit_type_interface: 99 | allow_redundancy: true 100 | excluded: 101 | - local 102 | 103 | file_header: 104 | required_pattern: \/\/ Copyright © \d{4} Flinesoft\. All rights reserved\. 105 | 106 | file_name: 107 | suffix_pattern: "Ext" 108 | 109 | identifier_name: 110 | max_length: 60 111 | excluded: 112 | - id 113 | - db 114 | - to 115 | 116 | line_length: 160 117 | 118 | nesting: 119 | type_level: 3 120 | 121 | trailing_comma: 122 | mandatory_comma: true 123 | 124 | trailing_whitespace: 125 | ignores_comments: false 126 | 127 | # Custom Rules 128 | custom_rules: 129 | class_name_suffix_collection_view_controller: 130 | included: ".*.swift" 131 | regex: 'class +\w+(?]+>)? *: +\w+CollectionViewController' 132 | name: "Class Name Suffix View Controller" 133 | message: "All `CollectionViewController` subclasses should end on `CollectionViewController`." 134 | severity: warning 135 | class_name_suffix_table_view_controller: 136 | included: ".*.swift" 137 | regex: 'class +\w+(?]+>)? *: +\w+TableViewController' 138 | name: "Class Name Suffix View Controller" 139 | message: "All `TableViewController` subclasses should end on `TableViewController`." 140 | severity: warning 141 | class_name_suffix_view_controller: 142 | included: ".*.swift" 143 | regex: 'class +\w+(?]+>)? *: +\w+ViewController' 144 | name: "Class Name Suffix View Controller" 145 | message: "All `ViewController` subclasses should end on `ViewController`." 146 | severity: warning 147 | closure_params_parantheses: 148 | included: ".*.swift" 149 | regex: '\{\s*\((?!self)[^):]+\)\s*in' 150 | name: "Unnecessary Closure Params Parantheses" 151 | message: "Don't use parantheses around non-typed parameters in a closure." 152 | severity: warning 153 | comment_type_note: 154 | included: ".*.swift" 155 | regex: '// *(?:WORKAROUND|HACK|WARNING)[:\\s]' 156 | name: "Comment Type NOTE" 157 | message: "Use a '// NOTE:' comment instead." 158 | severity: warning 159 | comment_type_refactor: 160 | included: ".*.swift" 161 | regex: '// *(?:TODO|NOTE)[:\\s][^\n]*(?:refactor|REFACTOR|Refactor)' 162 | name: "Comment Type REFACTOR" 163 | message: "Use a '// REFACTOR:' comment instead." 164 | severity: warning 165 | comment_type_todo: 166 | included: ".*.swift" 167 | regex: '// *(?:BUG|MOCK|FIXME|RELEASE|TEST)[:\\s]' 168 | name: "Comment Type TODO" 169 | message: "Use a '// TODO:' comment instead." 170 | severity: warning 171 | controller_class_name_suffix: 172 | included: ".*.swift" 173 | regex: 'class +\w+(?\w+)(?:<[^\>]+>)? *\{.*static let `default`(?:: *\k)? *= *\k\(.*(?<=private) init\(' 294 | name: "Singleton Default Private Init" 295 | message: "Singletons with a `default` object (pseudo-singletons) should not declare init methods as private." 296 | severity: warning 297 | singleton_shared_final: 298 | included: ".*.swift" 299 | regex: '(?\w+)(?:<[^\>]+>)? *\{.*static let shared(?:: *\k)? *= *\k\(' 300 | name: "Singleton Shared Final" 301 | message: "Singletons with a single object (`shared`) should be marked as final." 302 | severity: warning 303 | singleton_shared_private_init: 304 | included: ".*.swift" 305 | regex: 'class +(?\w+)(?:<[^\>]+>)? *\{.*static let shared(?:: *\k)? *= *\k\(.*(?<= |\t|public|internal) init\(' 306 | name: "Singleton Shared Private Init" 307 | message: "Singletons with a single object (`shared`) should declare their init method(s) as private." 308 | severity: warning 309 | singleton_shared_single_object: 310 | included: ".*.swift" 311 | regex: 'class +(?\w+)(?:<[^\>]+>)? *\{.*(?:static let shared(?:: *\k)? *= *\k\(.*static let \w+(?:: *\k)? *= *\k\(|static let \w+(?:: *\k)? *= *\k\(.*static let shared(?:: *\k)? *= *\k\()' 312 | name: "Singleton Shared Single Object" 313 | message: "Singletons with a `shared` object (real Singletons) should not have other static let properties. Use `default` instead (if needed)." 314 | severity: warning 315 | switch_associated_value_style: 316 | included: ".*.swift" 317 | regex: 'case\s+[^\(][^\n]*(?:\(let |[^\)], let)' 318 | name: "Switch Associated Value Style" 319 | message: "Always put the `let` in front of case – even if only one associated value captured." 320 | severity: warning 321 | todo_format: 322 | included: ".*.swift" 323 | regex: '\/\/ TODO: [^\n]{0,14}\n|\/\/ TODO: \[\S{1,12}\]|\/\/ TODO: [^\[]|\/\/ TODO: \[.{13}[^\]]|\/\/ TODO: \[[^a-z]{2}|\/\/ TODO: \[.{2}[^_]|\/\/ TODO: \[.{7}[^-]|\/\/ TODO: \[.{10}[^-]' 324 | name: "Todo Date" 325 | message: "All TODOs should have a format with creator credentials & date of their creation documented like this: `// TODO: [cg_YYYY-MM-DD] `." 326 | severity: warning 327 | todo_uppercase: 328 | included: ".*.swift" 329 | regex: '\/\/ ?tODO|\/\/ ?ToDO|\/\/ ?TOdO|\/\/ ?TODo|\/\/ ?todo|\/\/ ?Todo|\/\/ ?ToDo|\/\/ ?toDo' 330 | name: "Todo Uppercase" 331 | message: "All TODOs should be all-uppercased like this: `// TODO: [cg_YYYY-MM-DD] `." 332 | severity: warning 333 | todo_whitespacing: 334 | included: ".*.swift" 335 | regex: '\/\/TODO|\/\/ TODO\s|\/\/ TODO:[^ ]|\/\/ TODO: |\/\/ TODO: \[[^\s]{0,10}\][^ ]' 336 | name: "Todo Whitespace" 337 | message: "All TODOs should exactly start like this (mind the whitespacing): `// TODO: [cg_YYYY-MM-DD] `." 338 | severity: warning 339 | tuple_index: 340 | included: ".*.swift" 341 | regex: '(\$\d|\w*[^\d \(\[\{])\.\d' 342 | name: "Tuple Index" 343 | message: "Prevent unwraping tuples by their index – define a typealias with named components instead." 344 | severity: warning 345 | unnecessary_case_break: 346 | included: ".*.swift" 347 | regex: '(case |default)(?:[^\n\}]+\n){2,}\s*break *\n|\n *\n *break(?:\n *\n|\n *\})' 348 | name: "Unnecessary Case Break" 349 | message: "Don't use break in switch cases – Swift breaks by default." 350 | severity: warning 351 | unnecessary_nil_assignment: 352 | included: ".*.swift" 353 | regex: 'var \S+\s*:\s*[^\s]+\?\s*=\s*nil' 354 | name: "Unnecessary Nil Assignment" 355 | message: "Don't assign nil as a value when defining an optional type – it's nil by default." 356 | severity: warning 357 | vertical_whitespaces_around_mark: 358 | included: ".*.swift" 359 | regex: '\/\/\s*MARK:[^\n]*(\n\n)|(\n\n\n)[ \t]*\/\/\s*MARK:|[^\s{]\n[^\n\/]*\/\/\s*MARK:' 360 | name: "Vertical Whitespaces Around MARK:" 361 | message: "Include a single vertical whitespace (empty line) before and none after MARK: comments." 362 | severity: warning 363 | view_controller_variable_naming: 364 | included: ".*.swift" 365 | regex: '(?:let|var) +\w*(?:vc|VC|Vc|viewC|viewController|ViewController) *=' 366 | name: "View Controller Variable Naming" 367 | message: "Always name your view controller variables with the suffix `ViewCtrl`." 368 | severity: warning 369 | whitespace_around_range_operators: 370 | included: ".*.swift" 371 | regex: '\w\.\.[<\.]\w' 372 | name: "Whitespace around Range Operators" 373 | message: "A range operator should be surrounded by a single whitespace." 374 | severity: warning 375 | whitespace_comment_start: 376 | included: ".*.swift" 377 | regex: '[^:#\]\}\)][^:#\]\}\)]\/\/[^\s\/]' 378 | name: "Whitespace Comment Start" 379 | message: "A comment should always start with a whitespace." 380 | severity: warning 381 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 4 |

5 | 6 |

7 | 8 | Build Status 10 | 11 | 12 | codebeat badge 14 | 15 | 16 | Version: 1.9.3 18 | 19 | Swift: 5.0 21 | Platforms: iOS | tvOS 23 | 24 | License: MIT 26 | 27 |
28 | 29 | PayPal: Donate 31 | 32 | 33 | GitHub: Become a sponsor 35 | 36 | 37 | Patreon: Become a patron 39 | 40 |

41 | 42 |

43 | Installation 44 | • Usage 45 | • Donation 46 | • Issues 47 | • Contributing 48 | • License 49 |

50 | 51 | 52 | # HandyUIKit 53 | 54 | The goal of this library is to provide **handy UI related features** that we feel like they should have been part of the UIKit frameworks themselves. Therefore this library is intended to inherit solutions for common tasks that appear in daily programming and tries to comply to the same naming conventions as already used in the Apple frameworks. 55 | 56 | If you like this, please also checkout [HandySwift](https://github.com/FlineDev/HandySwift) for handy features that didn't make it into the Swift standard library. It includes additions that are not UI related. 57 | 58 | ## Installation 59 | 60 | Currently the recommended way of installing this library is via [Carthage](https://github.com/Carthage/Carthage). [Cocoapods](https://github.com/CocoaPods/CocoaPods) might work, too, but is not tested. 61 | 62 | You can of course also just include this framework manually into your project by downloading it or by using git submodules. 63 | 64 | 65 | ## Usage 66 | 67 | Please have a look at the UsageExamples.playground for a complete list of features provided. 68 | Open the Playground from within the `.xcworkspace` in order for it to work. 69 | 70 | --- 71 | #### Feature Overview 72 | 73 | - **Extensions** 74 | - [UIColor](#uicolorextension) 75 | - [UIView](#uiviewextension) 76 | - [CoreGraphics](#coregraphicsextensions) 77 | - [StringExtension](#stringextension) 78 | - [UIImageExtension](#uiimageextension) 79 | - [UITableViewExtension](#uitableviewextension) 80 | - [UIWindowExtension](#uiwindowextension) 81 | - [NibLoadable](#nibloadable) 82 | - **IBDesignables** 83 | - [RoundableView](#roundableview) 84 | - [TemplateButton](#templatebutton) 85 | - [TemplateImageView](#templateimageview) 86 | 87 | --- 88 | 89 | ### UIColorExtension 90 | 91 | > If you are using the RGB color system to define your colors, you definitely should checkout **[this great blog post](https://medium.com/@erikdkennedy/color-in-ui-design-a-practical-framework-e18cacd97f9e#.krfv78qsm)** on **why RGB is a bad choice** for most projects (not all, of course). And if you think you found the perfect way of changing the brightness of a color using the HSB system, you'll be proved wrong there, too. Honestly, it's worth a read. If you don't want though: "Luminance" is the keyword here and it is the single most important reason why HandyUIKit integrates **native support for the HLC** (or sometimes called LCh) color system to the `UIColor` class. HLC is a more human-understandable transformation of the LAB color space and shares its great advantage of having a single value that you need to change to **correctly change the perceived brightness** of any given color: The `luminance` value. And changing the brightness can save you a lot of time when working with colors in apps, as described in the blog post. 92 | 93 | #### init(hue:luminance:chroma:) 94 | Initializes a UIColor with given HLC (LCh) colors normed to ranges from 0 to 1. 95 | 96 | ``` Swift 97 | let hlcaColor = UIColor(hue: 180/360, luminance: 30/100, chroma: 125/128, alpha: 1) 98 | ``` 99 | 100 | #### .hlca 101 | Returns a tuple with named HLCA parameters for easy access. 102 | 103 | ``` Swift 104 | hlcaColor.hlca.hue // => 0.5 105 | hlcaColor.hlca.luminance // => 0.3 106 | hlcaColor.hlca.chroma // => 0.97 107 | hlcaColor.hlca.alpha // => 1.0 108 | ``` 109 | 110 | #### .rgba 111 | Returns a tuple with named RGBA parameters for easy access. 112 | 113 | ``` Swift 114 | let rgbaColor = UIColor(red: 0.1, green: 0.2, blue: 0.3, alpha: 0.4) 115 | rgbaColor.rgba.red // => 0.1 116 | rgbaColor.rgba.green // => 0.2 117 | rgbaColor.rgba.blue // => 0.3 118 | rgbaColor.rgba.alpha // => 0.4 119 | ``` 120 | 121 | #### .hsba 122 | Returns a tuple with named HSBA parameters for easy access. 123 | 124 | ``` Swift 125 | let hsbaColor = UIColor(hue: 0.1, saturation: 0.2, brightness: 0.3, alpha: 0.4) 126 | hsbaColor.hsba.hue // => 0.1 127 | hsbaColor.hsba.saturation // => 0.2 128 | hsbaColor.hsba.brightness // => 0.3 129 | hsbaColor.hsba.alpha // => 0.4 130 | ``` 131 | 132 | 133 | #### .change(ChangeableAttribute, by:) 134 | Creates a new `UIColor` object with a single attribute changed by a given difference using addition. 135 | 136 | ``` Swift 137 | color.rgba.blue // => 0.3 138 | let newColor = color.change(.blue, by: 0.2) 139 | newColor.rgba.blue // => 0.5 140 | ``` 141 | 142 | #### .change(ChangeableAttribute, to:) 143 | Creates a new `UIColor` object with the value of a single attribute set to a given value. 144 | 145 | ``` Swift 146 | color.hlca.luminance // => 0.3 147 | let newColor = color.change(.luminance, to: 0.8) 148 | newColor.hlca.luminance // => 0.8 149 | ``` 150 | 151 | ### UIViewExtension 152 | 153 | #### .toImage(size:) 154 | Takes a screenshot of the UIView's content optionally resizing the result to a given size. 155 | 156 | ``` Swift 157 | let view = UIView(frame: CGRect(width: 500, height: 500)) 158 | let subview = UIView(frame: CGRect(width: 200, height: 200)) 159 | view.addSubview(subview) 160 | 161 | view.backgroundColor = .blue 162 | subview.backgroundColor = .red 163 | 164 | let fullSizeContent = view.toImage() // => 165 | let downSizedContent = view.toImage(size: CGSize(width: 80, height: 80)) 166 | // => 167 | ``` 168 | 169 | #### .bindEdgesToSuperview() 170 | Adds constraints to the subview so it always has the same size and position as the superview. 171 | 172 | ``` Swift 173 | view.frame // => {x: 0, y: 0, w: 500, h: 500} 174 | subview.frame // => {x: 150, y: 150, w: 200, h: 200} 175 | subview.bindEdgesToSuperview() 176 | view.layoutIfNeeded() 177 | subview.frame // => {x: 0, y: 0, w: 500, h: 500} 178 | ``` 179 | 180 | 181 | ### CoreGraphicsExtensions 182 | 183 | #### CGSize.inPixels / CGSize.inPixels(screen:) 184 | Returns a new CGSize object with the width and height converted to true pixels on screen. 185 | 186 | ``` Swift 187 | let size = CGSize(width: 100, height: 50) 188 | size.inPixels // test this with a Retina screen target 189 | // => {w 200 h 100} 190 | size.inPixels(UIScreen.screens.last!) // pass a different screen 191 | // => {w 50 h 25} 192 | ``` 193 | 194 | #### CGPoint.inPixels / CGPoint.inPixels(screen:) 195 | Returns a new CGPoint object with the x and y converted to true pixels on screen. 196 | 197 | ``` Swift 198 | let point = CGPoint(x: 100, y: 50) 199 | point.inPixels // test this with a Retina screen target 200 | // => {x 200 y 100} 201 | let someScreen = UIScreen.screens.last! 202 | point.inPixels(someScreen) // pass a different screen 203 | // => {x 50 y 25} 204 | ``` 205 | 206 | #### CGRect.inPixels / CGRect.inPixels(screen:) 207 | Returns a new CGRect object with the origin and size converted to true pixels on screen. 208 | 209 | ``` Swift 210 | let rect = CGRect(x: 10, y: 20, width: 100, height: 50) 211 | rect.inPixels // test this with a Retina screen target 212 | // => {x 20 y 40 w 200 h 100} 213 | let someScreen = UIScreen.screens.last! 214 | rect.inPixels(someScreen) // pass a different screen 215 | // => {x 5 y 10 w 50 h 25} 216 | ``` 217 | 218 | #### CGRect.init(size:) / CGRect.init(width:height:) 219 | Creates a new CGRect object from origin zero with given size. 220 | 221 | ``` Swift 222 | let someSize = CGSize(width: 100, height: 50) 223 | 224 | let originZeroRect1 = CGRect(size: someSize) 225 | let originZeroRect2 = CGRect(width: 100, height: 50) 226 | ``` 227 | 228 | 229 | ### StringExtension 230 | #### .height(forFixedWidth:font:) 231 | Calculates and returns the height needed to fit the text into a width-constrained rect. 232 | 233 | ``` Swift 234 | let loremIpsum = "Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat. Quis aute iure reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint obcaecat cupiditat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." 235 | 236 | loremIpsum.height(forFixedWidth: 300, font: UIFont.systemFont(ofSize: 14, weight: UIFontWeightBold)) 237 | // => 183.77734375 238 | ``` 239 | 240 | #### .width(forFixedHeight:font:) 241 | Calculates and returns the width needed to fit the text into a height-constrained rect. 242 | 243 | ``` Swift 244 | loremIpsum.width(forFixedHeight: 21, font: UIFont.systemFont(ofSize: 12, weight: UIFontWeightUltraLight)) 245 | // => 2351.0390625 246 | ``` 247 | 248 | #### .hyphenated() 249 | A hyphenated NSAttributedString with justified alignment and word wrapping line break mode. 250 | 251 | ``` Swift 252 | loremIpsum.hyphenated() // => a justified & hyphenated NSAttributedString object 253 | ``` 254 | 255 | #### .superscripted(font:) / .subscripted(font:) / .superAndSubscripted(font:) 256 | Superscript and/or subscript part of your strings with the structures `^{superscripted text}` and `_{subscripted text}`. 257 | 258 | ``` Swift 259 | "x^{2}".superscripted(font: UIFont.systemFont(ofSize: 20, weight: .medium)) 260 | ``` 261 | Result: x2 262 | 263 | ``` Swift 264 | "CO_{2}".subscripted(font: UIFont.systemFont(ofSize: 20, weight: .medium)) 265 | ``` 266 | Result: CO2 267 | 268 | ``` Swift 269 | "_{20}Ca^{1,0}".superAndSubscripted(font: UIFont.systemFont(ofSize: 20, weight: .regular)) 270 | ``` 271 | Result: 20Ca1,0 272 | 273 | ### UIImageExtension 274 | #### .toGrayscale() 275 | Creates a grayscale version of the image. 276 | 277 | ``` Swift 278 | let image = UIImage(named: "someImage")! 279 | let grayscaleImage = image.toGrayscale() 280 | ``` 281 | 282 | ### UITableViewExtension 283 | #### dequeueCell(ofType:, for:) 284 | Returns a reusable table view cell of type `cellType` with the name of its type as reuse identifier and adds it to the table. 285 | 286 | ```swift 287 | let cell = tableView.dequeueCell(ofType: MyUITableViewCell.self, for: indexPath) 288 | ``` 289 | 290 | #### dequeueHeaderFooterView(ofType:) 291 | Returns a reusable header or footer view of type `viewType` with the name of its type as reuse identifier and adds it to the table. 292 | 293 | ```swift 294 | let view = tableView.dequeueHeaderFooterView(ofType: MyUITableHeaderFooterView.self) 295 | ``` 296 | 297 | #### registerCell(ofType:) 298 | Registers a nib with the name of `cellType` if it exists or registers the class of type `cellType` as reusable cell. 299 | 300 | ```swift 301 | tableView.registerCell(ofType: MyUITableViewCell.self) 302 | ``` 303 | 304 | #### registerHeaderFooterView(ofType:) 305 | Registers a nib with the name of `viewType` if it exists or registers the class of type `viewType` as reusable header footer view. 306 | 307 | ```swift 308 | tableView.registerHeaderFooterView(ofType: MyUITableHeaderFooterView.self) 309 | ``` 310 | 311 | ### UIWindowExtension 312 | #### visibleViewController 313 | 314 | Returns the currently visible view controller if any reachable within the window. 315 | 316 | #### visibleViewController(from:) 317 | 318 | Recursively follows navigation controllers, tab bar controllers and modal presented view controllers starting from the given view controller to find the currently visible view controller. 319 | 320 | ### NibLoadable 321 | 322 | This is a protocol helper you can make any `UIView` subclass conform to. The situation where you might want to do this is when you want to design a `UIView` subclass in a XIB file. In this case, just make your view type conform to `NibLoadable` like this: 323 | 324 | ```swift 325 | class MyTableViewCell: UITableViewCell, NibLoadable { 326 | // your code 327 | } 328 | ``` 329 | 330 | By default `NibLoadable` will search for a file named like your type, for example `MyTableViewCell.xib` within the project and load it. You can override `static var nibName: String` to change this behavior if you need to. 331 | 332 | Your view must be set as the `Files owner` within the XIB file, also there must be only one root `UIView` object (which should just be of type `UIView`, not your subclass). 333 | 334 | Now you can add `IBOutlets` and `IBActions` to your subclass and connect them within the XIB file to the `Files owner`. 335 | 336 | In order to make loading work from both code and Storyboards, call `loadFromNib()` from within your init methods like so: 337 | 338 | ```swift 339 | required init?(coder aDecoder: NSCoder) { 340 | super.init(coder: aDecoder) 341 | loadFromNib() 342 | } 343 | 344 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 345 | super.init(style: style, reuseIdentifier: reuseIdentifier) 346 | loadFromNib() 347 | } 348 | ``` 349 | 350 | That's it, now you should be able to load your custom view types designed within XIBs from code & in Storyboards. For Storyboard usage, simply add a `UIView` object and change it's type to your view subclass and everything should work when running your app. To see your custom view within Interface Builder, add `@IBDesignable` in front of the class declaration. 351 | 352 | If you need to do any setup steps after the IBOutlets are loaded, you can override `nibDidLoad` which can be seen as the analogous to `viewDidLoad` in view controller in this perspective. 353 | 354 | ### RoundableView 355 | 356 | This is an `IBDesignable` subclass of `UIView` which provides the `cornerRadius` to be set right from within Interface Builder. Simply add a `UIView` object to your IB file and change it's type to `RoundableView` and you should see `cornerRadius` within the property inspector. 357 | 358 | ### TemplateButton 359 | 360 | This is an `IBDesignable` subclass of `UIButton` which will automatically make the `image` a mask for the `tintColor` value by setting the image rendering mode to `.alwaysTemplate` automatically. 361 | 362 | ### TemplateImageView 363 | 364 | This is an `IBDesignable` subclass of `UIImageView` which will automatically make the `image` a mask for the `tintColor` value by setting the image rendering mode to `.alwaysTemplate` automatically. 365 | 366 | 367 | ## Donation 368 | 369 | BartyCrouch was brought to you by [Cihat Gündüz](https://github.com/Jeehut) in his free time. If you want to thank me and support the development of this project, please **make a small donation on [PayPal](https://paypal.me/Dschee/5EUR)**. In case you also like my other [open source contributions](https://github.com/FlineDev) and [articles](https://medium.com/@Jeehut), please consider motivating me by **becoming a sponsor on [GitHub](https://github.com/sponsors/Jeehut)** or a **patron on [Patreon](https://www.patreon.com/Jeehut)**. 370 | 371 | Thank you very much for any donation, it really helps out a lot! 💯 372 | 373 | 374 | ## Contributing 375 | 376 | See the file [CONTRIBUTING.md](https://github.com/FlineDev/HandyUIKit/blob/stable/CONTRIBUTING.md). 377 | 378 | 379 | ## License 380 | 381 | This library is released under the [MIT License](http://opensource.org/licenses/MIT). See LICENSE for details. 382 | -------------------------------------------------------------------------------- /HandyUIKit.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 2E1DABDE20790C1700627CC2 /* UIImageExtTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E1DABDD20790C1700627CC2 /* UIImageExtTests.swift */; }; 11 | 2E1DABDF20790C1700627CC2 /* UIImageExtTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E1DABDD20790C1700627CC2 /* UIImageExtTests.swift */; }; 12 | 2E1DABE520790F5900627CC2 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2E1DABE420790F5900627CC2 /* Images.xcassets */; }; 13 | 2E1DABE620790F5900627CC2 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2E1DABE420790F5900627CC2 /* Images.xcassets */; }; 14 | 2E71660A207904A200135F39 /* UIImageExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E716609207904A200135F39 /* UIImageExt.swift */; }; 15 | 2E71660B207904A200135F39 /* UIImageExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E716609207904A200135F39 /* UIImageExt.swift */; }; 16 | 8218E4C92211BE3B007AAAF3 /* RoundableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8218E4C82211BE3B007AAAF3 /* RoundableView.swift */; }; 17 | 8218E4CA2211BE3B007AAAF3 /* RoundableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8218E4C82211BE3B007AAAF3 /* RoundableView.swift */; }; 18 | 8218E4CC2211BE8A007AAAF3 /* TemplateButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8218E4CB2211BE8A007AAAF3 /* TemplateButton.swift */; }; 19 | 8218E4CD2211BE8A007AAAF3 /* TemplateButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8218E4CB2211BE8A007AAAF3 /* TemplateButton.swift */; }; 20 | 8218E4CF2211BEA8007AAAF3 /* TemplateImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8218E4CE2211BEA8007AAAF3 /* TemplateImageView.swift */; }; 21 | 8218E4D02211BEA8007AAAF3 /* TemplateImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8218E4CE2211BEA8007AAAF3 /* TemplateImageView.swift */; }; 22 | 8218E4D22211BEDC007AAAF3 /* NibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8218E4D12211BEDC007AAAF3 /* NibLoadable.swift */; }; 23 | 8218E4D32211BEDC007AAAF3 /* NibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8218E4D12211BEDC007AAAF3 /* NibLoadable.swift */; }; 24 | 8218E4E12211D972007AAAF3 /* UIWindowExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8218E4E02211D972007AAAF3 /* UIWindowExt.swift */; }; 25 | 8218E4E22211D972007AAAF3 /* UIWindowExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8218E4E02211D972007AAAF3 /* UIWindowExt.swift */; }; 26 | 8242F0A42189FE230075FDF3 /* CGSizeExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8242F0A32189FE230075FDF3 /* CGSizeExt.swift */; }; 27 | 8242F0A52189FE230075FDF3 /* CGSizeExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8242F0A32189FE230075FDF3 /* CGSizeExt.swift */; }; 28 | 8242F0A72189FE410075FDF3 /* CGPointExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8242F0A62189FE410075FDF3 /* CGPointExt.swift */; }; 29 | 8242F0A82189FE410075FDF3 /* CGPointExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8242F0A62189FE410075FDF3 /* CGPointExt.swift */; }; 30 | 8242F0AA2189FE500075FDF3 /* CGRectExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8242F0A92189FE500075FDF3 /* CGRectExt.swift */; }; 31 | 8242F0AB2189FE500075FDF3 /* CGRectExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8242F0A92189FE500075FDF3 /* CGRectExt.swift */; }; 32 | A11830F31E5996FD00CBE087 /* StringExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11830F21E5996FD00CBE087 /* StringExt.swift */; }; 33 | A11830F41E5996FD00CBE087 /* StringExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11830F21E5996FD00CBE087 /* StringExt.swift */; }; 34 | A11830F61E599BF600CBE087 /* StringExtTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11830F51E599BF600CBE087 /* StringExtTests.swift */; }; 35 | A11830F71E599BF600CBE087 /* StringExtTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11830F51E599BF600CBE087 /* StringExtTests.swift */; }; 36 | A14E0AC61E1F986A00DFC788 /* HandyUIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A14E0ABC1E1F986A00DFC788 /* HandyUIKit.framework */; }; 37 | A14E0AE41E1F987C00DFC788 /* HandyUIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A14E0ADB1E1F987C00DFC788 /* HandyUIKit.framework */; }; 38 | A14E0B111E1F9B3100DFC788 /* UIViewExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = A14E0B0E1E1F9B3100DFC788 /* UIViewExt.swift */; }; 39 | A14E0B121E1F9B3100DFC788 /* UIViewExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = A14E0B0E1E1F9B3100DFC788 /* UIViewExt.swift */; }; 40 | A1D4453C1E59C9370014A250 /* NSAttributedStringExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1D4453B1E59C9370014A250 /* NSAttributedStringExt.swift */; }; 41 | A1D4453D1E59C9370014A250 /* NSAttributedStringExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1D4453B1E59C9370014A250 /* NSAttributedStringExt.swift */; }; 42 | A1F221681E3E07DD00419B06 /* UIColorExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1F221671E3E07DD00419B06 /* UIColorExt.swift */; }; 43 | A1F221691E3E07DD00419B06 /* UIColorExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1F221671E3E07DD00419B06 /* UIColorExt.swift */; }; 44 | A1F221701E3E0B8400419B06 /* ColorExtTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1F2216D1E3E0B7700419B06 /* ColorExtTests.swift */; }; 45 | A1F221711E3E0B8500419B06 /* ColorExtTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1F2216D1E3E0B7700419B06 /* ColorExtTests.swift */; }; 46 | A1F2217C1E3E2C5500419B06 /* CoreGraphicsExtTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1F2217B1E3E2C5500419B06 /* CoreGraphicsExtTests.swift */; }; 47 | A1F2217D1E3E2C5500419B06 /* CoreGraphicsExtTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1F2217B1E3E2C5500419B06 /* CoreGraphicsExtTests.swift */; }; 48 | A1F221801E3E778F00419B06 /* ColorSpaces.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1F2217F1E3E778F00419B06 /* ColorSpaces.swift */; }; 49 | A1F221811E3E778F00419B06 /* ColorSpaces.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1F2217F1E3E778F00419B06 /* ColorSpaces.swift */; }; 50 | C5C8A2482181F7BE00D12F8E /* UITableViewExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5C8A2472181F7BE00D12F8E /* UITableViewExt.swift */; }; 51 | C5C8A2492181F7BE00D12F8E /* UITableViewExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5C8A2472181F7BE00D12F8E /* UITableViewExt.swift */; }; 52 | /* End PBXBuildFile section */ 53 | 54 | /* Begin PBXContainerItemProxy section */ 55 | A14E0AC71E1F986A00DFC788 /* PBXContainerItemProxy */ = { 56 | isa = PBXContainerItemProxy; 57 | containerPortal = A14E0AB31E1F986A00DFC788 /* Project object */; 58 | proxyType = 1; 59 | remoteGlobalIDString = A14E0ABB1E1F986A00DFC788; 60 | remoteInfo = HandyUIKit; 61 | }; 62 | A14E0AE51E1F987C00DFC788 /* PBXContainerItemProxy */ = { 63 | isa = PBXContainerItemProxy; 64 | containerPortal = A14E0AB31E1F986A00DFC788 /* Project object */; 65 | proxyType = 1; 66 | remoteGlobalIDString = A14E0ADA1E1F987C00DFC788; 67 | remoteInfo = "HandyUIKit-tvOS"; 68 | }; 69 | /* End PBXContainerItemProxy section */ 70 | 71 | /* Begin PBXFileReference section */ 72 | 2E1DABDD20790C1700627CC2 /* UIImageExtTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImageExtTests.swift; sourceTree = ""; }; 73 | 2E1DABE420790F5900627CC2 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 74 | 2E716609207904A200135F39 /* UIImageExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImageExt.swift; sourceTree = ""; }; 75 | 8218E4C82211BE3B007AAAF3 /* RoundableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundableView.swift; sourceTree = ""; }; 76 | 8218E4CB2211BE8A007AAAF3 /* TemplateButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateButton.swift; sourceTree = ""; }; 77 | 8218E4CE2211BEA8007AAAF3 /* TemplateImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateImageView.swift; sourceTree = ""; }; 78 | 8218E4D12211BEDC007AAAF3 /* NibLoadable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NibLoadable.swift; sourceTree = ""; }; 79 | 8218E4E02211D972007AAAF3 /* UIWindowExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIWindowExt.swift; sourceTree = ""; }; 80 | 8242F09C2189F59F0075FDF3 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; }; 81 | 8242F09D2189F6FD0075FDF3 /* CODE_OF_CONDUCT.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CODE_OF_CONDUCT.md; sourceTree = ""; }; 82 | 8242F09F2189F7190075FDF3 /* CONTRIBUTING.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CONTRIBUTING.md; sourceTree = ""; }; 83 | 8242F0A32189FE230075FDF3 /* CGSizeExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGSizeExt.swift; sourceTree = ""; }; 84 | 8242F0A62189FE410075FDF3 /* CGPointExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGPointExt.swift; sourceTree = ""; }; 85 | 8242F0A92189FE500075FDF3 /* CGRectExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGRectExt.swift; sourceTree = ""; }; 86 | A11830F21E5996FD00CBE087 /* StringExt.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringExt.swift; sourceTree = ""; }; 87 | A11830F51E599BF600CBE087 /* StringExtTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringExtTests.swift; sourceTree = ""; }; 88 | A14E0ABC1E1F986A00DFC788 /* HandyUIKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = HandyUIKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 89 | A14E0AC51E1F986A00DFC788 /* HandyUIKit-iOS_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "HandyUIKit-iOS_Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 90 | A14E0ADB1E1F987C00DFC788 /* HandyUIKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = HandyUIKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 91 | A14E0AE31E1F987C00DFC788 /* HandyUIKit-tvOS_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "HandyUIKit-tvOS_Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 92 | A14E0AF61E1F98E400DFC788 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 93 | A14E0AFB1E1F98E400DFC788 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 94 | A14E0B0E1E1F9B3100DFC788 /* UIViewExt.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewExt.swift; sourceTree = ""; }; 95 | A1D4453B1E59C9370014A250 /* NSAttributedStringExt.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSAttributedStringExt.swift; sourceTree = ""; }; 96 | A1F221671E3E07DD00419B06 /* UIColorExt.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIColorExt.swift; sourceTree = ""; }; 97 | A1F2216D1E3E0B7700419B06 /* ColorExtTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorExtTests.swift; sourceTree = ""; }; 98 | A1F221791E3E15FA00419B06 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 99 | A1F2217B1E3E2C5500419B06 /* CoreGraphicsExtTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreGraphicsExtTests.swift; sourceTree = ""; }; 100 | A1F2217F1E3E778F00419B06 /* ColorSpaces.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorSpaces.swift; sourceTree = ""; }; 101 | A1F221821E3FEDD600419B06 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = .swiftlint.yml; sourceTree = ""; }; 102 | A1F221831E3FEDF300419B06 /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; 103 | A1F221841E3FEE1C00419B06 /* HandyUIKit.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = HandyUIKit.podspec; sourceTree = ""; }; 104 | A1F221861E3FEE8B00419B06 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; 105 | C5C8A2472181F7BE00D12F8E /* UITableViewExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITableViewExt.swift; sourceTree = ""; }; 106 | /* End PBXFileReference section */ 107 | 108 | /* Begin PBXFrameworksBuildPhase section */ 109 | A14E0AB81E1F986A00DFC788 /* Frameworks */ = { 110 | isa = PBXFrameworksBuildPhase; 111 | buildActionMask = 2147483647; 112 | files = ( 113 | ); 114 | runOnlyForDeploymentPostprocessing = 0; 115 | }; 116 | A14E0AC21E1F986A00DFC788 /* Frameworks */ = { 117 | isa = PBXFrameworksBuildPhase; 118 | buildActionMask = 2147483647; 119 | files = ( 120 | A14E0AC61E1F986A00DFC788 /* HandyUIKit.framework in Frameworks */, 121 | ); 122 | runOnlyForDeploymentPostprocessing = 0; 123 | }; 124 | A14E0AD71E1F987C00DFC788 /* Frameworks */ = { 125 | isa = PBXFrameworksBuildPhase; 126 | buildActionMask = 2147483647; 127 | files = ( 128 | ); 129 | runOnlyForDeploymentPostprocessing = 0; 130 | }; 131 | A14E0AE01E1F987C00DFC788 /* Frameworks */ = { 132 | isa = PBXFrameworksBuildPhase; 133 | buildActionMask = 2147483647; 134 | files = ( 135 | A14E0AE41E1F987C00DFC788 /* HandyUIKit.framework in Frameworks */, 136 | ); 137 | runOnlyForDeploymentPostprocessing = 0; 138 | }; 139 | /* End PBXFrameworksBuildPhase section */ 140 | 141 | /* Begin PBXGroup section */ 142 | 2E1DABE020790DC300627CC2 /* Resources */ = { 143 | isa = PBXGroup; 144 | children = ( 145 | 2E1DABE420790F5900627CC2 /* Images.xcassets */, 146 | ); 147 | path = Resources; 148 | sourceTree = ""; 149 | }; 150 | 8218E4C72211BE28007AAAF3 /* IBDesignables */ = { 151 | isa = PBXGroup; 152 | children = ( 153 | 8218E4C82211BE3B007AAAF3 /* RoundableView.swift */, 154 | 8218E4CB2211BE8A007AAAF3 /* TemplateButton.swift */, 155 | 8218E4CE2211BEA8007AAAF3 /* TemplateImageView.swift */, 156 | ); 157 | path = IBDesignables; 158 | sourceTree = ""; 159 | }; 160 | 8218E4D42211C17F007AAAF3 /* Extensions */ = { 161 | isa = PBXGroup; 162 | children = ( 163 | A1F2216D1E3E0B7700419B06 /* ColorExtTests.swift */, 164 | A1F2217B1E3E2C5500419B06 /* CoreGraphicsExtTests.swift */, 165 | A11830F51E599BF600CBE087 /* StringExtTests.swift */, 166 | 2E1DABDD20790C1700627CC2 /* UIImageExtTests.swift */, 167 | ); 168 | path = Extensions; 169 | sourceTree = ""; 170 | }; 171 | A14E0AB21E1F986A00DFC788 = { 172 | isa = PBXGroup; 173 | children = ( 174 | A14E0AF21E1F98E400DFC788 /* Frameworks */, 175 | A14E0AF71E1F98E400DFC788 /* Tests */, 176 | A1F221781E3E15E500419B06 /* RootFiles */, 177 | A14E0ABD1E1F986A00DFC788 /* Products */, 178 | ); 179 | sourceTree = ""; 180 | }; 181 | A14E0ABD1E1F986A00DFC788 /* Products */ = { 182 | isa = PBXGroup; 183 | children = ( 184 | A14E0ABC1E1F986A00DFC788 /* HandyUIKit.framework */, 185 | A14E0AC51E1F986A00DFC788 /* HandyUIKit-iOS_Tests.xctest */, 186 | A14E0ADB1E1F987C00DFC788 /* HandyUIKit.framework */, 187 | A14E0AE31E1F987C00DFC788 /* HandyUIKit-tvOS_Tests.xctest */, 188 | ); 189 | name = Products; 190 | sourceTree = ""; 191 | }; 192 | A14E0AF21E1F98E400DFC788 /* Frameworks */ = { 193 | isa = PBXGroup; 194 | children = ( 195 | A14E0AF31E1F98E400DFC788 /* HandyUIKit */, 196 | A14E0AF51E1F98E400DFC788 /* SupportingFiles */, 197 | ); 198 | path = Frameworks; 199 | sourceTree = ""; 200 | }; 201 | A14E0AF31E1F98E400DFC788 /* HandyUIKit */ = { 202 | isa = PBXGroup; 203 | children = ( 204 | A1F2217F1E3E778F00419B06 /* ColorSpaces.swift */, 205 | A14E0B0C1E1F9B3100DFC788 /* Extensions */, 206 | 8218E4C72211BE28007AAAF3 /* IBDesignables */, 207 | 8218E4D12211BEDC007AAAF3 /* NibLoadable.swift */, 208 | ); 209 | path = HandyUIKit; 210 | sourceTree = ""; 211 | }; 212 | A14E0AF51E1F98E400DFC788 /* SupportingFiles */ = { 213 | isa = PBXGroup; 214 | children = ( 215 | A14E0AF61E1F98E400DFC788 /* Info.plist */, 216 | ); 217 | path = SupportingFiles; 218 | sourceTree = ""; 219 | }; 220 | A14E0AF71E1F98E400DFC788 /* Tests */ = { 221 | isa = PBXGroup; 222 | children = ( 223 | A14E0AF81E1F98E400DFC788 /* HandyUIKitTests */, 224 | A14E0AFA1E1F98E400DFC788 /* SupportingFiles */, 225 | ); 226 | path = Tests; 227 | sourceTree = ""; 228 | }; 229 | A14E0AF81E1F98E400DFC788 /* HandyUIKitTests */ = { 230 | isa = PBXGroup; 231 | children = ( 232 | 8218E4D42211C17F007AAAF3 /* Extensions */, 233 | 2E1DABE020790DC300627CC2 /* Resources */, 234 | ); 235 | path = HandyUIKitTests; 236 | sourceTree = ""; 237 | }; 238 | A14E0AFA1E1F98E400DFC788 /* SupportingFiles */ = { 239 | isa = PBXGroup; 240 | children = ( 241 | A14E0AFB1E1F98E400DFC788 /* Info.plist */, 242 | ); 243 | path = SupportingFiles; 244 | sourceTree = ""; 245 | }; 246 | A14E0B0C1E1F9B3100DFC788 /* Extensions */ = { 247 | isa = PBXGroup; 248 | children = ( 249 | 8242F0A62189FE410075FDF3 /* CGPointExt.swift */, 250 | 8242F0A92189FE500075FDF3 /* CGRectExt.swift */, 251 | 8242F0A32189FE230075FDF3 /* CGSizeExt.swift */, 252 | A1D4453B1E59C9370014A250 /* NSAttributedStringExt.swift */, 253 | A11830F21E5996FD00CBE087 /* StringExt.swift */, 254 | A1F221671E3E07DD00419B06 /* UIColorExt.swift */, 255 | 2E716609207904A200135F39 /* UIImageExt.swift */, 256 | C5C8A2472181F7BE00D12F8E /* UITableViewExt.swift */, 257 | A14E0B0E1E1F9B3100DFC788 /* UIViewExt.swift */, 258 | 8218E4E02211D972007AAAF3 /* UIWindowExt.swift */, 259 | ); 260 | path = Extensions; 261 | sourceTree = ""; 262 | }; 263 | A1F221781E3E15E500419B06 /* RootFiles */ = { 264 | isa = PBXGroup; 265 | children = ( 266 | A1F221821E3FEDD600419B06 /* .swiftlint.yml */, 267 | 8242F09C2189F59F0075FDF3 /* CHANGELOG.md */, 268 | 8242F09D2189F6FD0075FDF3 /* CODE_OF_CONDUCT.md */, 269 | 8242F09F2189F7190075FDF3 /* CONTRIBUTING.md */, 270 | A1F221841E3FEE1C00419B06 /* HandyUIKit.podspec */, 271 | A1F221861E3FEE8B00419B06 /* LICENSE */, 272 | A1F221831E3FEDF300419B06 /* Package.swift */, 273 | A1F221791E3E15FA00419B06 /* README.md */, 274 | ); 275 | name = RootFiles; 276 | sourceTree = ""; 277 | }; 278 | /* End PBXGroup section */ 279 | 280 | /* Begin PBXHeadersBuildPhase section */ 281 | A14E0AB91E1F986A00DFC788 /* Headers */ = { 282 | isa = PBXHeadersBuildPhase; 283 | buildActionMask = 2147483647; 284 | files = ( 285 | ); 286 | runOnlyForDeploymentPostprocessing = 0; 287 | }; 288 | A14E0AD81E1F987C00DFC788 /* Headers */ = { 289 | isa = PBXHeadersBuildPhase; 290 | buildActionMask = 2147483647; 291 | files = ( 292 | ); 293 | runOnlyForDeploymentPostprocessing = 0; 294 | }; 295 | /* End PBXHeadersBuildPhase section */ 296 | 297 | /* Begin PBXNativeTarget section */ 298 | A14E0ABB1E1F986A00DFC788 /* HandyUIKit-iOS */ = { 299 | isa = PBXNativeTarget; 300 | buildConfigurationList = A14E0AD01E1F986A00DFC788 /* Build configuration list for PBXNativeTarget "HandyUIKit-iOS" */; 301 | buildPhases = ( 302 | A14E0AB71E1F986A00DFC788 /* Sources */, 303 | A14E0AB81E1F986A00DFC788 /* Frameworks */, 304 | A14E0AB91E1F986A00DFC788 /* Headers */, 305 | A14E0ABA1E1F986A00DFC788 /* Resources */, 306 | A1F2218C1E3FEF2800419B06 /* SwiftLint */, 307 | ); 308 | buildRules = ( 309 | ); 310 | dependencies = ( 311 | ); 312 | name = "HandyUIKit-iOS"; 313 | productName = HandyUIKit; 314 | productReference = A14E0ABC1E1F986A00DFC788 /* HandyUIKit.framework */; 315 | productType = "com.apple.product-type.framework"; 316 | }; 317 | A14E0AC41E1F986A00DFC788 /* HandyUIKit-iOS_Tests */ = { 318 | isa = PBXNativeTarget; 319 | buildConfigurationList = A14E0AD31E1F986A00DFC788 /* Build configuration list for PBXNativeTarget "HandyUIKit-iOS_Tests" */; 320 | buildPhases = ( 321 | A14E0AC11E1F986A00DFC788 /* Sources */, 322 | A14E0AC21E1F986A00DFC788 /* Frameworks */, 323 | A14E0AC31E1F986A00DFC788 /* Resources */, 324 | ); 325 | buildRules = ( 326 | ); 327 | dependencies = ( 328 | A14E0AC81E1F986A00DFC788 /* PBXTargetDependency */, 329 | ); 330 | name = "HandyUIKit-iOS_Tests"; 331 | productName = HandyUIKitTests; 332 | productReference = A14E0AC51E1F986A00DFC788 /* HandyUIKit-iOS_Tests.xctest */; 333 | productType = "com.apple.product-type.bundle.unit-test"; 334 | }; 335 | A14E0ADA1E1F987C00DFC788 /* HandyUIKit-tvOS */ = { 336 | isa = PBXNativeTarget; 337 | buildConfigurationList = A14E0AEC1E1F987C00DFC788 /* Build configuration list for PBXNativeTarget "HandyUIKit-tvOS" */; 338 | buildPhases = ( 339 | A14E0AD61E1F987C00DFC788 /* Sources */, 340 | A14E0AD71E1F987C00DFC788 /* Frameworks */, 341 | A14E0AD81E1F987C00DFC788 /* Headers */, 342 | A14E0AD91E1F987C00DFC788 /* Resources */, 343 | A1F2218A1E3FEF1300419B06 /* SwiftLint */, 344 | ); 345 | buildRules = ( 346 | ); 347 | dependencies = ( 348 | ); 349 | name = "HandyUIKit-tvOS"; 350 | productName = "HandyUIKit-tvOS"; 351 | productReference = A14E0ADB1E1F987C00DFC788 /* HandyUIKit.framework */; 352 | productType = "com.apple.product-type.framework"; 353 | }; 354 | A14E0AE21E1F987C00DFC788 /* HandyUIKit-tvOS_Tests */ = { 355 | isa = PBXNativeTarget; 356 | buildConfigurationList = A14E0AEF1E1F987C00DFC788 /* Build configuration list for PBXNativeTarget "HandyUIKit-tvOS_Tests" */; 357 | buildPhases = ( 358 | A14E0ADF1E1F987C00DFC788 /* Sources */, 359 | A14E0AE01E1F987C00DFC788 /* Frameworks */, 360 | A14E0AE11E1F987C00DFC788 /* Resources */, 361 | ); 362 | buildRules = ( 363 | ); 364 | dependencies = ( 365 | A14E0AE61E1F987C00DFC788 /* PBXTargetDependency */, 366 | ); 367 | name = "HandyUIKit-tvOS_Tests"; 368 | productName = "HandyUIKit-tvOSTests"; 369 | productReference = A14E0AE31E1F987C00DFC788 /* HandyUIKit-tvOS_Tests.xctest */; 370 | productType = "com.apple.product-type.bundle.unit-test"; 371 | }; 372 | /* End PBXNativeTarget section */ 373 | 374 | /* Begin PBXProject section */ 375 | A14E0AB31E1F986A00DFC788 /* Project object */ = { 376 | isa = PBXProject; 377 | attributes = { 378 | LastSwiftUpdateCheck = 0820; 379 | LastUpgradeCheck = 1300; 380 | ORGANIZATIONNAME = Flinesoft; 381 | TargetAttributes = { 382 | A14E0ABB1E1F986A00DFC788 = { 383 | CreatedOnToolsVersion = 8.2.1; 384 | LastSwiftMigration = 1140; 385 | ProvisioningStyle = Automatic; 386 | }; 387 | A14E0AC41E1F986A00DFC788 = { 388 | CreatedOnToolsVersion = 8.2.1; 389 | DevelopmentTeam = 767S6EFMJ8; 390 | LastSwiftMigration = 1140; 391 | ProvisioningStyle = Automatic; 392 | }; 393 | A14E0ADA1E1F987C00DFC788 = { 394 | CreatedOnToolsVersion = 8.2.1; 395 | LastSwiftMigration = 1300; 396 | ProvisioningStyle = Automatic; 397 | }; 398 | A14E0AE21E1F987C00DFC788 = { 399 | CreatedOnToolsVersion = 8.2.1; 400 | DevelopmentTeam = 767S6EFMJ8; 401 | LastSwiftMigration = 1300; 402 | ProvisioningStyle = Automatic; 403 | }; 404 | }; 405 | }; 406 | buildConfigurationList = A14E0AB61E1F986A00DFC788 /* Build configuration list for PBXProject "HandyUIKit" */; 407 | compatibilityVersion = "Xcode 3.2"; 408 | developmentRegion = en; 409 | hasScannedForEncodings = 0; 410 | knownRegions = ( 411 | en, 412 | Base, 413 | ); 414 | mainGroup = A14E0AB21E1F986A00DFC788; 415 | productRefGroup = A14E0ABD1E1F986A00DFC788 /* Products */; 416 | projectDirPath = ""; 417 | projectRoot = ""; 418 | targets = ( 419 | A14E0ABB1E1F986A00DFC788 /* HandyUIKit-iOS */, 420 | A14E0AC41E1F986A00DFC788 /* HandyUIKit-iOS_Tests */, 421 | A14E0ADA1E1F987C00DFC788 /* HandyUIKit-tvOS */, 422 | A14E0AE21E1F987C00DFC788 /* HandyUIKit-tvOS_Tests */, 423 | ); 424 | }; 425 | /* End PBXProject section */ 426 | 427 | /* Begin PBXResourcesBuildPhase section */ 428 | A14E0ABA1E1F986A00DFC788 /* Resources */ = { 429 | isa = PBXResourcesBuildPhase; 430 | buildActionMask = 2147483647; 431 | files = ( 432 | ); 433 | runOnlyForDeploymentPostprocessing = 0; 434 | }; 435 | A14E0AC31E1F986A00DFC788 /* Resources */ = { 436 | isa = PBXResourcesBuildPhase; 437 | buildActionMask = 2147483647; 438 | files = ( 439 | 2E1DABE520790F5900627CC2 /* Images.xcassets in Resources */, 440 | ); 441 | runOnlyForDeploymentPostprocessing = 0; 442 | }; 443 | A14E0AD91E1F987C00DFC788 /* Resources */ = { 444 | isa = PBXResourcesBuildPhase; 445 | buildActionMask = 2147483647; 446 | files = ( 447 | ); 448 | runOnlyForDeploymentPostprocessing = 0; 449 | }; 450 | A14E0AE11E1F987C00DFC788 /* Resources */ = { 451 | isa = PBXResourcesBuildPhase; 452 | buildActionMask = 2147483647; 453 | files = ( 454 | 2E1DABE620790F5900627CC2 /* Images.xcassets in Resources */, 455 | ); 456 | runOnlyForDeploymentPostprocessing = 0; 457 | }; 458 | /* End PBXResourcesBuildPhase section */ 459 | 460 | /* Begin PBXShellScriptBuildPhase section */ 461 | A1F2218A1E3FEF1300419B06 /* SwiftLint */ = { 462 | isa = PBXShellScriptBuildPhase; 463 | buildActionMask = 2147483647; 464 | files = ( 465 | ); 466 | inputPaths = ( 467 | ); 468 | name = SwiftLint; 469 | outputPaths = ( 470 | ); 471 | runOnlyForDeploymentPostprocessing = 0; 472 | shellPath = /bin/sh; 473 | shellScript = "if [ \"${CONFIGURATION}\" = \"Debug\" ]; then\n if which swiftlint > /dev/null; then\n swiftlint --quiet\n else\n echo \"warning: SwiftLint not installed, download it from https://github.com/realm/SwiftLint\"\n fi\nfi\n"; 474 | }; 475 | A1F2218C1E3FEF2800419B06 /* SwiftLint */ = { 476 | isa = PBXShellScriptBuildPhase; 477 | buildActionMask = 2147483647; 478 | files = ( 479 | ); 480 | inputPaths = ( 481 | ); 482 | name = SwiftLint; 483 | outputPaths = ( 484 | ); 485 | runOnlyForDeploymentPostprocessing = 0; 486 | shellPath = /bin/sh; 487 | shellScript = "if [ \"${CONFIGURATION}\" = \"Debug\" ]; then\n if which swiftlint > /dev/null; then\n swiftlint --quiet\n else\n echo \"warning: SwiftLint not installed, download it from https://github.com/realm/SwiftLint\"\n fi\nfi\n"; 488 | }; 489 | /* End PBXShellScriptBuildPhase section */ 490 | 491 | /* Begin PBXSourcesBuildPhase section */ 492 | A14E0AB71E1F986A00DFC788 /* Sources */ = { 493 | isa = PBXSourcesBuildPhase; 494 | buildActionMask = 2147483647; 495 | files = ( 496 | A1F221801E3E778F00419B06 /* ColorSpaces.swift in Sources */, 497 | A1F221681E3E07DD00419B06 /* UIColorExt.swift in Sources */, 498 | 8242F0A72189FE410075FDF3 /* CGPointExt.swift in Sources */, 499 | 8242F0A42189FE230075FDF3 /* CGSizeExt.swift in Sources */, 500 | 2E71660A207904A200135F39 /* UIImageExt.swift in Sources */, 501 | C5C8A2482181F7BE00D12F8E /* UITableViewExt.swift in Sources */, 502 | 8218E4E12211D972007AAAF3 /* UIWindowExt.swift in Sources */, 503 | A11830F31E5996FD00CBE087 /* StringExt.swift in Sources */, 504 | 8218E4CF2211BEA8007AAAF3 /* TemplateImageView.swift in Sources */, 505 | 8218E4C92211BE3B007AAAF3 /* RoundableView.swift in Sources */, 506 | 8218E4D22211BEDC007AAAF3 /* NibLoadable.swift in Sources */, 507 | A1D4453C1E59C9370014A250 /* NSAttributedStringExt.swift in Sources */, 508 | A14E0B111E1F9B3100DFC788 /* UIViewExt.swift in Sources */, 509 | 8242F0AA2189FE500075FDF3 /* CGRectExt.swift in Sources */, 510 | 8218E4CC2211BE8A007AAAF3 /* TemplateButton.swift in Sources */, 511 | ); 512 | runOnlyForDeploymentPostprocessing = 0; 513 | }; 514 | A14E0AC11E1F986A00DFC788 /* Sources */ = { 515 | isa = PBXSourcesBuildPhase; 516 | buildActionMask = 2147483647; 517 | files = ( 518 | A1F2217C1E3E2C5500419B06 /* CoreGraphicsExtTests.swift in Sources */, 519 | A1F221701E3E0B8400419B06 /* ColorExtTests.swift in Sources */, 520 | A11830F61E599BF600CBE087 /* StringExtTests.swift in Sources */, 521 | 2E1DABDE20790C1700627CC2 /* UIImageExtTests.swift in Sources */, 522 | ); 523 | runOnlyForDeploymentPostprocessing = 0; 524 | }; 525 | A14E0AD61E1F987C00DFC788 /* Sources */ = { 526 | isa = PBXSourcesBuildPhase; 527 | buildActionMask = 2147483647; 528 | files = ( 529 | A1F221811E3E778F00419B06 /* ColorSpaces.swift in Sources */, 530 | A1F221691E3E07DD00419B06 /* UIColorExt.swift in Sources */, 531 | 8242F0A82189FE410075FDF3 /* CGPointExt.swift in Sources */, 532 | 8242F0A52189FE230075FDF3 /* CGSizeExt.swift in Sources */, 533 | 2E71660B207904A200135F39 /* UIImageExt.swift in Sources */, 534 | C5C8A2492181F7BE00D12F8E /* UITableViewExt.swift in Sources */, 535 | 8218E4E22211D972007AAAF3 /* UIWindowExt.swift in Sources */, 536 | A11830F41E5996FD00CBE087 /* StringExt.swift in Sources */, 537 | 8218E4D02211BEA8007AAAF3 /* TemplateImageView.swift in Sources */, 538 | 8218E4CA2211BE3B007AAAF3 /* RoundableView.swift in Sources */, 539 | 8218E4D32211BEDC007AAAF3 /* NibLoadable.swift in Sources */, 540 | A1D4453D1E59C9370014A250 /* NSAttributedStringExt.swift in Sources */, 541 | A14E0B121E1F9B3100DFC788 /* UIViewExt.swift in Sources */, 542 | 8242F0AB2189FE500075FDF3 /* CGRectExt.swift in Sources */, 543 | 8218E4CD2211BE8A007AAAF3 /* TemplateButton.swift in Sources */, 544 | ); 545 | runOnlyForDeploymentPostprocessing = 0; 546 | }; 547 | A14E0ADF1E1F987C00DFC788 /* Sources */ = { 548 | isa = PBXSourcesBuildPhase; 549 | buildActionMask = 2147483647; 550 | files = ( 551 | A1F2217D1E3E2C5500419B06 /* CoreGraphicsExtTests.swift in Sources */, 552 | A1F221711E3E0B8500419B06 /* ColorExtTests.swift in Sources */, 553 | A11830F71E599BF600CBE087 /* StringExtTests.swift in Sources */, 554 | 2E1DABDF20790C1700627CC2 /* UIImageExtTests.swift in Sources */, 555 | ); 556 | runOnlyForDeploymentPostprocessing = 0; 557 | }; 558 | /* End PBXSourcesBuildPhase section */ 559 | 560 | /* Begin PBXTargetDependency section */ 561 | A14E0AC81E1F986A00DFC788 /* PBXTargetDependency */ = { 562 | isa = PBXTargetDependency; 563 | target = A14E0ABB1E1F986A00DFC788 /* HandyUIKit-iOS */; 564 | targetProxy = A14E0AC71E1F986A00DFC788 /* PBXContainerItemProxy */; 565 | }; 566 | A14E0AE61E1F987C00DFC788 /* PBXTargetDependency */ = { 567 | isa = PBXTargetDependency; 568 | target = A14E0ADA1E1F987C00DFC788 /* HandyUIKit-tvOS */; 569 | targetProxy = A14E0AE51E1F987C00DFC788 /* PBXContainerItemProxy */; 570 | }; 571 | /* End PBXTargetDependency section */ 572 | 573 | /* Begin XCBuildConfiguration section */ 574 | A14E0ACE1E1F986A00DFC788 /* Debug */ = { 575 | isa = XCBuildConfiguration; 576 | buildSettings = { 577 | ALWAYS_SEARCH_USER_PATHS = NO; 578 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 579 | CLANG_ANALYZER_NONNULL = YES; 580 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 581 | CLANG_CXX_LIBRARY = "libc++"; 582 | CLANG_ENABLE_MODULES = YES; 583 | CLANG_ENABLE_OBJC_ARC = YES; 584 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 585 | CLANG_WARN_BOOL_CONVERSION = YES; 586 | CLANG_WARN_COMMA = YES; 587 | CLANG_WARN_CONSTANT_CONVERSION = YES; 588 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 589 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 590 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 591 | CLANG_WARN_EMPTY_BODY = YES; 592 | CLANG_WARN_ENUM_CONVERSION = YES; 593 | CLANG_WARN_INFINITE_RECURSION = YES; 594 | CLANG_WARN_INT_CONVERSION = YES; 595 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 596 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 597 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 598 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 599 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 600 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 601 | CLANG_WARN_STRICT_PROTOTYPES = YES; 602 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 603 | CLANG_WARN_UNREACHABLE_CODE = YES; 604 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 605 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 606 | COPY_PHASE_STRIP = NO; 607 | CURRENT_PROJECT_VERSION = 1; 608 | DEBUG_INFORMATION_FORMAT = dwarf; 609 | ENABLE_STRICT_OBJC_MSGSEND = YES; 610 | ENABLE_TESTABILITY = YES; 611 | GCC_C_LANGUAGE_STANDARD = gnu99; 612 | GCC_DYNAMIC_NO_PIC = NO; 613 | GCC_NO_COMMON_BLOCKS = YES; 614 | GCC_OPTIMIZATION_LEVEL = 0; 615 | GCC_PREPROCESSOR_DEFINITIONS = ( 616 | "DEBUG=1", 617 | "$(inherited)", 618 | ); 619 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 620 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 621 | GCC_WARN_UNDECLARED_SELECTOR = YES; 622 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 623 | GCC_WARN_UNUSED_FUNCTION = YES; 624 | GCC_WARN_UNUSED_VARIABLE = YES; 625 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 626 | MTL_ENABLE_DEBUG_INFO = YES; 627 | ONLY_ACTIVE_ARCH = YES; 628 | SDKROOT = iphoneos; 629 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 630 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 631 | SWIFT_SWIFT3_OBJC_INFERENCE = Off; 632 | SWIFT_VERSION = 4.2; 633 | VERSIONING_SYSTEM = "apple-generic"; 634 | VERSION_INFO_PREFIX = ""; 635 | }; 636 | name = Debug; 637 | }; 638 | A14E0ACF1E1F986A00DFC788 /* Release */ = { 639 | isa = XCBuildConfiguration; 640 | buildSettings = { 641 | ALWAYS_SEARCH_USER_PATHS = NO; 642 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 643 | CLANG_ANALYZER_NONNULL = YES; 644 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 645 | CLANG_CXX_LIBRARY = "libc++"; 646 | CLANG_ENABLE_MODULES = YES; 647 | CLANG_ENABLE_OBJC_ARC = YES; 648 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 649 | CLANG_WARN_BOOL_CONVERSION = YES; 650 | CLANG_WARN_COMMA = YES; 651 | CLANG_WARN_CONSTANT_CONVERSION = YES; 652 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 653 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 654 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 655 | CLANG_WARN_EMPTY_BODY = YES; 656 | CLANG_WARN_ENUM_CONVERSION = YES; 657 | CLANG_WARN_INFINITE_RECURSION = YES; 658 | CLANG_WARN_INT_CONVERSION = YES; 659 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 660 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 661 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 662 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 663 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 664 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 665 | CLANG_WARN_STRICT_PROTOTYPES = YES; 666 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 667 | CLANG_WARN_UNREACHABLE_CODE = YES; 668 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 669 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 670 | COPY_PHASE_STRIP = NO; 671 | CURRENT_PROJECT_VERSION = 1; 672 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 673 | ENABLE_NS_ASSERTIONS = NO; 674 | ENABLE_STRICT_OBJC_MSGSEND = YES; 675 | GCC_C_LANGUAGE_STANDARD = gnu99; 676 | GCC_NO_COMMON_BLOCKS = YES; 677 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 678 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 679 | GCC_WARN_UNDECLARED_SELECTOR = YES; 680 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 681 | GCC_WARN_UNUSED_FUNCTION = YES; 682 | GCC_WARN_UNUSED_VARIABLE = YES; 683 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 684 | MTL_ENABLE_DEBUG_INFO = NO; 685 | SDKROOT = iphoneos; 686 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 687 | SWIFT_SWIFT3_OBJC_INFERENCE = Off; 688 | SWIFT_VERSION = 4.2; 689 | VALIDATE_PRODUCT = YES; 690 | VERSIONING_SYSTEM = "apple-generic"; 691 | VERSION_INFO_PREFIX = ""; 692 | }; 693 | name = Release; 694 | }; 695 | A14E0AD11E1F986A00DFC788 /* Debug */ = { 696 | isa = XCBuildConfiguration; 697 | buildSettings = { 698 | CLANG_ENABLE_MODULES = YES; 699 | CODE_SIGN_IDENTITY = ""; 700 | DEFINES_MODULE = YES; 701 | DEVELOPMENT_TEAM = ""; 702 | DYLIB_COMPATIBILITY_VERSION = 1; 703 | DYLIB_CURRENT_VERSION = 1; 704 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 705 | INFOPLIST_FILE = Frameworks/SupportingFiles/Info.plist; 706 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 707 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 708 | OTHER_SWIFT_FLAGS = "-D IOS"; 709 | PRODUCT_BUNDLE_IDENTIFIER = com.flinesoft.HandyUIKit; 710 | PRODUCT_NAME = HandyUIKit; 711 | SKIP_INSTALL = YES; 712 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 713 | SWIFT_VERSION = 5.0; 714 | TARGETED_DEVICE_FAMILY = "1,2"; 715 | }; 716 | name = Debug; 717 | }; 718 | A14E0AD21E1F986A00DFC788 /* Release */ = { 719 | isa = XCBuildConfiguration; 720 | buildSettings = { 721 | CLANG_ENABLE_MODULES = YES; 722 | CODE_SIGN_IDENTITY = ""; 723 | DEFINES_MODULE = YES; 724 | DEVELOPMENT_TEAM = ""; 725 | DYLIB_COMPATIBILITY_VERSION = 1; 726 | DYLIB_CURRENT_VERSION = 1; 727 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 728 | INFOPLIST_FILE = Frameworks/SupportingFiles/Info.plist; 729 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 730 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 731 | OTHER_SWIFT_FLAGS = "-D IOS"; 732 | PRODUCT_BUNDLE_IDENTIFIER = com.flinesoft.HandyUIKit; 733 | PRODUCT_NAME = HandyUIKit; 734 | SKIP_INSTALL = YES; 735 | SWIFT_VERSION = 5.0; 736 | TARGETED_DEVICE_FAMILY = "1,2"; 737 | }; 738 | name = Release; 739 | }; 740 | A14E0AD41E1F986A00DFC788 /* Debug */ = { 741 | isa = XCBuildConfiguration; 742 | buildSettings = { 743 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 744 | DEVELOPMENT_TEAM = 767S6EFMJ8; 745 | INFOPLIST_FILE = Tests/SupportingFiles/Info.plist; 746 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 747 | PRODUCT_BUNDLE_IDENTIFIER = com.flinesoft.HandyUIKitTests; 748 | PRODUCT_NAME = "$(TARGET_NAME)"; 749 | SWIFT_VERSION = 5.0; 750 | TARGETED_DEVICE_FAMILY = "1,2"; 751 | }; 752 | name = Debug; 753 | }; 754 | A14E0AD51E1F986A00DFC788 /* Release */ = { 755 | isa = XCBuildConfiguration; 756 | buildSettings = { 757 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 758 | DEVELOPMENT_TEAM = 767S6EFMJ8; 759 | INFOPLIST_FILE = Tests/SupportingFiles/Info.plist; 760 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 761 | PRODUCT_BUNDLE_IDENTIFIER = com.flinesoft.HandyUIKitTests; 762 | PRODUCT_NAME = "$(TARGET_NAME)"; 763 | SWIFT_VERSION = 5.0; 764 | TARGETED_DEVICE_FAMILY = "1,2"; 765 | }; 766 | name = Release; 767 | }; 768 | A14E0AED1E1F987C00DFC788 /* Debug */ = { 769 | isa = XCBuildConfiguration; 770 | buildSettings = { 771 | CLANG_ENABLE_MODULES = YES; 772 | CODE_SIGN_IDENTITY = ""; 773 | DEFINES_MODULE = YES; 774 | DEVELOPMENT_TEAM = ""; 775 | DYLIB_COMPATIBILITY_VERSION = 1; 776 | DYLIB_CURRENT_VERSION = 1; 777 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 778 | INFOPLIST_FILE = Frameworks/SupportingFiles/Info.plist; 779 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 780 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 781 | PRODUCT_BUNDLE_IDENTIFIER = com.flinesoft.HandyUIKit; 782 | PRODUCT_NAME = HandyUIKit; 783 | SDKROOT = appletvos; 784 | SKIP_INSTALL = YES; 785 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 786 | SWIFT_VERSION = 5.0; 787 | TARGETED_DEVICE_FAMILY = 3; 788 | TVOS_DEPLOYMENT_TARGET = 9.0; 789 | }; 790 | name = Debug; 791 | }; 792 | A14E0AEE1E1F987C00DFC788 /* Release */ = { 793 | isa = XCBuildConfiguration; 794 | buildSettings = { 795 | CLANG_ENABLE_MODULES = YES; 796 | CODE_SIGN_IDENTITY = ""; 797 | DEFINES_MODULE = YES; 798 | DEVELOPMENT_TEAM = ""; 799 | DYLIB_COMPATIBILITY_VERSION = 1; 800 | DYLIB_CURRENT_VERSION = 1; 801 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 802 | INFOPLIST_FILE = Frameworks/SupportingFiles/Info.plist; 803 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 804 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 805 | PRODUCT_BUNDLE_IDENTIFIER = com.flinesoft.HandyUIKit; 806 | PRODUCT_NAME = HandyUIKit; 807 | SDKROOT = appletvos; 808 | SKIP_INSTALL = YES; 809 | SWIFT_VERSION = 5.0; 810 | TARGETED_DEVICE_FAMILY = 3; 811 | TVOS_DEPLOYMENT_TARGET = 9.0; 812 | }; 813 | name = Release; 814 | }; 815 | A14E0AF01E1F987C00DFC788 /* Debug */ = { 816 | isa = XCBuildConfiguration; 817 | buildSettings = { 818 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 819 | DEVELOPMENT_TEAM = 767S6EFMJ8; 820 | INFOPLIST_FILE = Tests/SupportingFiles/Info.plist; 821 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 822 | PRODUCT_BUNDLE_IDENTIFIER = "com.flinesoft.HandyUIKit-tvOSTests"; 823 | PRODUCT_NAME = "$(TARGET_NAME)"; 824 | SDKROOT = appletvos; 825 | SWIFT_VERSION = 5.0; 826 | TARGETED_DEVICE_FAMILY = 3; 827 | TVOS_DEPLOYMENT_TARGET = 12.0; 828 | }; 829 | name = Debug; 830 | }; 831 | A14E0AF11E1F987C00DFC788 /* Release */ = { 832 | isa = XCBuildConfiguration; 833 | buildSettings = { 834 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 835 | DEVELOPMENT_TEAM = 767S6EFMJ8; 836 | INFOPLIST_FILE = Tests/SupportingFiles/Info.plist; 837 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 838 | PRODUCT_BUNDLE_IDENTIFIER = "com.flinesoft.HandyUIKit-tvOSTests"; 839 | PRODUCT_NAME = "$(TARGET_NAME)"; 840 | SDKROOT = appletvos; 841 | SWIFT_VERSION = 5.0; 842 | TARGETED_DEVICE_FAMILY = 3; 843 | TVOS_DEPLOYMENT_TARGET = 12.0; 844 | }; 845 | name = Release; 846 | }; 847 | /* End XCBuildConfiguration section */ 848 | 849 | /* Begin XCConfigurationList section */ 850 | A14E0AB61E1F986A00DFC788 /* Build configuration list for PBXProject "HandyUIKit" */ = { 851 | isa = XCConfigurationList; 852 | buildConfigurations = ( 853 | A14E0ACE1E1F986A00DFC788 /* Debug */, 854 | A14E0ACF1E1F986A00DFC788 /* Release */, 855 | ); 856 | defaultConfigurationIsVisible = 0; 857 | defaultConfigurationName = Release; 858 | }; 859 | A14E0AD01E1F986A00DFC788 /* Build configuration list for PBXNativeTarget "HandyUIKit-iOS" */ = { 860 | isa = XCConfigurationList; 861 | buildConfigurations = ( 862 | A14E0AD11E1F986A00DFC788 /* Debug */, 863 | A14E0AD21E1F986A00DFC788 /* Release */, 864 | ); 865 | defaultConfigurationIsVisible = 0; 866 | defaultConfigurationName = Release; 867 | }; 868 | A14E0AD31E1F986A00DFC788 /* Build configuration list for PBXNativeTarget "HandyUIKit-iOS_Tests" */ = { 869 | isa = XCConfigurationList; 870 | buildConfigurations = ( 871 | A14E0AD41E1F986A00DFC788 /* Debug */, 872 | A14E0AD51E1F986A00DFC788 /* Release */, 873 | ); 874 | defaultConfigurationIsVisible = 0; 875 | defaultConfigurationName = Release; 876 | }; 877 | A14E0AEC1E1F987C00DFC788 /* Build configuration list for PBXNativeTarget "HandyUIKit-tvOS" */ = { 878 | isa = XCConfigurationList; 879 | buildConfigurations = ( 880 | A14E0AED1E1F987C00DFC788 /* Debug */, 881 | A14E0AEE1E1F987C00DFC788 /* Release */, 882 | ); 883 | defaultConfigurationIsVisible = 0; 884 | defaultConfigurationName = Release; 885 | }; 886 | A14E0AEF1E1F987C00DFC788 /* Build configuration list for PBXNativeTarget "HandyUIKit-tvOS_Tests" */ = { 887 | isa = XCConfigurationList; 888 | buildConfigurations = ( 889 | A14E0AF01E1F987C00DFC788 /* Debug */, 890 | A14E0AF11E1F987C00DFC788 /* Release */, 891 | ); 892 | defaultConfigurationIsVisible = 0; 893 | defaultConfigurationName = Release; 894 | }; 895 | /* End XCConfigurationList section */ 896 | }; 897 | rootObject = A14E0AB31E1F986A00DFC788 /* Project object */; 898 | } 899 | --------------------------------------------------------------------------------