You can set the scalable width/height with the @ScaledMetric property wrapper that is available since iOS 14 or macOS 11.
For example, define this view first:
struct ScaledFrame<Content>: View where Content: View {
@ScaledMetric private var width: Double
@ScaledMetric private var height: Double
private var alignment: Alignment
private var content: () -> Content
init(
width: ScaledMetric<Double>? = nil,
height: ScaledMetric<Double>? = nil,
alignment: Alignment = .center,
content: @escaping () -> Content
) {
_width = width ?? ScaledMetric(wrappedValue: -1)
_height = height ?? ScaledMetric(wrappedValue: -1)
self.alignment = alignment
self.content = content
}
var body: some View {
content().frame(
width: width > 0 ? width : nil,
height: height > 0 ? height : nil,
alignment: alignment)
}
}
// For convenience :)
extension View {
func scaledFrame(
width: Double?,
height: Double?,
relativeTo textStyle: Font.TextStyle,
alignment: Alignment = .center
) -> some View {
ScaledFrame(
width: width.flatMap { ScaledMetric(wrappedValue: $0, relativeTo: textStyle) },
height: height.flatMap { ScaledMetric(wrappedValue: $0, relativeTo: textStyle) }) {
self
}
}
}
Then you can use it like this:
struct Previews_ScaledFrame_Previews: PreviewProvider {
static var previews: some View {
let size = 30.0
let textStyle = Font.TextStyle.body
let scaledSize = ScaledMetric(wrappedValue: size, relativeTo: textStyle)
return VStack(alignment: .leading, spacing: 0) {
HStack {
// Use it directly like this
ScaledFrame(width: scaledSize, height: scaledSize) {
Image(systemName: "folder")
}
Text("Hello")
}
HStack {
// Or use it via the method like this
Image(systemName: "mappin")
.scaledFrame(
width: size,
height: size,
relativeTo: textStyle)
Text("World")
}
}
}
}
These are how it looks like when using the Dynamic Type:
xSmall

Large

AX5
