Skip to content

Commit

Permalink
Merge pull request #361 from ndonald2/adapt-font-metrics
Browse files Browse the repository at this point in the history
Add AdaptiveStyle based on UIFontMetrics
  • Loading branch information
chrisballinger authored May 23, 2019
2 parents 73fe5c3 + 24d9d69 commit ccc6172
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 7 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
47 changes: 41 additions & 6 deletions Sources/UIKit/AdaptiveStyle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -75,6 +81,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):
Expand Down Expand Up @@ -154,6 +170,7 @@ extension AdaptiveStyle: EmbeddedTransformation {
static let preferred = "preferred"
static let above = "above"
static let below = "below"
static let fontMetrics = "fontMetrics"

}

Expand All @@ -177,23 +194,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
}
Expand Down
2 changes: 2 additions & 0 deletions Sources/UIKit/EmbeddedTransformation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")

}

Expand Down
53 changes: 53 additions & 0 deletions Tests/AdaptiveStyleTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -147,6 +148,58 @@ class AdaptiveStyleTests: XCTestCase {
}
}

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

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, *)
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)

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)))
Expand Down

0 comments on commit ccc6172

Please sign in to comment.