From 804ff8ef29b4bd242bbe48e2c30d6fc0955def90 Mon Sep 17 00:00:00 2001 From: Nick Donaldson Date: Mon, 6 May 2019 16:58:56 -0600 Subject: [PATCH 1/5] Add support for adaptive sizing based on UIFontMetrics --- Sources/UIKit/AdaptiveStyle.swift | 49 ++++++++++++++++++---- Sources/UIKit/EmbeddedTransformation.swift | 2 + Tests/AdaptiveStyleTests.swift | 40 ++++++++++++++++++ 3 files changed, 83 insertions(+), 8 deletions(-) diff --git a/Sources/UIKit/AdaptiveStyle.swift b/Sources/UIKit/AdaptiveStyle.swift index 12dcc961..0e68f000 100644 --- a/Sources/UIKit/AdaptiveStyle.swift +++ b/Sources/UIKit/AdaptiveStyle.swift @@ -23,6 +23,12 @@ public enum AdaptiveStyle { /// family of methods. case preferred + /// Enable automatic scaling of fonts obtained using `UIFontMetrics` + /// available on iOS 11+ based on the provided `textStyle` and optional + /// `maxPointSize`. If `maxPointSize` is `nil` the font will grow unbounded. + @available(iOS 11, tvOS 11, *) + case fontMetrics(textStyle: BonMotTextStyle, maxPointSize: CGFloat?) + /// If the text is scaled above `size`, substitute the font named /// `useFontNamed`, but using all the same attributes as the original font. /// This style may be combined with other scaling behaviors such as `control` @@ -34,7 +40,6 @@ public enum AdaptiveStyle { /// This style may be combined with other scaling behaviors such as `control` /// and `body`. case below(size: CGFloat, useFontNamed: String) - } extension AdaptiveStyle: AdaptiveStyleTransformation { @@ -75,6 +80,16 @@ extension AdaptiveStyle: AdaptiveStyleTransformation { else { print("No text style in the font, can not adapt") } + case .fontMetrics(let style, let maxPointSize): + if #available(iOS 11, tvOS 11, *) { + let metrics = UIFontMetrics(forTextStyle: style) + if let maxPointSize = maxPointSize { + font = metrics.scaledFont(for: font, maximumPointSize: maxPointSize, compatibleWith: traitCollection) + } + else { + font = metrics.scaledFont(for: font, compatibleWith: traitCollection) + } + } case .above(let size, let fontName): font = pointSize > size ? font.fontWithSameAttributes(named: fontName) : font case .below(let size, let family): @@ -154,7 +169,7 @@ extension AdaptiveStyle: EmbeddedTransformation { static let preferred = "preferred" static let above = "above" static let below = "below" - + static let fontMetrics = "fontMetrics" } var asDictionary: StyleAttributes { @@ -177,23 +192,41 @@ extension AdaptiveStyle: EmbeddedTransformation { return [EmbeddedTransformationHelpers.Key.type: Value.body] case .preferred: return [EmbeddedTransformationHelpers.Key.type: Value.preferred] + case .fontMetrics(let textStyle, let maxPointSize): + var attributes: StyleAttributes = [ + EmbeddedTransformationHelpers.Key.type: Value.fontMetrics, + EmbeddedTransformationHelpers.Key.textStyle: textStyle, + ] + if let maxPointSize = maxPointSize { + attributes[EmbeddedTransformationHelpers.Key.maxPointSize] = maxPointSize + } + return attributes } } static func from(dictionary dict: StyleAttributes) -> EmbeddedTransformation? { switch (dict[EmbeddedTransformationHelpers.Key.type] as? String, dict[EmbeddedTransformationHelpers.Key.size] as? CGFloat, - dict[Key.fontName] as? String) { - case (Value.control?, nil, nil): + dict[Key.fontName] as? String, + dict[EmbeddedTransformationHelpers.Key.textStyle] as? BonMotTextStyle, + dict[EmbeddedTransformationHelpers.Key.maxPointSize] as? CGFloat) { + case (Value.control?, nil, nil, nil, nil): return AdaptiveStyle.control - case (Value.body?, nil, nil): + case (Value.body?, nil, nil, nil, nil): return AdaptiveStyle.body - case (Value.preferred?, nil, nil): + case (Value.preferred?, nil, nil, nil, nil): return AdaptiveStyle.preferred - case let (Value.above?, size?, fontName?): + case let (Value.above?, size?, fontName?, nil, nil): return AdaptiveStyle.above(size: size, useFontNamed: fontName) - case let (Value.below?, size?, fontName?): + case let (Value.below?, size?, fontName?, nil, nil): return AdaptiveStyle.below(size: size, useFontNamed: fontName) + case let (Value.fontMetrics?, nil, nil, textStyle?, maxPointSize): + if #available(iOS 11, tvOS 11, *) { + return AdaptiveStyle.fontMetrics(textStyle: textStyle, maxPointSize: maxPointSize) + } + else { + return nil + } default: return nil } diff --git a/Sources/UIKit/EmbeddedTransformation.swift b/Sources/UIKit/EmbeddedTransformation.swift index 6fb3eec3..f89496ba 100644 --- a/Sources/UIKit/EmbeddedTransformation.swift +++ b/Sources/UIKit/EmbeddedTransformation.swift @@ -37,6 +37,8 @@ internal enum EmbeddedTransformationHelpers { static let type = NSAttributedString.Key("type") static let size = NSAttributedString.Key("size") + static let textStyle = NSAttributedString.Key("textStyle") + static let maxPointSize = NSAttributedString.Key("maxPointSize") } diff --git a/Tests/AdaptiveStyleTests.swift b/Tests/AdaptiveStyleTests.swift index 31ab09eb..26853e3f 100644 --- a/Tests/AdaptiveStyleTests.swift +++ b/Tests/AdaptiveStyleTests.swift @@ -147,6 +147,46 @@ class AdaptiveStyleTests: XCTestCase { } } + @available(iOS 11, tvOS 11, *) + func testFontMetricsAdaptation() { + let inputFont = UIFont(name: "Avenir-Book", size: 28)! + let style = StringStyle(.font(inputFont), .adapt(.fontMetrics(textStyle: .headline, maxPointSize: nil))) + print(style.attributes) + + let testAttributes = { (contentSizeCategory: BonMotContentSizeCategory) -> StyleAttributes in + let traitCollection = UITraitCollection(preferredContentSizeCategory: contentSizeCategory) + return NSAttributedString.adapt(attributes: style.attributes, to: traitCollection) + } + + BONAssert(attributes: testAttributes(UIContentSizeCategory.extraSmall), query: { $0.pointSize }, float: 24) + BONAssert(attributes: testAttributes(UIContentSizeCategory.small), query: { $0.pointSize }, float: 25) + BONAssert(attributes: testAttributes(UIContentSizeCategory.medium), query: { $0.pointSize }, float: 27) + BONAssert(attributes: testAttributes(UIContentSizeCategory.large), query: { $0.pointSize }, float: 28) + BONAssert(attributes: testAttributes(UIContentSizeCategory.extraLarge), query: { $0.pointSize }, float: 31) + BONAssert(attributes: testAttributes(UIContentSizeCategory.extraExtraLarge), query: { $0.pointSize }, float: 33) + BONAssert(attributes: testAttributes(UIContentSizeCategory.extraExtraExtraLarge), query: { $0.pointSize }, float: 37) + } + + @available(iOS 11, tvOS 11, *) + func testFontMetricsWithMaxPointSizeAdaptation() { + let inputFont = UIFont(name: "Avenir-Book", size: 28)! + let style = StringStyle(.font(inputFont), .adapt(.fontMetrics(textStyle: .headline, maxPointSize: 30))) + print(style.attributes) + + let testAttributes = { (contentSizeCategory: BonMotContentSizeCategory) -> StyleAttributes in + let traitCollection = UITraitCollection(preferredContentSizeCategory: contentSizeCategory) + return NSAttributedString.adapt(attributes: style.attributes, to: traitCollection) + } + + BONAssert(attributes: testAttributes(UIContentSizeCategory.extraSmall), query: { $0.pointSize }, float: 24) + BONAssert(attributes: testAttributes(UIContentSizeCategory.small), query: { $0.pointSize }, float: 25) + BONAssert(attributes: testAttributes(UIContentSizeCategory.medium), query: { $0.pointSize }, float: 27) + BONAssert(attributes: testAttributes(UIContentSizeCategory.large), query: { $0.pointSize }, float: 28) + BONAssert(attributes: testAttributes(UIContentSizeCategory.extraLarge), query: { $0.pointSize }, float: 30) + BONAssert(attributes: testAttributes(UIContentSizeCategory.extraExtraLarge), query: { $0.pointSize }, float: 30) + BONAssert(attributes: testAttributes(UIContentSizeCategory.extraExtraExtraLarge), query: { $0.pointSize }, float: 30) + } + func testAdobeAdaptiveTracking() { let font = UIFont(name: "Avenir-Book", size: 30)! let chain = StringStyle(.font(font), .adapt(.control), .tracking(.adobe(300))) From db72d0130d2f50c48b4c6076086fc9d4169e2d7b Mon Sep 17 00:00:00 2001 From: Nick Donaldson Date: Mon, 6 May 2019 17:25:44 -0600 Subject: [PATCH 2/5] Skip font metrics tests on iOS 10 --- Tests/AdaptiveStyleTests.swift | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Tests/AdaptiveStyleTests.swift b/Tests/AdaptiveStyleTests.swift index 26853e3f..b8832f1d 100644 --- a/Tests/AdaptiveStyleTests.swift +++ b/Tests/AdaptiveStyleTests.swift @@ -16,6 +16,7 @@ import XCTest let defaultTraitCollection = UITraitCollection(preferredContentSizeCategory: UIContentSizeCategory.large) // These tests rely on iOS 10.0 APIs. Test method needs to be updated to run on iOS 9.0 +//swiftlint:disable type_body_length @available(iOS 10.0, *) class AdaptiveStyleTests: XCTestCase { @@ -147,8 +148,14 @@ class AdaptiveStyleTests: XCTestCase { } } - @available(iOS 11, tvOS 11, *) + @available(iOS 11, *) func testFontMetricsAdaptation() { + // Test is still always run (and fails) on the iOS 10 simulator despite + // the availability annotation. + guard ProcessInfo().operatingSystemVersion.majorVersion > 10 else { + return + } + let inputFont = UIFont(name: "Avenir-Book", size: 28)! let style = StringStyle(.font(inputFont), .adapt(.fontMetrics(textStyle: .headline, maxPointSize: nil))) print(style.attributes) @@ -167,8 +174,14 @@ class AdaptiveStyleTests: XCTestCase { BONAssert(attributes: testAttributes(UIContentSizeCategory.extraExtraExtraLarge), query: { $0.pointSize }, float: 37) } - @available(iOS 11, tvOS 11, *) + @available(iOS 11, *) func testFontMetricsWithMaxPointSizeAdaptation() { + // Test is still always run (and fails) on the iOS 10 simulator despite + // the availability annotation. + guard ProcessInfo().operatingSystemVersion.majorVersion > 10 else { + return + } + let inputFont = UIFont(name: "Avenir-Book", size: 28)! let style = StringStyle(.font(inputFont), .adapt(.fontMetrics(textStyle: .headline, maxPointSize: 30))) print(style.attributes) From 898d7adb8b95a3af372fd04b84bd1142400d8429 Mon Sep 17 00:00:00 2001 From: Nick Donaldson Date: Mon, 6 May 2019 17:26:31 -0600 Subject: [PATCH 3/5] Revert change to whitespace --- Sources/UIKit/AdaptiveStyle.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/UIKit/AdaptiveStyle.swift b/Sources/UIKit/AdaptiveStyle.swift index 0e68f000..461cc1b7 100644 --- a/Sources/UIKit/AdaptiveStyle.swift +++ b/Sources/UIKit/AdaptiveStyle.swift @@ -40,6 +40,7 @@ public enum AdaptiveStyle { /// This style may be combined with other scaling behaviors such as `control` /// and `body`. case below(size: CGFloat, useFontNamed: String) + } extension AdaptiveStyle: AdaptiveStyleTransformation { @@ -170,6 +171,7 @@ extension AdaptiveStyle: EmbeddedTransformation { static let above = "above" static let below = "below" static let fontMetrics = "fontMetrics" + } var asDictionary: StyleAttributes { From 6e542ee227957ef6a5161eedc16c26a3d701872c Mon Sep 17 00:00:00 2001 From: Chris Ballinger Date: Wed, 22 May 2019 16:47:08 -0700 Subject: [PATCH 4/5] Trigger CI From 24d9d6964c69f61b4b1e9cf420ddb15c1ed2272b Mon Sep 17 00:00:00 2001 From: Chris Ballinger Date: Wed, 22 May 2019 18:15:25 -0700 Subject: [PATCH 5/5] Fix failure when running Danger on forked PRs --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7ef50f9a..9f54acbf 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -42,7 +42,7 @@ jobs: name: Danger when: always command: | - bundle exec danger + if [ -n "$DANGER_GITHUB_API_TOKEN" ]; then bundle exec danger; else echo "Skipping Danger for forked pull request."; fi - run: name: Upload to Codecov when: always