diff --git a/CHANGELOG.md b/CHANGELOG.md index cf2ed5c..f69661e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ N/A N/A -## [1.5.0](https://github.com/tbaranes/FittableFontLabel/releases/tag/1.4.0) (06-11-2017) +## [2.0.0](https://github.com/tbaranes/FittableFontLabel/releases/tag/2.0.0) (10-11-2017) #### Enhancements diff --git a/FittableFontLabel.podspec b/FittableFontLabel.podspec index 3977025..4cac702 100644 --- a/FittableFontLabel.podspec +++ b/FittableFontLabel.podspec @@ -4,7 +4,7 @@ Pod::Spec.new do |s| s.name = "FittableFontLabel" s.module_name = "FittableFontLabel" -s.version = "1.5.0" +s.version = "2.0.0" s.summary = "UILabel (extension) that adjust the font size to fit a rect (width and height)." s.description = "UILabel (extension or subclass) that adjust the font size to fit a frame: width and height if multilines, width only if single lines" s.homepage = "https://github.com/tbaranes/FittableFontLabel" diff --git a/FittableFontLabel/FittableFontLabel.xcodeproj/project.pbxproj b/FittableFontLabel/FittableFontLabel.xcodeproj/project.pbxproj index c7fae85..7b748bc 100644 --- a/FittableFontLabel/FittableFontLabel.xcodeproj/project.pbxproj +++ b/FittableFontLabel/FittableFontLabel.xcodeproj/project.pbxproj @@ -10,8 +10,8 @@ E2227AA61CCE80C5006C4289 /* FittableFontLabel.h in Headers */ = {isa = PBXBuildFile; fileRef = E2227AA51CCE80C5006C4289 /* FittableFontLabel.h */; settings = {ATTRIBUTES = (Public, ); }; }; E2227AAD1CCE80C5006C4289 /* FittableFontLabel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E2227AA21CCE80C5006C4289 /* FittableFontLabel.framework */; }; E2227AB21CCE80C5006C4289 /* FittableFontLabelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2227AB11CCE80C5006C4289 /* FittableFontLabelTests.swift */; }; - E2A4B6631D84769C005E6710 /* FittableFontLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A4B6611D84769C005E6710 /* FittableFontLabel.swift */; }; - E2A4B6641D84769C005E6710 /* UILabelExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2A4B6621D84769C005E6710 /* UILabelExtension.swift */; }; + E2EFA1B41FB5915A00096E56 /* FittableFontLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2EFA1B21FB5915A00096E56 /* FittableFontLabel.swift */; }; + E2EFA1B51FB5915A00096E56 /* UILabelExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2EFA1B31FB5915A00096E56 /* UILabelExtension.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -31,8 +31,8 @@ E2227AAC1CCE80C5006C4289 /* FittableFontLabel iOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "FittableFontLabel iOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; E2227AB11CCE80C5006C4289 /* FittableFontLabelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FittableFontLabelTests.swift; sourceTree = ""; }; E2227AB31CCE80C5006C4289 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - E2A4B6611D84769C005E6710 /* FittableFontLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FittableFontLabel.swift; sourceTree = ""; }; - E2A4B6621D84769C005E6710 /* UILabelExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UILabelExtension.swift; sourceTree = ""; }; + E2EFA1B21FB5915A00096E56 /* FittableFontLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FittableFontLabel.swift; sourceTree = ""; }; + E2EFA1B31FB5915A00096E56 /* UILabelExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UILabelExtension.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -57,7 +57,7 @@ E2227A981CCE80C5006C4289 = { isa = PBXGroup; children = ( - E2A4B6601D84769C005E6710 /* Source */, + E2EFA1B11FB5915A00096E56 /* Source */, E2227AA41CCE80C5006C4289 /* FittableFontLabel iOS */, E2227AB01CCE80C5006C4289 /* FittableFontLabelTests */, E2227AA31CCE80C5006C4289 /* Products */, @@ -92,13 +92,14 @@ path = "FittableFontLabel iOS Tests"; sourceTree = ""; }; - E2A4B6601D84769C005E6710 /* Source */ = { + E2EFA1B11FB5915A00096E56 /* Source */ = { isa = PBXGroup; children = ( - E2A4B6611D84769C005E6710 /* FittableFontLabel.swift */, - E2A4B6621D84769C005E6710 /* UILabelExtension.swift */, + E2EFA1B21FB5915A00096E56 /* FittableFontLabel.swift */, + E2EFA1B31FB5915A00096E56 /* UILabelExtension.swift */, ); - path = Source; + name = Source; + path = ../Source; sourceTree = ""; }; /* End PBXGroup section */ @@ -229,8 +230,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - E2A4B6641D84769C005E6710 /* UILabelExtension.swift in Sources */, - E2A4B6631D84769C005E6710 /* FittableFontLabel.swift in Sources */, + E2EFA1B51FB5915A00096E56 /* UILabelExtension.swift in Sources */, + E2EFA1B41FB5915A00096E56 /* FittableFontLabel.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/FittableFontLabel/Source/FittableFontLabel.swift b/FittableFontLabel/Source/FittableFontLabel.swift deleted file mode 100644 index a51c2b9..0000000 --- a/FittableFontLabel/Source/FittableFontLabel.swift +++ /dev/null @@ -1,105 +0,0 @@ -// -// FittableFontLabel.swift -// -// Copyright (c) 2016 Tom Baranes (https://github.com/tbaranes) -// -// 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 - -// An UILabel subclass allowing you to automatize the process of adjusting the font size. -@IBDesignable -open class FittableFontLabel: UILabel { - - // MARK: Properties - - /// If true, the font size will be adjusted each time that the text or the frame change. - @IBInspectable public var autoAdjustFontSize: Bool = true - - /// The biggest font size to use during drawing. The default value is the current font size - @IBInspectable public var maxFontSize: CGFloat = CGFloat.nan - - /// The scale factor that determines the smallest font size to use during drawing. The default value is 0.1 - @IBInspectable public var minFontScale: CGFloat = CGFloat.nan - - /// UIEdgeInset - @IBInspectable public var leftInset: CGFloat = 0 - @IBInspectable public var rightInset: CGFloat = 0 - @IBInspectable public var topInset: CGFloat = 0 - @IBInspectable public var bottomInset: CGFloat = 0 - - // MARK: Properties override - - open override var text: String? { - didSet { - adjustFontSize() - } - } - - open override var frame: CGRect { - didSet { - adjustFontSize() - } - } - - // MARK: Private - - var isUpdatingFromIB = false - - // MARK: Life cycle - - open override func prepareForInterfaceBuilder() { - super.prepareForInterfaceBuilder() - isUpdatingFromIB = autoAdjustFontSize - adjustFontSize() - } - - open override func awakeFromNib() { - super.awakeFromNib() - isUpdatingFromIB = autoAdjustFontSize - adjustFontSize() - } - - open override func layoutSubviews() { - super.layoutSubviews() - if !isUpdatingFromIB { - adjustFontSize() - } - isUpdatingFromIB = false - } - - // MARK: Insets - - open override func drawText(in rect: CGRect) { - let insets = UIEdgeInsets.init(top: topInset, left: leftInset, bottom: bottomInset, right: rightInset) - super.drawText(in: UIEdgeInsetsInsetRect(rect, insets)) - } - -} - -// MARK: Helpers - -extension FittableFontLabel { - - private func adjustFontSize() { - if autoAdjustFontSize { - fontSizeToFit(maxFontSize: maxFontSize, minFontScale: minFontScale) - } - } -} diff --git a/FittableFontLabel/Source/UILabelExtension.swift b/FittableFontLabel/Source/UILabelExtension.swift deleted file mode 100644 index ba43078..0000000 --- a/FittableFontLabel/Source/UILabelExtension.swift +++ /dev/null @@ -1,142 +0,0 @@ -// FittableLabel.swift -// -// Copyright (c) 2016 Tom Baranes (https://github.com/tbaranes) -// -// 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 extension UILabel { - - /** - Resize the font to make the current text fit the label frame. - - - parameter maxFontSize: The max font size available - - parameter minFontScale: The min font scale that the font will have - - parameter rectSize: Rect size where the label must fit - */ - public func fontSizeToFit(maxFontSize: CGFloat = 100, minFontScale: CGFloat = 0.1, rectSize: CGSize? = nil) { - guard let unwrappedText = self.text else { - return - } - - let newFontSize = fontSizeThatFits(text: unwrappedText, maxFontSize: maxFontSize, minFontScale: minFontScale, rectSize: rectSize) - font = font.withSize(newFontSize) - } - - /** - Returns a font size of a specific string in a specific font that fits a specific size - - - parameter text: The text to use - - parameter maxFontSize: The max font size available - - parameter minFontScale: The min font scale that the font will have - - parameter rectSize: Rect size where the label must fit - */ - public func fontSizeThatFits(text string: String, maxFontSize: CGFloat = 100, minFontScale: CGFloat = 0.1, rectSize: CGSize? = nil) -> CGFloat { - let maxFontSize = maxFontSize.isNaN ? 100 : maxFontSize - let minFontScale = minFontScale.isNaN ? 0.1 : minFontScale - let minimumFontSize = maxFontSize * minFontScale - let rectSize = rectSize ?? bounds.size - guard !string.isEmpty else { - return self.font.pointSize - } - - - let constraintSize = numberOfLines == 1 ? - CGSize(width: CGFloat.greatestFiniteMagnitude, height: rectSize.height) : - CGSize(width: rectSize.width, height: CGFloat.greatestFiniteMagnitude) - return binarySearch(string: string, minSize: minimumFontSize, maxSize: maxFontSize, size: rectSize, constraintSize: constraintSize) - } - -} - -// MARK: - Helpers - -extension UILabel { - - private func currentAttributedStringAttributes() -> [NSAttributedStringKey: Any] { - var newAttributes = [NSAttributedStringKey: Any]() - attributedText?.enumerateAttributes(in: NSRange(0..<(text?.count ?? 0)), options: .longestEffectiveRangeNotRequired, using: { attributes, range, stop in - newAttributes = attributes - }) - return newAttributes - } - -} - -// MARK: - Search - -extension UILabel { - - private enum FontSizeState { - case fit, tooBig, tooSmall - } - - private func binarySearch(string: String, minSize: CGFloat, maxSize: CGFloat, size: CGSize, constraintSize: CGSize) -> CGFloat { - let fontSize = (minSize + maxSize) / 2 - var attributes = currentAttributedStringAttributes() - attributes[NSAttributedStringKey.font] = font.withSize(fontSize) - - let rect = string.boundingRect(with: constraintSize, options: .usesLineFragmentOrigin, attributes: attributes, context: nil) - let state = numberOfLines == 1 ? singleLineSizeState(rect: rect, size: size) : multiLineSizeState(rect: rect, size: size) - - // if the search range is smaller than 0.1 of a font size we stop - // returning either side of min or max depending on the state - let diff = maxSize - minSize - guard diff > 0.1 else { - switch state { - case .tooSmall: - return maxSize - default: - return minSize - } - } - - switch state { - case .fit: return fontSize - case .tooBig: return binarySearch(string: string, minSize: minSize, maxSize: fontSize, size: size, constraintSize: constraintSize) - case .tooSmall: return binarySearch(string: string, minSize: fontSize, maxSize: maxSize, size: size, constraintSize: constraintSize) - } - } - - private func singleLineSizeState(rect: CGRect, size: CGSize) -> FontSizeState { - if rect.width >= size.width + 10 && rect.width <= size.width { - return .fit - } else if rect.width > size.width { - return .tooBig - } else { - return .tooSmall - } - } - - private func multiLineSizeState(rect: CGRect, size: CGSize) -> FontSizeState { - // if rect within 10 of size - if rect.height < size.height + 10 && - rect.height > size.height - 10 && - rect.width > size.width + 10 && - rect.width < size.width - 10 { - return .fit - } else if rect.height > size.height || rect.width > size.width { - return .tooBig - } else { - return .tooSmall - } - } - -} diff --git a/Source/FittableFontLabel.swift b/Source/FittableFontLabel.swift index 1c74911..d18ecd6 100644 --- a/Source/FittableFontLabel.swift +++ b/Source/FittableFontLabel.swift @@ -95,9 +95,9 @@ open class FittableFontLabel: UILabel { // MARK: Helpers -fileprivate extension FittableFontLabel { +extension FittableFontLabel { - func adjustFontSize() { + private func adjustFontSize() { if autoAdjustFontSize { fontSizeToFit(maxFontSize: maxFontSize, minFontScale: minFontScale) } diff --git a/Source/UILabelExtension.swift b/Source/UILabelExtension.swift index 2f75755..18ee169 100644 --- a/Source/UILabelExtension.swift +++ b/Source/UILabelExtension.swift @@ -67,11 +67,11 @@ public extension UILabel { // MARK: - Helpers -private extension UILabel { +extension UILabel { - func currentAttributedStringAttributes() -> [String: Any] { - var newAttributes = [String: Any]() - attributedText?.enumerateAttributes(in: NSRange(0..<(text?.characters.count ?? 0)), options: .longestEffectiveRangeNotRequired, using: { attributes, _, _ in + private func currentAttributedStringAttributes() -> [NSAttributedStringKey: Any] { + var newAttributes = [NSAttributedStringKey: Any]() + attributedText?.enumerateAttributes(in: NSRange(0..<(text?.count ?? 0)), options: .longestEffectiveRangeNotRequired, using: { attributes, _, _ in newAttributes = attributes }) return newAttributes @@ -81,16 +81,16 @@ private extension UILabel { // MARK: - Search -private extension UILabel { +extension UILabel { - enum FontSizeState { + private enum FontSizeState { case fit, tooBig, tooSmall } - func binarySearch(string: String, minSize: CGFloat, maxSize: CGFloat, size: CGSize, constraintSize: CGSize) -> CGFloat { + private func binarySearch(string: String, minSize: CGFloat, maxSize: CGFloat, size: CGSize, constraintSize: CGSize) -> CGFloat { let fontSize = (minSize + maxSize) / 2 var attributes = currentAttributedStringAttributes() - attributes[NSFontAttributeName] = font.withSize(fontSize) + attributes[NSAttributedStringKey.font] = font.withSize(fontSize) let rect = string.boundingRect(with: constraintSize, options: .usesLineFragmentOrigin, attributes: attributes, context: nil) let state = numberOfLines == 1 ? singleLineSizeState(rect: rect, size: size) : multiLineSizeState(rect: rect, size: size) @@ -100,7 +100,7 @@ private extension UILabel { let diff = maxSize - minSize guard diff > 0.1 else { switch state { - case .TooSmall: + case .tooSmall: return maxSize default: return minSize @@ -108,33 +108,33 @@ private extension UILabel { } switch state { - case .Fit: return fontSize - case .TooBig: return binarySearch(string: string, minSize: minSize, maxSize: fontSize, size: size, constraintSize: constraintSize) - case .TooSmall: return binarySearch(string: string, minSize: fontSize, maxSize: maxSize, size: size, constraintSize: constraintSize) + case .fit: return fontSize + case .tooBig: return binarySearch(string: string, minSize: minSize, maxSize: fontSize, size: size, constraintSize: constraintSize) + case .tooSmall: return binarySearch(string: string, minSize: fontSize, maxSize: maxSize, size: size, constraintSize: constraintSize) } } - func singleLineSizeState(rect: CGRect, size: CGSize) -> FontSizeState { + private func singleLineSizeState(rect: CGRect, size: CGSize) -> FontSizeState { if rect.width >= size.width + 10 && rect.width <= size.width { - return .Fit + return .fit } else if rect.width > size.width { - return .TooBig + return .tooBig } else { - return .TooSmall + return .tooSmall } } - func multiLineSizeState(rect: CGRect, size: CGSize) -> FontSizeState { + private func multiLineSizeState(rect: CGRect, size: CGSize) -> FontSizeState { // if rect within 10 of size if rect.height < size.height + 10 && rect.height > size.height - 10 && rect.width > size.width + 10 && rect.width < size.width - 10 { - return .Fit + return .fit } else if rect.height > size.height || rect.width > size.width { - return .TooBig + return .tooBig } else { - return .TooSmall + return .tooSmall } }