Skip to content

Instantly share code, notes, and snippets.

@christianselig
Last active July 27, 2022 21:24
Show Gist options
  • Save christianselig/e70175c55fd95180f974ba7e965d1886 to your computer and use it in GitHub Desktop.
Save christianselig/e70175c55fd95180f974ba7e965d1886 to your computer and use it in GitHub Desktop.
Make magic numbers more accessible in SwiftUI

More Accessible Magic Numbers

A lot of StackOverflow answers for dealing with things in SwiftUI sometimes being positioned not how you want (for example) understandably solve it with a magic number to put something where they want.

The downside of this is that it likely only works at the default Dynamic Type size, so anyone with larger or smaller text settings (for accessibility reasons, for instance) will likely have a layout that doesn't look as optimized (for instance, if you assume 24.0 is a good number based on the default .large size, if they have their phone set to accessibilityExtraExtraExtraLarge, 24.0 will likely be way too small).

So here's a quick helper extension that simply scales a number based on the Dynamic Type size, so that 24.0 will increase and decrease with the user's Dynamic Type setting.

// Extending BinaryFloatingPoint so that this extension works with both `CGFloat` and `Double`.
extension BinaryFloatingPoint {
    func scaled(to dynamicTypeSize: DynamicTypeSize) -> CGFloat {
        // Important that you pass in the trait collection to `UIFontMetrics`, because otherwise it'll always use the system setting, which you may have overriden for a `UIHostingController` (for instance if you have in-app font options independent of the system).
        let traitCollection = UITraitCollection(preferredContentSizeCategory: dynamicTypeSize.contentSizeCategory)

        // UIFontMetrics does most of the heavy lifting by scaling the number up or down
        let scaled = UIFontMetrics.default.scaledValue(for: CGFloat(self), compatibleWith: traitCollection)
        return scaled
    }
}

// UIFont's scaling doesn't have a function that accepts SwiftUI's `DynamicTypeSize`, so convert it to UIKit's `UIContentSizeCategory`.
extension DynamicTypeSize {
    var contentSizeCategory: UIContentSizeCategory {
        switch self {
        case .xSmall:
            return .extraSmall
        case .small:
            return .small
        case .medium:
            return .medium
        case .large:
            return .large
        case .xLarge:
            return .extraLarge
        case .xxLarge:
            return .extraExtraLarge
        case .xxxLarge:
            return .extraExtraExtraLarge
        case .accessibility1:
            return .accessibilityMedium
        case .accessibility2:
            return .accessibilityLarge
        case .accessibility3:
            return .accessibilityExtraLarge
        case .accessibility4:
            return .accessibilityExtraExtraLarge
        case .accessibility5:
            return .accessibilityExtraExtraExtraLarge
        @unknown default:
            assertionFailure("Unknown content size category encountered: \(self)")
            return .large
        }
    }
}

Here's a usage example:

struct ContentView: View {
    @Environment(\.dynamicTypeSize) private var dynamicTypeSize: DynamicTypeSize
    
    let offsetX: CGFloat { 
        // This will return, for instance, -33.0 when Dynamic Type is set to XXXLarge
        -25.0.scaled(to: dynamicTypeSize))
    }

    var body: some View {
        VStack {
            Text("\(offsetX)")
            
            List {
                ForEach(0 ..< 10, id: \.self) { index in
                    Text("Move me!")
                        .listRowInsets(.init(top: 0.0, leading: offsetX, bottom: 0.0, trailing: 0.0))
                }
                .onMove(perform: { _, _ in })
            }
            .environment(\.editMode, .constant(.active))
        }
    }
}

(This will immediately respond to any system Dynamic Type changes or any UITraitCollection overrides)

@christianselig
Copy link
Author

Hahaha whoops, SwiftUI has this built in! Just use @ScaledMetric! https://www.hackingwithswift.com/quick-start/swiftui/what-is-the-scaledmetric-property-wrapper

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment