Skip to content

Instantly share code, notes, and snippets.

@apptekstudios
Created June 28, 2020 10:03
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save apptekstudios/e5f282a67beaa85dc725d1d98ec74191 to your computer and use it in GitHub Desktop.
Save apptekstudios/e5f282a67beaa85dc725d1d98ec74191 to your computer and use it in GitHub Desktop.
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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment