Skip to content

Instantly share code, notes, and snippets.

@marcpalmer
Last active May 12, 2021 12:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save marcpalmer/73ea6178228cbda090a088561c2b05ed to your computer and use it in GitHub Desktop.
Save marcpalmer/73ea6178228cbda090a088561c2b05ed to your computer and use it in GitHub Desktop.
Example of adaptive values in SwiftUI where size classes determine the value you want to use
import Foundation
import SwiftUI
/// A value that can have different values depending on compact or regular size class in a single axis.
///
/// Usage example:
///
/// ```
/// static let textPadding = SizeClassDependentValue(compact: 20, regular: 50)
///
/// var body: some View {
/// HStack {
/// Text("Hello").adaptivePadding(Self.textPadding)
/// }
/// }
/// ```
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
}
/// 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)
}
}
/// Add negation support for floating point values, so that you can use e.g. `-paddingAmount`
extension SizeClassDependentValue where Value: FloatingPoint {
public 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.
extension View {
/// Like `padding` but using a value that is dependent on size class
@inlinable 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