Created
June 28, 2020 10:03
Revisions
-
apptekstudios created this gist
Jun 28, 2020 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,161 @@ import SwiftUI struct AccessibleView: View { @ScaledMetricCustom(relativeTo: .title) var someSize: CGFloat = 100 @ScaledFont(customFontName: "TimesNewRomanPS-BoldMT", size: 18, relativeTo: .body) var bodyFont var body: some View { VStack { Rectangle() .frame(width: someSize, height: someSize) Text("Hello world I am dynamically scaled!") .fixedSize(horizontal: false, vertical: true) .font(bodyFont) Spacer() } } } struct AccessibleView_Previews: PreviewProvider { static var previews: some View { NavigationView { ScrollView { VStack { AccessibleView() AccessibleView() .environment(\.sizeCategory, .extraExtraLarge) AccessibleView() .environment(\.sizeCategory, .accessibilityExtraLarge) AccessibleView() .environment(\.sizeCategory, .accessibilityExtraExtraExtraLarge) } } .navigationBarTitle("Demo") } } } @propertyWrapper struct ScaledMetricCustom<Value>: DynamicProperty where Value: BinaryFloatingPoint { @Environment(\.horizontalSizeClass) var horizontalSizeClass @Environment(\.verticalSizeClass) var verticalSizeClass @Environment(\.sizeCategory) var contentSize // Creates the scaled metric with an unscaled value and a text style to scale relative to. init(wrappedValue: Value, maxValue: Value? = nil, relativeTo textStyle: Font.TextStyle = .body) { self.textStyle = textStyle.uiKit self.baseValue = wrappedValue self.maxValue = maxValue } let textStyle: UIFont.TextStyle let baseValue: Value let maxValue: Value? var traitCollection: UITraitCollection { UITraitCollection(traitsFrom: [ UITraitCollection(horizontalSizeClass: horizontalSizeClass?.uiKit ?? .unspecified), UITraitCollection(verticalSizeClass: verticalSizeClass?.uiKit ?? .unspecified), UITraitCollection(preferredContentSizeCategory: contentSize.uiKit) ]) } // The value scaled based on the current environment. var wrappedValue: Value { let scaled = Value(UIFontMetrics(forTextStyle: textStyle).scaledValue(for: CGFloat(baseValue), compatibleWith: traitCollection)) return maxValue.map { min($0, scaled) } ?? scaled } } @propertyWrapper struct ScaledFont: DynamicProperty { @ScaledMetricCustom var fontSize: CGFloat private var fontDefinition: FontDefinition private var maxSize: CGFloat? init(systemFontOfSize fontSize: CGFloat, weight: Font.Weight, design: Font.Design, maxSize: CGFloat? = nil, relativeTo textStyle: Font.TextStyle = .body) { fontDefinition = .system(weight: weight, design: design) self.maxSize = maxSize self._fontSize = ScaledMetricCustom(wrappedValue: fontSize, relativeTo: textStyle) } init(customFontName name: String, size: CGFloat, maxSize: CGFloat? = nil, relativeTo textStyle: Font.TextStyle = .body) { fontDefinition = .custom(name: name) self.maxSize = maxSize self._fontSize = ScaledMetricCustom(wrappedValue: size, relativeTo: textStyle) } private enum FontDefinition { case system(weight: Font.Weight, design: Font.Design) case custom(name: String) } var wrappedValue: Font { switch fontDefinition { case let .custom(name): if #available(iOS 14.0, *) { return Font.custom(name, fixedSize: fontSize) // This is actually using the scaled value (so we pass it to fixed size)! } else { return Font.custom(name, size: fontSize) } case let .system(weight, design): return Font.system(size: fontSize, weight: weight, design: design) } } } extension UserInterfaceSizeClass { var uiKit: UIUserInterfaceSizeClass { switch self { case .compact: return .compact case .regular: return .regular @unknown default: return .unspecified } } } extension ContentSizeCategory { var uiKit: UIContentSizeCategory { switch self { case .accessibilityExtraExtraExtraLarge: return .accessibilityExtraExtraExtraLarge case .accessibilityExtraExtraLarge: return .accessibilityExtraExtraLarge case .accessibilityExtraLarge: return .accessibilityExtraLarge case .accessibilityLarge: return .accessibilityLarge case .accessibilityMedium: return .accessibilityMedium case .extraExtraExtraLarge: return .extraExtraExtraLarge case .extraExtraLarge: return .extraExtraLarge case .extraLarge: return .extraLarge case .extraSmall: return .extraSmall case .large: return .large case .medium: return .medium case .small: return .small @unknown default: return .unspecified } } } extension Font.TextStyle { var uiKit: UIFont.TextStyle { switch self { case .body: return .body case .callout: return .callout case .caption: return .caption1 case .caption2: return .caption2 case .footnote: return .footnote case .headline: return .headline case .largeTitle: return .largeTitle case .subheadline: return .subheadline case .title: return .title1 case .title2: return .title2 case .title3: return .title3 @unknown default: return .body } } } // import PlaygroundSupport // PlaygroundSupport.PlaygroundPage.current.setLiveView(AccessibleView_Previews.previews)