Skip to content

Commit

Permalink
Merge pull request #37 from faberNovel/feature/swift_ui_custom_fonts
Browse files Browse the repository at this point in the history
Feature/swift UI custom fonts
  • Loading branch information
alexandre-pod authored Jul 4, 2023
2 parents 3b4851a + f701a8a commit c02fd14
Show file tree
Hide file tree
Showing 13 changed files with 176 additions and 10 deletions.
65 changes: 63 additions & 2 deletions ADUtilsTests/DynamicFontTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
// Created by Benjamin Lavialle on 25/10/2017.
//

import Foundation
import UIKit
import SwiftUI
import Quick
import ADUtils
import Nimble
Expand All @@ -18,6 +19,13 @@ private extension UIFont {
}
}

private extension Font {

static func ad_mainFont(forTextStyle textStyle: Font.TextStyle) -> Font {
return FontHelper.shared.helveticaNeueDynamicFont.font(forTextStyle: textStyle)
}
}

private class FontHelper {

static let shared = FontHelper()
Expand All @@ -36,7 +44,7 @@ class DynamicFontTest: QuickSpec {

override class func spec() {

describe("display fonts") {
describe("display UIKit fonts") {

let types: [UIFont.TextStyle] = [
.title1,
Expand Down Expand Up @@ -69,6 +77,59 @@ class DynamicFontTest: QuickSpec {
it("should layout labels properly") {
stackView.layoutIfNeeded()
assertSnapshot(matching: stackView, as: .image, named: "DynamicFontLayoutTest")
assertSnapshot(
matching: stackView,
as: .image(traits: UITraitCollection(preferredContentSizeCategory: .extraExtraExtraLarge)),
named: "DynamicFontLayoutXXLTest"
)
}
}

describe("display SwiftUI fonts") {

@available(iOS 14.0, *)
struct DynamicFontsView: View {

let types: [Font.TextStyle] = [
.title,
.title2,
.title3,
.headline,
.subheadline,
.body,
.callout,
.footnote,
.caption,
.caption2
]

var body: some View {
VStack(alignment: .leading, spacing: 8) {
ForEach(types.indices, id: \.self) { index in
Text("Lorem sizzle pimpin' sit amizzle").font(.ad_mainFont(forTextStyle: types[index]))
}
}
}
}
it("should layout labels properly") {
if #available(iOS 14.0, *) {
let view = DynamicFontsView()
assertSnapshot(
matching: view,
as: .image(layout: .fixed(width: 200, height: 1000)),
named: "SwiftUIDynamicFontLayoutTest"
)
assertSnapshot(
matching: view,
as: .image(
layout: .fixed(width: 200, height: 1000),
traits: UITraitCollection(preferredContentSizeCategory: .extraExtraExtraLarge)
),
named: "SwiftUIDynamicFontLayoutXXLTest"
)
} else {
throw XCTSkip("title2, title3, caption2 are only available on iOS 14")
}
}
}
}
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

### Created

- Add a SwiftUI Font provider in `DynamicFont`

## [11.3.0] - 2022-08-01Z

### Created
Expand Down
44 changes: 43 additions & 1 deletion Modules/ADUtils/DynamicFont.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,27 @@

import Foundation
import UIKit
import SwiftUI

/**
The DynamicFontProvider protocol provides a font depending on parameters
*/
public protocol DynamicFontProvider {

/**
Provides a font for the given textStyle
Provides a UIKit font for the given textStyle
- parameter textStyle: The font text style
*/
func font(forTextStyle textStyle: UIFont.TextStyle) -> UIFont

/**
Provides a SwiftUI font for the given textStyle
- parameter textStyle: The font text style
- Note: On iOS 13 the font will scale like the body text style.
From iOS 14 it will scale like the provided text style.
*/
@available(iOS 13.0, tvOS 13.0, *)
func font(forTextStyle textStyle: Font.TextStyle) -> Font
}

/**
Expand Down Expand Up @@ -54,6 +64,11 @@ public struct DynamicFont: DynamicFontProvider {
public func font(forTextStyle textStyle: UIFont.TextStyle) -> UIFont {
return provider.font(forTextStyle: textStyle)
}

@available(iOS 13.0, tvOS 13.0, *)
public func font(forTextStyle textStyle: Font.TextStyle) -> Font {
return provider.font(forTextStyle: textStyle)
}
}

private struct DefaultDynamicFontProvider: DynamicFontProvider {
Expand All @@ -63,6 +78,11 @@ private struct DefaultDynamicFontProvider: DynamicFontProvider {
func font(forTextStyle textStyle: UIFont.TextStyle) -> UIFont {
return UIFont.preferredFont(forTextStyle: textStyle)
}

@available(iOS 13.0, tvOS 13.0, *)
func font(forTextStyle textStyle: Font.TextStyle) -> Font {
return Font.system(textStyle)
}
}

private struct CustomFontDynamicFontProvider: DynamicFontProvider {
Expand All @@ -81,6 +101,16 @@ private struct CustomFontDynamicFontProvider: DynamicFontProvider {
}
}

@available(iOS 13.0, tvOS 13.0, *)
func font(forTextStyle textStyle: Font.TextStyle) -> Font {
do {
return try throwingFont(forTextStyle: textStyle)
} catch {
assertionFailure("[DynamicFont] Missing font for \(fontDescription.name) with style : \(textStyle)")
return Font.system(textStyle)
}
}

// MARK: - Private

private var currentSpecifiedContentSizeCategory: UIContentSizeCategory {
Expand All @@ -92,6 +122,18 @@ private struct CustomFontDynamicFontProvider: DynamicFontProvider {
return currentContentSizeCategory
}

@available(iOS 13.0, tvOS 13.0, *)
private func throwingFont(forTextStyle textStyle: Font.TextStyle) throws -> Font {
let styleDescription = try fontDescription.fontStyleDescription(for: textStyle)
let customFont: Font
if #available(iOS 14.0, tvOS 14.0, *) {
customFont = Font.custom(styleDescription.name, size: styleDescription.size, relativeTo: textStyle)
} else {
customFont = Font.custom(styleDescription.name, size: styleDescription.size)
}
return customFont
}

private func throwingFont(forTextStyle textStyle: UIFont.TextStyle) throws -> UIFont {
let styleDescription = try fontDescription.fontStyleDescription(for: textStyle)
let customFont = UIFont(name: styleDescription.name, size: styleDescription.size)
Expand Down
61 changes: 60 additions & 1 deletion Modules/ADUtils/FontDescription.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import Foundation
import UIKit
import SwiftUI

private typealias FontDescriptionDictionary = [UIFont.TextStyle.RawValue: FontStyleDescription]

Expand Down Expand Up @@ -59,7 +60,23 @@ struct FontDescription {
- returns: the FontStyleDescription corresponding to the text style, as specified in the plist
*/
func fontStyleDescription(for fontTextStyle: UIFont.TextStyle) throws -> FontStyleDescription {
guard let fontStyleDescription = dictionary[fontTextStyle.rawValue] else {
try fontStyleDescription(for: fontTextStyle.rawValue)
}

/**
Provides the font for the given text style
- parameter fontTextStyle: the text style
- returns: the FontStyleDescription corresponding to the text style, as specified in the plist
*/
@available(iOS 13.0, tvOS 13.0, *)
func fontStyleDescription(for fontTextStyle: Font.TextStyle) throws -> FontStyleDescription {
try fontStyleDescription(for: fontTextStyle.rawValue)
}

// MARK: - Private

private func fontStyleDescription(for stringValue: String) throws -> FontStyleDescription {
guard let fontStyleDescription = dictionary[stringValue] else {
throw FontDescriptionError.styleForFontUnavailable
}
return fontStyleDescription
Expand All @@ -85,3 +102,45 @@ enum FontDescriptionError: Error {
case plistMissing
case fontMissing
}

@available(iOS 13.0, tvOS 13.0, *)
fileprivate extension Font.TextStyle {

// MARK: - Font

// ???: (Thomas Esterlin) 2023/03/06 I used these values to be able to keep the same plist
// if dev want to use it for both UIKit and SwiftUI
var rawValue: String {
switch self {
case .largeTitle:
#if os(tvOS)
// ???: (Alexandre Podlewski) 03/07/2023 UIFont.TextStyle.largeTitle is not available in tvOS
return "UICTFontTextStyleTitle0"
#else
return UIFont.TextStyle.largeTitle.rawValue
#endif
case .title:
return UIFont.TextStyle.title1.rawValue
case .title2:
return UIFont.TextStyle.title2.rawValue
case .title3:
return UIFont.TextStyle.title3.rawValue
case .headline:
return UIFont.TextStyle.headline.rawValue
case .subheadline:
return UIFont.TextStyle.subheadline.rawValue
case .body:
return UIFont.TextStyle.body.rawValue
case .callout:
return UIFont.TextStyle.callout.rawValue
case .footnote:
return UIFont.TextStyle.footnote.rawValue
case .caption:
return UIFont.TextStyle.caption1.rawValue
case .caption2:
return UIFont.TextStyle.caption2.rawValue
@unknown default:
return ""
}
}
}
2 changes: 1 addition & 1 deletion Modules/ADUtils/UILayoutGuide+Constraints.swift
Original file line number Diff line number Diff line change
Expand Up @@ -689,7 +689,7 @@ extension UILayoutGuide {
}
}

@available(iOS 13.0, *)
@available(iOS 13.0, tvOS 13.0, *)
@available(tvOSApplicationExtension 13.0, *)
extension UILayoutGuide {
/**
Expand Down
2 changes: 1 addition & 1 deletion Modules/ADUtils/UIView+Constraints.swift
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ extension UIView {
}
}

@available(iOS 13.0, *)
@available(iOS 13.0, tvOS 13.0, *)
@available(tvOSApplicationExtension 13.0, *)
extension UIView {

Expand Down
2 changes: 1 addition & 1 deletion Modules/ADUtils/UIView+LayoutGuideConstraints.swift
Original file line number Diff line number Diff line change
Expand Up @@ -644,7 +644,7 @@ extension UIView {
}
}

@available(iOS 13.0, *)
@available(iOS 13.0, tvOS 13.0, *)
@available(tvOSApplicationExtension 13.0, *)
extension UIView {

Expand Down
2 changes: 1 addition & 1 deletion Modules/ADUtils/UIViewController+ChildInsertion.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ extension UIViewController {
}
}

@available(iOS 13.0, *)
@available(iOS 13.0, tvOS 13.0, *)
@available(tvOSApplicationExtension 13.0, *)
extension UIViewController {

Expand Down
2 changes: 1 addition & 1 deletion Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,4 @@ SPEC CHECKSUMS:

PODFILE CHECKSUM: a085ff0ceae15032eb100393c6df98696f11d7af

COCOAPODS: 1.11.3
COCOAPODS: 1.12.1
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
[![Twitter](https://img.shields.io/badge/[email protected]?style=flat)](https://twitter.com/FabernovelTech)
![](https://github.com/faberNovel/ADUtils/workflows/CI/badge.svg)

ADUtils is a set of helpers, shortcuts or other tools providing simplified interactions with UIKit and more generally with Swift.
ADUtils is a set of helpers, shortcuts or other tools providing simplified interactions with UIKit and SwiftUI.

- [Features](#features)
- [ADUtils](#adutils)
Expand Down

0 comments on commit c02fd14

Please sign in to comment.