diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e06c8f1..9950da5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +### 0.12.0 +- **Add**: StatefulButton & RoundedStatefulButton to TIUIElements. +- **Add**: added CACornerMask rounding extension to TIUIElements. +- **Add**: UIControl.State dictionary extensions to TIUIKitCore. +- **Add**: UIFont and CTFont extensions to TIUIKitCore. +- **Breaking change**: reworked BaseTextAttributes & ViewText. Removed ViewTextConfigurable protocol & conformances. + ### 0.11.0 - **Add**: Cocoapods support for TI-family libraries. - **Add**: `SeparatorConfigurable` and all helper types for separator configuration. diff --git a/Package.swift b/Package.swift index 7e976793..3489e4a0 100644 --- a/Package.swift +++ b/Package.swift @@ -23,7 +23,7 @@ let package = Package( .target(name: "TIUIKitCore", path: "TIUIKitCore/Sources"), .target(name: "TISwiftUtils", path: "TISwiftUtils/Sources"), .target(name: "TIFoundationUtils", dependencies: ["TISwiftUtils"], path: "TIFoundationUtils/Sources"), - .target(name: "TIUIElements", dependencies: ["TIUIKitCore"], path: "TIUIElements/Sources"), + .target(name: "TIUIElements", dependencies: ["TIUIKitCore", "TISwiftUtils"], path: "TIUIElements/Sources"), .target(name: "TITableKitUtils", dependencies: ["TIUIElements", "TableKit"], path: "TITableKitUtils/Sources"), .target(name: "OTPSwiftView", dependencies: ["TIUIKitCore", "TISwiftUtils"], path: "OTPSwiftView/Sources") ] diff --git a/TIFoundationUtils/TIFoundationUtils.podspec b/TIFoundationUtils/TIFoundationUtils.podspec index 384ae080..4ac6b85e 100644 --- a/TIFoundationUtils/TIFoundationUtils.podspec +++ b/TIFoundationUtils/TIFoundationUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIFoundationUtils' - s.version = '0.11.0' + s.version = '0.12.0' s.summary = 'Set of helpers for Foundation framework classes.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TISwiftUtils/TISwiftUtils.podspec b/TISwiftUtils/TISwiftUtils.podspec index 4a256658..d24ceb09 100644 --- a/TISwiftUtils/TISwiftUtils.podspec +++ b/TISwiftUtils/TISwiftUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TISwiftUtils' - s.version = '0.11.0' + s.version = '0.12.0' s.summary = 'Bunch of useful helpers for Swift development.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TITableKitUtils/TITableKitUtils.podspec b/TITableKitUtils/TITableKitUtils.podspec index 3cacb356..9ac729c2 100644 --- a/TITableKitUtils/TITableKitUtils.podspec +++ b/TITableKitUtils/TITableKitUtils.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TITableKitUtils' - s.version = '0.11.0' + s.version = '0.12.0' s.summary = 'Set of helpers for TableKit classes.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/TIUIKitCore/Sources/ViewText/ViewTextConfigurable/UITextField+ViewTextConfigurable.swift b/TIUIElements/Sources/Extensions/CACornerMask/CACornerMask+Extensions.swift similarity index 73% rename from TIUIKitCore/Sources/ViewText/ViewTextConfigurable/UITextField+ViewTextConfigurable.swift rename to TIUIElements/Sources/Extensions/CACornerMask/CACornerMask+Extensions.swift index eaa2ce79..ef0f4f2f 100644 --- a/TIUIKitCore/Sources/ViewText/ViewTextConfigurable/UITextField+ViewTextConfigurable.swift +++ b/TIUIElements/Sources/Extensions/CACornerMask/CACornerMask+Extensions.swift @@ -20,25 +20,23 @@ // THE SOFTWARE. // -import UIKit.UITextField +import QuartzCore -extension UITextField: ViewTextConfigurable { - - public var textFont: UIFont? { - get { - return font - } - set { - font = newValue - } +public extension CACornerMask { + static var topCorners: Self { + [.layerMinXMinYCorner, .layerMaxXMinYCorner] } - public var titleColor: UIColor? { - get { - return textColor - } - set { - textColor = newValue - } + static var bottomCorners: Self { + [.layerMinXMaxYCorner, .layerMaxXMaxYCorner] + } + + static var allCorners: Self { + [ + .layerMinXMinYCorner, + .layerMaxXMinYCorner, + .layerMinXMaxYCorner, + .layerMaxXMaxYCorner + ] } } diff --git a/TIUIKitCore/Sources/ViewText/ViewTextConfigurable/UILabel+ViewTextConfigurable.swift b/TIUIElements/Sources/Extensions/CALayer/CALayer+RoundCorners.swift similarity index 76% rename from TIUIKitCore/Sources/ViewText/ViewTextConfigurable/UILabel+ViewTextConfigurable.swift rename to TIUIElements/Sources/Extensions/CALayer/CALayer+RoundCorners.swift index 8418ffa4..37126d9b 100644 --- a/TIUIKitCore/Sources/ViewText/ViewTextConfigurable/UILabel+ViewTextConfigurable.swift +++ b/TIUIElements/Sources/Extensions/CALayer/CALayer+RoundCorners.swift @@ -20,25 +20,17 @@ // THE SOFTWARE. // -import UIKit.UILabel +import QuartzCore -extension UILabel: ViewTextConfigurable { - - public var textFont: UIFont? { - get { - return font - } - set { - font = newValue - } +public extension CALayer { + func round(corners: CACornerMask) { + masksToBounds = true + maskedCorners = corners } - public var titleColor: UIColor? { - get { - return textColor - } - set { - textColor = newValue - } + func round(corners: CACornerMask, radius: CGFloat) { + round(corners: corners) + + cornerRadius = radius } } diff --git a/TIUIElements/Sources/Separators/BaseSeparatorCell.swift b/TIUIElements/Sources/Separators/BaseSeparatorCell.swift index 003f7dd0..fe07b3f5 100644 --- a/TIUIElements/Sources/Separators/BaseSeparatorCell.swift +++ b/TIUIElements/Sources/Separators/BaseSeparatorCell.swift @@ -74,6 +74,7 @@ open class BaseSeparatorCell: BaseInitializableCell, SeparatorConfigurable { open override func prepareForReuse() { super.prepareForReuse() + configureSeparators(with: .none) } @@ -131,17 +132,21 @@ open class BaseSeparatorCell: BaseInitializableCell, SeparatorConfigurable { private extension BaseSeparatorCell { func updateTopSeparator(with configuration: SeparatorConfiguration) { topSeparatorView.backgroundColor = configuration.color + topViewHeightConstraint?.constant = configuration.height + topViewTopConstraint?.constant = configuration.insets.top topViewLeftConstraint?.constant = configuration.insets.left - topViewRightConstraint?.constant = -configuration.insets.right + topViewRightConstraint?.constant = configuration.insets.right } func updateBottomSeparator(with configuration: SeparatorConfiguration) { bottomSeparatorView.backgroundColor = configuration.color + bottomViewHeightConstraint?.constant = configuration.height - bottomViewBottomConstraint?.constant = -configuration.insets.bottom + + bottomViewBottomConstraint?.constant = configuration.insets.bottom bottomViewLeftConstraint?.constant = configuration.insets.left - bottomViewRightConstraint?.constant = -configuration.insets.right + bottomViewRightConstraint?.constant = configuration.insets.right } } diff --git a/TIUIElements/Sources/Views/BaseInitializableControl.swift b/TIUIElements/Sources/Views/BaseInitializableControl.swift index 7d852d5d..aef5ec83 100644 --- a/TIUIElements/Sources/Views/BaseInitializableControl.swift +++ b/TIUIElements/Sources/Views/BaseInitializableControl.swift @@ -1,3 +1,25 @@ +// +// Copyright (c) 2020 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + import UIKit import TIUIKitCore diff --git a/TIUIElements/Sources/Views/StatefulButton/RoundedStatefulButton.swift b/TIUIElements/Sources/Views/StatefulButton/RoundedStatefulButton.swift new file mode 100644 index 00000000..9d9cb5e0 --- /dev/null +++ b/TIUIElements/Sources/Views/StatefulButton/RoundedStatefulButton.swift @@ -0,0 +1,56 @@ +// +// Copyright (c) 2020 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import UIKit + +open class RoundedStatefulButton: StatefulButton { + + // UIView override + + override public init(frame: CGRect) { + super.init(frame: frame) + + configureAppearance() + } + + required public init?(coder: NSCoder) { + super.init(coder: coder) + + configureAppearance() + } + + open override func layoutSubviews() { + super.layoutSubviews() + + layer.cornerRadius = calculateCornerRadius(for: bounds) + } + + // MARK: - Open methods + + open func configureAppearance() { + layer.round(corners: .allCorners) + } + + open func calculateCornerRadius(for bounds: CGRect) -> CGFloat { + min(bounds.width, bounds.height) / 2 + } +} diff --git a/TIUIElements/Sources/Views/StatefulButton/StatefulButton.swift b/TIUIElements/Sources/Views/StatefulButton/StatefulButton.swift new file mode 100644 index 00000000..eb171bac --- /dev/null +++ b/TIUIElements/Sources/Views/StatefulButton/StatefulButton.swift @@ -0,0 +1,229 @@ +// +// Copyright (c) 2020 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import TISwiftUtils +import TIUIKitCore +import UIKit + +open class StatefulButton: UIButton { + + public enum ActivityIndicatorPosition { + case center + case before(view: UIView, offset: CGFloat) + case after(view: UIView, offset: CGFloat) + + var offset: CGFloat { + switch self { + case .center: + return .zero + + case let .before(_, offset), let .after(_, offset): + return offset + } + } + } + + public typealias StateEventPropagations = [State: Bool] + + private var activityIndicator: ActivityIndicator? { + willSet { + activityIndicator?.removeFromSuperview() + } + didSet { + if let activityIndicator = activityIndicator { + addSubview(activityIndicator) + } + } + } + + public var isLoading = false { + didSet { + isLoading + ? activityIndicator?.startAnimating() + : activityIndicator?.stopAnimating() + + isEnabled = !isLoading + } + } + + public var additionalHitTestMargins: UIEdgeInsets = .zero + + public var onDisabledStateTapHandler: VoidClosure? + + var eventPropagations: StateEventPropagations = [:] + + // MARK: - Background + + private var backgroundColors: StateColors = [:] { + didSet { + updateBackgroundColor() + } + } + + public func set(backgroundColors: StateColors) { + backgroundColors.forEach { setBackgroundColor($1, for: $0) } + } + + public func setBackgroundColor(_ color: UIColor?, for state: State) { + backgroundColors[state] = color + } + + public func backgroundColor(for state: State) -> UIColor? { + // Value of optional type 'UIColor??' must be unwrapped to a value of type 'UIColor?' + // 🤷 Swift 5.3 (Xcode 12.2) + backgroundColors[state] ?? nil //swiftlint:disable:this redundant_nil_coalescing + } + + public func setEventPropagation(_ eventPropagation: Bool, for state: State) { + eventPropagations[state] = eventPropagation + } + + // MARK: - UIControl override + + override open var isEnabled: Bool { + didSet { + updateBackgroundColor() + } + } + + override open var isHighlighted: Bool { + didSet { + updateBackgroundColor() + } + } + + open override var isSelected: Bool { + didSet { + updateBackgroundColor() + } + } + + // MARK: - UIView override + + open override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + let insetBounds = CGRect(x: bounds.minX - additionalHitTestMargins.left, + y: bounds.minY - additionalHitTestMargins.top, + width: bounds.width + additionalHitTestMargins.right, + height: bounds.height + additionalHitTestMargins.bottom) + + return super.point(inside: point, with: event) + || insetBounds.contains(point) + } + + open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + let pointInsideView = self.point(inside: point, with: event) + + // hitTest called multiple times, but we need only one + // so the current solution is to pick a final call with nil event + + if !isEnabled && pointInsideView && event == nil { + onDisabledStateTapHandler?() + } + + let touchEventReceiver = super.hitTest(point, with: event) + + let shouldPropagateEvent = eventPropagations[state] ?? true + + if pointInsideView && touchEventReceiver == nil && !shouldPropagateEvent { + return self // disable propagation + } + + return touchEventReceiver + } + + // MARK: - Public + + public func configure(activityIndicator: ActivityIndicator, + at position: ActivityIndicatorPosition) { + + self.activityIndicator = activityIndicator + + let titleInset = activityIndicator.intrinsicContentSize.width + position.offset + + switch position { + case .center: + titleEdgeInsets = .zero + + case .before: + titleEdgeInsets = UIEdgeInsets(top: .zero, + left: titleInset, + bottom: .zero, + right: .zero) + + case .after: + titleEdgeInsets = UIEdgeInsets(top: .zero, + left: .zero, + bottom: .zero, + right: titleInset) + } + + activityIndicator.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate(constraints(for: activityIndicator, + at: position)) + } + + private func constraints(for activityIndicator: ActivityIndicator, + at position: ActivityIndicatorPosition) -> [NSLayoutConstraint] { + switch position { + case .center: + return [ + activityIndicator.centerXAnchor.constraint(equalTo: centerXAnchor), + activityIndicator.centerYAnchor.constraint(equalTo: centerYAnchor) + ] + + case let .before(view, offset): + return [ + activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor), + activityIndicator.trailingAnchor.constraint(equalTo: view.leadingAnchor, constant: -offset) + ] + + case let .after(view, offset): + return [ + activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor), + activityIndicator.leadingAnchor.constraint(equalTo: view.trailingAnchor, constant: offset) + ] + } + } + + // MARK: - Private + + private func updateBackgroundColor() { + if isEnabled { + if isHighlighted { + updateBackgroundColor(to: .highlighted) + } else { + updateBackgroundColor(to: .normal) + } + } else { + updateBackgroundColor(to: .disabled) + } + } + + private func updateBackgroundColor(to state: State) { + if let stateColor = backgroundColor(for: state) { + backgroundColor = stateColor + } else if state != .normal, let normalStateColor = backgroundColor(for: .normal) { + backgroundColor = normalStateColor + } + } +} diff --git a/TIUIElements/TIUIElements.podspec b/TIUIElements/TIUIElements.podspec index 74097f9d..b1883afb 100644 --- a/TIUIElements/TIUIElements.podspec +++ b/TIUIElements/TIUIElements.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIUIElements' - s.version = '0.11.0' + s.version = '0.12.0' s.summary = 'Bunch of useful protocols and views.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' } @@ -13,4 +13,5 @@ Pod::Spec.new do |s| s.source_files = s.name + '/Sources/**/*' s.dependency 'TIUIKitCore', s.version.to_s + s.dependency 'TISwiftUtils', s.version.to_s end diff --git a/TIUIKitCore/Sources/Extensions/CTFont/CTFont+FontRegistration.swift b/TIUIKitCore/Sources/Extensions/CTFont/CTFont+FontRegistration.swift new file mode 100644 index 00000000..848b3451 --- /dev/null +++ b/TIUIKitCore/Sources/Extensions/CTFont/CTFont+FontRegistration.swift @@ -0,0 +1,69 @@ +// +// Copyright (c) 2020 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import CoreGraphics +import CoreText +import Foundation + +public extension CTFont { + enum FontRegistrationError: Error { + case fontsNotFound + case unableToCreateCGFontFromData + case unableToRegisterFont(CFError) + case unknown + } + + static func registerFont(at url: URL) throws { + let fontData = try Data(contentsOf: url) + + guard let dataProvider = CGDataProvider(data: fontData as CFData), + let font = CGFont(dataProvider) else { + throw FontRegistrationError.unableToCreateCGFontFromData + } + + var errorRef: Unmanaged? + let registrationWasSuccessful = CTFontManagerRegisterGraphicsFont(font, &errorRef) + + if registrationWasSuccessful { + return + } else if let error = errorRef?.takeRetainedValue() { + if CFErrorGetCode(error) != CTFontManagerError.alreadyRegistered.rawValue { + throw FontRegistrationError.unableToRegisterFont(error) + } else { + return // it's okay, font is already registered + } + } else { + throw FontRegistrationError.unknown + } + } + + static func registerAllFonts(in bundle: Bundle) throws { + guard let fontUrls = bundle.urls(forResourcesWithExtension: "woff2", subdirectory: nil), + !fontUrls.isEmpty else { + throw FontRegistrationError.fontsNotFound + } + + try fontUrls.forEach { + try registerFont(at: $0) + } + } +} diff --git a/TIUIKitCore/Sources/Extensions/UIButton/UIButton+StateConfiguration.swift b/TIUIKitCore/Sources/Extensions/UIButton/UIButton+StateConfiguration.swift new file mode 100644 index 00000000..45a14cd4 --- /dev/null +++ b/TIUIKitCore/Sources/Extensions/UIButton/UIButton+StateConfiguration.swift @@ -0,0 +1,47 @@ +// +// Copyright (c) 2020 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import UIKit.UIButton + +public extension UIButton { + func set(titleColors: StateColors) { + titleColors.forEach { setTitleColor($1, for: $0) } + } + + func set(titles: StateTitles) { + titles.forEach { setTitle($1, for: $0) } + } + + func set(attributtedTitles: StateAttributedTitles) { + attributtedTitles.forEach { setAttributedTitle($1, for: $0) } + } + + // MARK: - Images + + func set(images: StateImages) { + images.forEach { setImage($1, for: $0) } + } + + func set(backgroundImages: StateImages) { + backgroundImages.forEach { setBackgroundImage($1, for: $0) } + } +} diff --git a/TIUIKitCore/Sources/Extensions/UIControlState/UIControlState+Dictionary.swift b/TIUIKitCore/Sources/Extensions/UIControlState/UIControlState+Dictionary.swift new file mode 100644 index 00000000..c83b00fe --- /dev/null +++ b/TIUIKitCore/Sources/Extensions/UIControlState/UIControlState+Dictionary.swift @@ -0,0 +1,36 @@ +// +// Copyright (c) 2020 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import UIKit.UIControl + +extension UIControl.State: Hashable { + public func hash(into hasher: inout Hasher) { + hasher.combine(Int(rawValue)) + } +} + +public extension UIControl { + typealias StateColors = [UIControl.State: UIColor?] + typealias StateImages = [UIControl.State: UIImage?] + typealias StateTitles = [UIControl.State: String?] + typealias StateAttributedTitles = [UIControl.State: NSAttributedString?] +} diff --git a/TIUIKitCore/Sources/Extensions/UIFont/UIFont+FontDescriptor.swift b/TIUIKitCore/Sources/Extensions/UIFont/UIFont+FontDescriptor.swift new file mode 100644 index 00000000..09864092 --- /dev/null +++ b/TIUIKitCore/Sources/Extensions/UIFont/UIFont+FontDescriptor.swift @@ -0,0 +1,35 @@ +// +// Copyright (c) 2020 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import UIKit.UIFont + +public extension UIFont { + convenience init(fontFamily: String, fontFace: String, size: CGFloat) { + let fontDescriptor = UIFontDescriptor(fontAttributes: [ + UIFontDescriptor.AttributeName.size: size, + UIFontDescriptor.AttributeName.family: fontFamily, + UIFontDescriptor.AttributeName.face: fontFace + ]) + + self.init(descriptor: fontDescriptor, size: size) + } +} diff --git a/TIUIKitCore/Sources/TextAttributes/BaseTextAttributes/BaseTextAttributes.swift b/TIUIKitCore/Sources/TextAttributes/BaseTextAttributes/BaseTextAttributes.swift new file mode 100644 index 00000000..e5ddc663 --- /dev/null +++ b/TIUIKitCore/Sources/TextAttributes/BaseTextAttributes/BaseTextAttributes.swift @@ -0,0 +1,158 @@ +// +// Copyright (c) 2020 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import UIKit.UIFont +import UIKit.UIColor + +/// Base set of attributes to configure appearance of text. +open class BaseTextAttributes { + + /// Text font. + public let font: UIFont + /// Text color. + public let color: UIColor + /// Text alignment. + public let alignment: NSTextAlignment + /// Paragraph line height. + public let lineHeightMultiple: CGFloat + /// Number of lines for labels. + public let numberOfLines: Int + + public init(font: UIFont, + color: UIColor, + alignment: NSTextAlignment, + lineHeightMultiple: CGFloat, + numberOfLines: Int) { + self.font = font + self.color = color + self.alignment = alignment + self.lineHeightMultiple = lineHeightMultiple + self.numberOfLines = numberOfLines + } + + private func configure(textContainer: T) { + textContainer.set(font: font) + textContainer.set(color: color) + textContainer.set(alignment: alignment) + } + + open func configure(label: UILabel) { + configure(textContainer: label) + + label.numberOfLines = numberOfLines + } + + open func configure(textField: UITextField) { + configure(textContainer: textField) + } + + open func configure(button: UIButton, for state: UIControl.State) { + if let buttonLabel = button.titleLabel { + configure(label: buttonLabel) + } + + button.setTitleColor(color, for: state) + } + + private func configure(textContainer: T, + with string: String?, + appearanceConfiguration: (T) -> Void, + textConfiguration: (String?) -> Void, + attributedTextConfiguration: (NSAttributedString?) -> Void) { + if lineHeightMultiple == 1.0 { // default + appearanceConfiguration(textContainer) + + textConfiguration(string) + } else { + let resultAttributedString: NSAttributedString? + + if let string = string { + resultAttributedString = attributedString(for: string) + } else { + resultAttributedString = nil + } + + attributedTextConfiguration(resultAttributedString) + } + } + + open func configure(label: UILabel, with string: String?) { + configure(textContainer: label, + with: string, + appearanceConfiguration: configure(label:), + textConfiguration: { label.text = $0 }, + attributedTextConfiguration: { + label.attributedText = $0 + label.numberOfLines = numberOfLines + }) + } + + open func configure(textField: UITextField, with string: String?) { + configure(textContainer: textField, + with: string, + appearanceConfiguration: configure(textField:), + textConfiguration: { textField.text = $0 }, + attributedTextConfiguration: { textField.attributedText = $0 }) + } + + open func configure(button: UIButton, with string: String?, for state: UIControl.State) { + configure(textContainer: button, + with: string, + appearanceConfiguration: { configure(button: $0, for: state) }, + textConfiguration: { button.setTitle($0, for: state) }, + attributedTextConfiguration: { + button.setAttributedTitle($0, for: state) + button.titleLabel?.numberOfLines = numberOfLines + }) + } + + open func attributedString(for string: String) -> NSAttributedString { + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.alignment = alignment + paragraphStyle.lineHeightMultiple = lineHeightMultiple + + let attributes: [NSAttributedString.Key: Any] = [ + .font: font, + .foregroundColor: color, + .paragraphStyle: paragraphStyle + ] + + return NSAttributedString(string: string, attributes: attributes) + } +} + +public extension BaseTextAttributes { + typealias FigmaPercent = CGFloat + + convenience init(font: UIFont, + color: UIColor, + alignment: NSTextAlignment, + isMultiline: Bool, + lineHeight: FigmaPercent = 100.0) { + + self.init(font: font, + color: color, + alignment: alignment, + lineHeightMultiple: lineHeight / 100.0, + numberOfLines: isMultiline ? 0 : 1) + } +} diff --git a/TIUIKitCore/Sources/ViewText/BaseTextAttributes.swift b/TIUIKitCore/Sources/TextAttributes/BaseTextAttributes/BaseTextAttributesConfigurable.swift similarity index 55% rename from TIUIKitCore/Sources/ViewText/BaseTextAttributes.swift rename to TIUIKitCore/Sources/TextAttributes/BaseTextAttributes/BaseTextAttributesConfigurable.swift index d9d5a26e..944baca8 100644 --- a/TIUIKitCore/Sources/ViewText/BaseTextAttributes.swift +++ b/TIUIKitCore/Sources/TextAttributes/BaseTextAttributes/BaseTextAttributesConfigurable.swift @@ -20,38 +20,38 @@ // THE SOFTWARE. // -import UIKit.UIFont -import UIKit.UIColor +import UIKit -/// Base set of attributes to configure appearance of text. -open class BaseTextAttributes { +protocol BaseTextAttributesConfigurable { + func set(font: UIFont) + func set(color: UIColor) + func set(alignment: NSTextAlignment) +} - /// Text font. - public let font: UIFont - /// Text color. - public let color: UIColor - /// Text alignment. - public let alignment: NSTextAlignment - - /// Memberwise initializer. - /// - /// - Parameters: - /// - font: Text font. - /// - color: Text color. - /// - alignment: Text alignment. - public init(font: UIFont, color: UIColor, alignment: NSTextAlignment = .natural) { +extension UILabel: BaseTextAttributesConfigurable { + public func set(font: UIFont) { self.font = font - self.color = color - self.alignment = alignment + } + + public func set(color: UIColor) { + self.textColor = color + } + + public func set(alignment: NSTextAlignment) { + self.textAlignment = alignment } } -public extension BaseTextAttributes { +extension UITextField: BaseTextAttributesConfigurable { + public func set(font: UIFont) { + self.font = font + } - /// Configures text appearance of given ViewTextConfigurable instance. - /// - /// - Parameter view: ViewTextConfigurable instance to configure with BaseTextAttributes. - func configureBaseApperance(of view: ViewTextConfigurable) { - view.configureBaseAppearance(with: self) + public func set(color: UIColor) { + self.textColor = color + } + + public func set(alignment: NSTextAlignment) { + self.textAlignment = alignment } } diff --git a/TIUIKitCore/Sources/TextAttributes/ViewText/ViewText.swift b/TIUIKitCore/Sources/TextAttributes/ViewText/ViewText.swift new file mode 100644 index 00000000..2900fee8 --- /dev/null +++ b/TIUIKitCore/Sources/TextAttributes/ViewText/ViewText.swift @@ -0,0 +1,74 @@ +// +// Copyright (c) 2020 Touch Instinct +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the Software), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import UIKit + +public struct ViewText { + public let text: String? + public let attributes: BaseTextAttributes + + public init(text: String?, attributes: BaseTextAttributes) { + self.text = text + self.attributes = attributes + } + + public init(string: String, + font: UIFont, + color: UIColor, + alignment: NSTextAlignment = .natural, + lineHeightMultiple: CGFloat = 1.0, + numberOfLines: Int = 1) { + + text = string + attributes = BaseTextAttributes(font: font, + color: color, + alignment: alignment, + lineHeightMultiple: lineHeightMultiple, + numberOfLines: numberOfLines) + } + + public func size(maxWidth: CGFloat = .greatestFiniteMagnitude, + maxHeight: CGFloat = .greatestFiniteMagnitude) -> CGSize { + + guard let text = text else { + return .zero + } + + let attributedString = attributes.attributedString(for: text) + + return attributedString.boundingRect(with: CGSize(width: maxWidth, height: maxHeight), + options: [.usesLineFragmentOrigin, .usesFontLeading], + context: nil).size + } + + public func configure(label: UILabel) { + attributes.configure(label: label, with: text) + } + + public func configure(textField: UITextField) { + attributes.configure(textField: textField, with: text) + } + + public func configure(button: UIButton, for state: UIControl.State) { + attributes.configure(button: button, with: text, for: state) + } +} diff --git a/TIUIKitCore/Sources/ViewText/ViewText.swift b/TIUIKitCore/Sources/ViewText/ViewText.swift deleted file mode 100644 index b84ea370..00000000 --- a/TIUIKitCore/Sources/ViewText/ViewText.swift +++ /dev/null @@ -1,91 +0,0 @@ -// -// Copyright (c) 2020 Touch Instinct -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the Software), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -import UIKit - -/// Enum that describes text with appearance options. -/// -/// - string: Regular string with common and often-used text attributes. -/// - attributedString: Attributed string. -public enum ViewText { - - case string(String, textAttributes: BaseTextAttributes) - case attributedString(NSAttributedString) -} - -public extension ViewText { - - /// Convenient initializer for .string case with default alignment parameter. - /// - /// - Parameters: - /// - string: Text to use. - /// - font: Font to use. - /// - color: Color to use. - /// - alignment: Alignment to use. Default is natural. - init(string: String, font: UIFont, color: UIColor, alignment: NSTextAlignment = .natural) { - self = .string(string, textAttributes: BaseTextAttributes(font: font, - color: color, - alignment: alignment)) - } - - /// Attributed string created using text attributes. - var attributedString: NSAttributedString { - switch self { - case let .string(title, textAttributes): - - let paragraphStyle = NSMutableParagraphStyle() - paragraphStyle.alignment = textAttributes.alignment - - let attributes: [NSAttributedString.Key: Any] = [ - .font: textAttributes.font, - .foregroundColor: textAttributes.color, - .paragraphStyle: paragraphStyle - ] - - return NSAttributedString(string: title, attributes: attributes) - - case .attributedString(let attributedTitle): - return attributedTitle - } - } - - /// Method that calculates size of view text using given max width and height arguments. - /// - /// - Parameters: - /// - maxWidth: The width constraint to apply when computing the string’s bounding rectangle. - /// - maxHeight: The width constraint to apply when computing the string’s bounding rectangle. - /// - Returns: Returns the size required to draw the text. - func size(maxWidth: CGFloat = CGFloat.greatestFiniteMagnitude, - maxHeight: CGFloat = CGFloat.greatestFiniteMagnitude) -> CGSize { - - return attributedString.boundingRect(with: CGSize(width: maxWidth, height: maxHeight), - options: [.usesLineFragmentOrigin, .usesFontLeading], - context: nil).size - } - - /// Configures given ViewTextConfigurable instance. - /// - /// - Parameter view: ViewTextConfigurable instance to configure with ViewText. - func configure(view: ViewTextConfigurable) { - view.configure(with: self) - } -} diff --git a/TIUIKitCore/Sources/ViewText/ViewTextConfigurable/UIButton+ViewTextConfigurable.swift b/TIUIKitCore/Sources/ViewText/ViewTextConfigurable/UIButton+ViewTextConfigurable.swift deleted file mode 100644 index de0e5e5f..00000000 --- a/TIUIKitCore/Sources/ViewText/ViewTextConfigurable/UIButton+ViewTextConfigurable.swift +++ /dev/null @@ -1,121 +0,0 @@ -// -// Copyright (c) 2020 Touch Instinct -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the Software), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -import UIKit.UIButton - -extension UIButton: ViewTextConfigurable { - - public var textFont: UIFont? { - get { - return titleLabel?.font - } - set { - titleLabel?.font = newValue - } - } - - public var titleColor: UIColor? { - get { - return currentTitleColor - } - set { - setTitleColor(newValue, for: []) - } - } - - public var textAlignment: NSTextAlignment { - get { - return contentHorizontalAlignment.textAlignment - } - set { - contentHorizontalAlignment = .init(textAlignment: newValue) - } - } - - public var text: String? { - get { - return currentTitle - } - set { - setTitle(newValue, for: []) - } - } - - public var attributedText: NSAttributedString? { - get { - return currentAttributedTitle - } - set { - setAttributedTitle(newValue, for: []) - } - } -} - -private extension UIControl.ContentHorizontalAlignment { - - init(textAlignment: NSTextAlignment) { - switch textAlignment { - case .left: - self = .leading - - case .right: - self = .trailing - - case .center: - self = .center - - case .justified: - self = .fill - - case .natural: - self = .leading - - @unknown default: - self = .leading - } - } - - var textAlignment: NSTextAlignment { - switch self { - case .left: - return .left - - case .right: - return .right - - case .center: - return .center - - case .fill: - return .justified - - case .leading: - return .natural - - case .trailing: - return .right - - @unknown default: - return .natural - } - } -} diff --git a/TIUIKitCore/Sources/ViewText/ViewTextConfigurable/ViewTextConfigurable.swift b/TIUIKitCore/Sources/ViewText/ViewTextConfigurable/ViewTextConfigurable.swift deleted file mode 100644 index dfd7256c..00000000 --- a/TIUIKitCore/Sources/ViewText/ViewTextConfigurable/ViewTextConfigurable.swift +++ /dev/null @@ -1,69 +0,0 @@ -// -// Copyright (c) 2020 Touch Instinct -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the Software), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -import UIKit.UIFont -import UIKit.UIColor - -/// Protocol that represents text object with appearance attributes. -public protocol ViewTextConfigurable: AnyObject { - - /// Font of text object. - var textFont: UIFont? { get set } - - /// Text color of text object. - var titleColor: UIColor? { get set } - - /// Text alignment of text object. - var textAlignment: NSTextAlignment { get set } - - /// Text itself of text object. - var text: String? { get set } - - /// Attributed text of text object. - var attributedText: NSAttributedString? { get set } -} - -public extension ViewTextConfigurable { - - /// Configures text and text appearance of view using ViewText object. - /// - /// - Parameter viewText: ViewText object with text and text appearance. - func configure(with viewText: ViewText) { - switch viewText { - case let .string(text, textAttributes): - self.text = text - self.configureBaseAppearance(with: textAttributes) - - case .attributedString(let attributedString): - self.attributedText = attributedString - } - } - - /// Configures text appearance of view. - /// - /// - Parameter baseTextAttributes: Set of attributes to configure appearance of text. - func configureBaseAppearance(with baseTextAttributes: BaseTextAttributes) { - textFont = baseTextAttributes.font - titleColor = baseTextAttributes.color - textAlignment = baseTextAttributes.alignment - } -} diff --git a/TIUIKitCore/TIUIKitCore.podspec b/TIUIKitCore/TIUIKitCore.podspec index bd49a66e..60139774 100644 --- a/TIUIKitCore/TIUIKitCore.podspec +++ b/TIUIKitCore/TIUIKitCore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'TIUIKitCore' - s.version = '0.11.0' + s.version = '0.12.0' s.summary = 'Core UI elements: protocols, views and helpers.' s.homepage = 'https://github.com/TouchInstinct/LeadKit/tree/' + s.version.to_s + '/' + s.name s.license = { :type => 'MIT', :file => 'LICENSE' }