diff --git a/ADUtilsTests/DynamicFontTest.swift b/ADUtilsTests/DynamicFontTest.swift index a4f6140..df61bf4 100644 --- a/ADUtilsTests/DynamicFontTest.swift +++ b/ADUtilsTests/DynamicFontTest.swift @@ -5,7 +5,8 @@ // Created by Benjamin Lavialle on 25/10/2017. // -import Foundation +import UIKit +import SwiftUI import Quick import ADUtils import Nimble @@ -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() @@ -36,7 +44,7 @@ class DynamicFontTest: QuickSpec { override class func spec() { - describe("display fonts") { + describe("display UIKit fonts") { let types: [UIFont.TextStyle] = [ .title1, @@ -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") + } } } } diff --git a/ADUtilsTests/__Snapshots__/DynamicFontTest/spec.DynamicFontLayoutXXLTest.png b/ADUtilsTests/__Snapshots__/DynamicFontTest/spec.DynamicFontLayoutXXLTest.png new file mode 100644 index 0000000..bc7624e Binary files /dev/null and b/ADUtilsTests/__Snapshots__/DynamicFontTest/spec.DynamicFontLayoutXXLTest.png differ diff --git a/ADUtilsTests/__Snapshots__/DynamicFontTest/spec.SwiftUIDynamicFontLayoutTest.png b/ADUtilsTests/__Snapshots__/DynamicFontTest/spec.SwiftUIDynamicFontLayoutTest.png new file mode 100644 index 0000000..45e1a7f Binary files /dev/null and b/ADUtilsTests/__Snapshots__/DynamicFontTest/spec.SwiftUIDynamicFontLayoutTest.png differ diff --git a/ADUtilsTests/__Snapshots__/DynamicFontTest/spec.SwiftUIDynamicFontLayoutXXLTest.png b/ADUtilsTests/__Snapshots__/DynamicFontTest/spec.SwiftUIDynamicFontLayoutXXLTest.png new file mode 100644 index 0000000..3cb4cb9 Binary files /dev/null and b/ADUtilsTests/__Snapshots__/DynamicFontTest/spec.SwiftUIDynamicFontLayoutXXLTest.png differ diff --git a/CHANGELOG.md b/CHANGELOG.md index 6dd398a..d0db2f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/Modules/ADUtils/DynamicFont.swift b/Modules/ADUtils/DynamicFont.swift index 313e9a4..1abe19f 100644 --- a/Modules/ADUtils/DynamicFont.swift +++ b/Modules/ADUtils/DynamicFont.swift @@ -7,6 +7,7 @@ import Foundation import UIKit +import SwiftUI /** The DynamicFontProvider protocol provides a font depending on parameters @@ -14,10 +15,19 @@ import UIKit 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 } /** @@ -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 { @@ -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 { @@ -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 { @@ -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) diff --git a/Modules/ADUtils/FontDescription.swift b/Modules/ADUtils/FontDescription.swift index fa73a84..e011597 100644 --- a/Modules/ADUtils/FontDescription.swift +++ b/Modules/ADUtils/FontDescription.swift @@ -7,6 +7,7 @@ import Foundation import UIKit +import SwiftUI private typealias FontDescriptionDictionary = [UIFont.TextStyle.RawValue: FontStyleDescription] @@ -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 @@ -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 "" + } + } +} diff --git a/Modules/ADUtils/UILayoutGuide+Constraints.swift b/Modules/ADUtils/UILayoutGuide+Constraints.swift index 4c9b900..065ecab 100644 --- a/Modules/ADUtils/UILayoutGuide+Constraints.swift +++ b/Modules/ADUtils/UILayoutGuide+Constraints.swift @@ -689,7 +689,7 @@ extension UILayoutGuide { } } -@available(iOS 13.0, *) +@available(iOS 13.0, tvOS 13.0, *) @available(tvOSApplicationExtension 13.0, *) extension UILayoutGuide { /** diff --git a/Modules/ADUtils/UIView+Constraints.swift b/Modules/ADUtils/UIView+Constraints.swift index 212d025..afdf953 100644 --- a/Modules/ADUtils/UIView+Constraints.swift +++ b/Modules/ADUtils/UIView+Constraints.swift @@ -361,7 +361,7 @@ extension UIView { } } -@available(iOS 13.0, *) +@available(iOS 13.0, tvOS 13.0, *) @available(tvOSApplicationExtension 13.0, *) extension UIView { diff --git a/Modules/ADUtils/UIView+LayoutGuideConstraints.swift b/Modules/ADUtils/UIView+LayoutGuideConstraints.swift index b3adcfa..c3a518e 100644 --- a/Modules/ADUtils/UIView+LayoutGuideConstraints.swift +++ b/Modules/ADUtils/UIView+LayoutGuideConstraints.swift @@ -644,7 +644,7 @@ extension UIView { } } -@available(iOS 13.0, *) +@available(iOS 13.0, tvOS 13.0, *) @available(tvOSApplicationExtension 13.0, *) extension UIView { diff --git a/Modules/ADUtils/UIViewController+ChildInsertion.swift b/Modules/ADUtils/UIViewController+ChildInsertion.swift index 5693781..0e63c76 100644 --- a/Modules/ADUtils/UIViewController+ChildInsertion.swift +++ b/Modules/ADUtils/UIViewController+ChildInsertion.swift @@ -63,7 +63,7 @@ extension UIViewController { } } -@available(iOS 13.0, *) +@available(iOS 13.0, tvOS 13.0, *) @available(tvOSApplicationExtension 13.0, *) extension UIViewController { diff --git a/Podfile.lock b/Podfile.lock index 9c3c2e0..76ac3d1 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -38,4 +38,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: a085ff0ceae15032eb100393c6df98696f11d7af -COCOAPODS: 1.11.3 +COCOAPODS: 1.12.1 diff --git a/README.md b/README.md index 087af0a..10727f2 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![Twitter](https://img.shields.io/badge/twitter-@FabernovelTech-blue.svg?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)