Created
December 24, 2024 18:48
-
-
Save marcpalmer/b593fb2b6e9502a77d19dfc249d360e0 to your computer and use it in GitHub Desktop.
Size-dependent metrics for SwiftUI
This file contains 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 characters
// | |
// SizeClassDependentValue.swift | |
// Captionista | |
// | |
// Created by Marc Palmer on 11/05/2021. | |
// Copyright © 2021 Montana Floss Co. Ltd. All rights reserved. | |
// | |
import Foundation | |
import SwiftUI | |
/// A value that can have different values depending on compact or regular size class in a single axis. | |
public struct SizeClassDependentValue<Value> { | |
private let compact: Value | |
private let regular: Value | |
let axis: Axis | |
/// Create using the values for the given axis. | |
public init(_ axis: Axis = .horizontal, compact: Value, regular: Value? = nil) { | |
self.axis = axis | |
self.compact = compact | |
self.regular = regular ?? compact | |
} | |
public init(_ value: Value) { | |
self.init(compact: value, regular: value) | |
} | |
/// Get the value for the given size class. | |
/// - note: If the sizeClass is nil it will default to the value for regular | |
public func value(for sizeClass: UserInterfaceSizeClass?) -> Value { | |
switch sizeClass { | |
case .compact: | |
return compact | |
case .none, | |
.regular: | |
return regular | |
@unknown default: | |
return regular | |
} | |
} | |
/// Syntactic sugar for getting the value for the size class. | |
public subscript(_ sizeClass: UserInterfaceSizeClass?) -> Value { | |
value(for: sizeClass) | |
} | |
} | |
extension SizeClassDependentValue: ExpressibleByFloatLiteral where Value: BinaryFloatingPoint { | |
public init(floatLiteral value: FloatLiteralType) { | |
self.init(Value(value)) | |
} | |
} | |
extension SizeClassDependentValue: ExpressibleByIntegerLiteral where Value: BinaryFloatingPoint { | |
public init(integerLiteral value: IntegerLiteralType) { | |
self.init(Value(value)) | |
} | |
} | |
/// Add negation support for floating point values, so that you can use e.g. `-paddingAmount` | |
public extension SizeClassDependentValue where Value: FloatingPoint { | |
static var zero: Self { SizeClassDependentValue<Value>(compact: 0) } | |
static prefix func -(value: SizeClassDependentValue<Value>) -> SizeClassDependentValue<Value> { | |
SizeClassDependentValue(value.axis, compact: -(value.compact), regular: -(value.regular)) | |
} | |
} | |
/// Add an `adaptivePadding` View modifier that takes size class dependent values and evaluates them automatically | |
/// so that your View does not have to include the size class environment values. | |
public extension View { | |
/// Like `padding` but using a value that is dependent on size class | |
func adaptivePadding(_ edges: Edge.Set = .all, | |
_ length: SizeClassDependentValue<CGFloat>) -> some View { | |
modifier(SizeClassDependentPadding(edges: edges, length: length)) | |
} | |
} | |
/// The adaptive padding modifier implementation | |
public struct SizeClassDependentPadding: ViewModifier { | |
let edges: Edge.Set | |
let length: SizeClassDependentValue<CGFloat> | |
@Environment(\.horizontalSizeClass) var horizontalSizeClass | |
@Environment(\.verticalSizeClass) var verticalSizeClass | |
public init(edges: Edge.Set, length: SizeClassDependentValue<CGFloat>) { | |
self.edges = edges | |
self.length = length | |
} | |
public func body(content: Content) -> some View { | |
/// Match the size class used to the intended axis of the value. | |
/// This allows the value to determine what axis affects it, and means you can use | |
/// values that are affected by `horizontal` size classes for both horizontal or vertical padding, e.g. | |
/// your padding at the sides will often match padding at the top, but they are both based on the | |
/// screen width. | |
let length = self.length.value(for: length.axis == .horizontal ? horizontalSizeClass : verticalSizeClass) | |
return content.padding(edges, length) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment