added StatefulButton & RoundedStatefulButton.

added CACornerMask rounding extension.
added UIControl.State dictionary extensions.
added UIFont registration.
reworked BaseTextAttributes & ViewText.
removed ViewTextConfigurable.
This commit is contained in:
Ivan Smolin 2020-12-23 11:02:52 +03:00
parent f3fe5e08d8
commit 102c15ca07
23 changed files with 798 additions and 350 deletions

View File

@ -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.

View File

@ -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")
]

View File

@ -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' }

View File

@ -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' }

View File

@ -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' }

View File

@ -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
]
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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

View File

@ -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
}
}

View File

@ -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
}
}
}

View File

@ -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

View File

@ -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<CFError>?
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)
}
}
}

View File

@ -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) }
}
}

View File

@ -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?]
}

View File

@ -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)
}
}

View File

@ -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<T: BaseTextAttributesConfigurable>(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<T>(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)
}
}

View File

@ -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
}
}

View File

@ -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)
}
}

View File

@ -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 strings bounding rectangle.
/// - maxHeight: The width constraint to apply when computing the strings 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)
}
}

View File

@ -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
}
}
}

View File

@ -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
}
}

View File

@ -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' }