Swiftui Hstack with Equal Height

SwiftUI HStack with equal Height

Here is possible approach using .alignmentGuide

demo

struct Test7: View {
@State
private var height: CGFloat = .zero // < calculable height

var body: some View {
HStack (alignment: .top) {
Text( "111")
.frame(minHeight: height) // in Preview default is visible
.background(Color.red)

VStack(alignment: .leading) {
Text("2222222")
.background(Color.gray)
Text("333333333")
.background(Color.blue)
}
.alignmentGuide(.top, computeValue: { d in
DispatchQueue.main.async { // << dynamically detected - needs to be async !!
self.height = max(d.height, self.height)
}
return d[.top]
})
}
.background(Color.yellow)
}
}

Note: real result is visible only in LivePreview, because height is calculated dynamically and assign in next rendering cycle to avoid conflicts on @State.

SwiftUI HStack elements with equal height

You can set the max value in the ViewHeightKey preference key:

struct ViewHeightKey: PreferenceKey {
static var defaultValue: CGFloat { 0 }
static func reduce(value: inout Value, nextValue: () -> Value) {
value = max(value, nextValue()) // set the `max` value (from both buttons)
}
}

and then read view height from both buttons and force vertical fixedSize:

struct DynamicallyScalingView: View {
@State private var labelHeight = CGFloat.zero

var body: some View {
HStack {
Button(action: {}, label: {
Text("SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME TEXT SOME")
})
.foregroundColor(Color.white)
.padding(.vertical)
.frame(minWidth: 0, maxWidth: .infinity)
.frame(minHeight: labelHeight) // min height for both buttons
.background(Color.blue)
.cornerRadius(8)
.fixedSize(horizontal: false, vertical: true) // expand vertically
.background(GeometryReader { // apply to both buttons
Color.clear
.preference(
key: ViewHeightKey.self,
value: $0.frame(in: .local).size.height
)
})

Button(action: {}, label: {
Text("jahlsd")
})
.foregroundColor(Color.white)
.padding(.vertical)
.frame(minWidth: 0, maxWidth: .infinity)
.frame(minHeight: labelHeight)
.background(Color.blue)
.cornerRadius(8)
.fixedSize(horizontal: false, vertical: true)
.background(GeometryReader {
Color.clear
.preference(
key: ViewHeightKey.self,
value: $0.frame(in: .local).size.height
)
})
}
.onPreferenceChange(ViewHeightKey.self) {
self.labelHeight = $0
}
.padding(.horizontal)
}
}

Note: as the buttons are similar now, the next step would be to extract them as another component to avoid duplication.

How do I make all views the same height in a SwiftUI View with an HStack?

Here is possible (seems simplest) approach. Tested with Xcode 12 / iOS 14

demo

struct DemoView: View {
var dataSource = [1, 0, 34, 12, 59, 44]

var body: some View {
HStack(alignment: .top) {
Spacer()
/// i want these to all be the same height
ForEach(0 ..< 6) { index in
VStack {
Text("\(index)")
Color.clear
.frame(width: 20, height: CGFloat(dataSource.max() ?? 0))
.overlay(
Color.orange
.frame(height: CGFloat(self.dataSource[index]))
, alignment: .top)
Text("\(dataSource[index])")
}
}
Spacer()
}
}
}

Align heights of HStack members

Wrap your Image in a Text. Since your image is from SF Symbols, SwiftUI will scale it to match the dynamic type size. (I'm not sure how it will scale other images.)

VStack {
let background = RoundedRectangle(cornerRadius: 10)
.foregroundColor(.red)

ForEach(Font.TextStyle.allCases, id: \.self) { style in
HStack {
Text("\(style)" as String)
.padding()
.background(background)

Spacer()

Text(Image(systemName: "checkmark.seal.fill"))
.padding()
.background(background)
}
.font(.system(style))
}
}

screen shot showing all dynamic type sizes

SwiftUI HStack fill whole width with equal spacing

The frame layout modifier, with .infinity for the maxWidth parameter can be used to achieve this, without the need for an additional Shape View.

struct ContentView: View {
var data = ["View", "V", "View Long"]

var body: some View {
VStack {

// This will be as small as possible to fit the data
HStack {
ForEach(data, id: \.self) { item in
Text(item)
.border(Color.red)
}
}

// The frame modifier allows the view to expand horizontally
HStack {
ForEach(data, id: \.self) { item in
Text(item)
.frame(maxWidth: .infinity)
.border(Color.red)
}
}
}
}
}

Comparison using .frame modifier

Make a grid of buttons of same width and height in SwiftUI

How about using a GeometryReader(docs here)? They give the dimensions of their parent view to the children. Here's a partial implementation based on what you already have:

import SwiftUI

struct ContentView : View {
var body: some View {
VStack {
Text("0")
NumpadView()
}
}
}

struct NumpadView : View {

let rows: Length = 5
let columns: Length = 4
let spacing: Length = 10

var horizontalEdges: Length {
return columns - 1
}

var verticalEdges: Length {
return rows - 1
}

func getItemWidth(containerWidth: Length) -> Length {
return (containerWidth - spacing * horizontalEdges) / columns
}

func getItemHeight(containerHeight: Length) -> Length {
return (containerHeight - spacing * verticalEdges) / rows
}

var body: some View {
GeometryReader { geometry in

VStack(alignment: .center, spacing: self.spacing) {
HStack(alignment: .center, spacing: self.spacing) {
Button(action: {}) {
Text("7")
.frame(width: self.getItemWidth(containerWidth: geometry.size.width), height: self.getItemHeight(containerHeight: geometry.size.height))
.background(Color.green)
}

Button(action: {}) {
Text("8")
.frame(width: self.getItemWidth(containerWidth: geometry.size.width), height: self.getItemHeight(containerHeight: geometry.size.height))
.background(Color.yellow)
}

Button(action: {}) {
Text("9")
.frame(width: self.getItemWidth(containerWidth: geometry.size.width), height: self.getItemHeight(containerHeight: geometry.size.height))
.background(Color.green)
}
}

HStack(alignment: .center, spacing: self.spacing) {
Button(action: {}) {
Text("4")
.frame(width: self.getItemWidth(containerWidth: geometry.size.width), height: self.getItemHeight(containerHeight: geometry.size.height))
.background(Color.yellow)
}

Button(action: {}) {
Text("5")
.frame(width: self.getItemWidth(containerWidth: geometry.size.width), height: self.getItemHeight(containerHeight: geometry.size.height))
.background(Color.green)
}

Button(action: {}) {
Text("6")
.frame(width: self.getItemWidth(containerWidth: geometry.size.width), height: self.getItemHeight(containerHeight: geometry.size.height))
.background(Color.yellow)
}
}

HStack(alignment: .center, spacing: self.spacing) {
Button(action: {}) {
Text("1")
.frame(width: self.getItemWidth(containerWidth: geometry.size.width), height: self.getItemHeight(containerHeight: geometry.size.height))
.background(Color.green)
}

Button(action: {}) {
Text("2")
.frame(width: self.getItemWidth(containerWidth: geometry.size.width), height: self.getItemHeight(containerHeight: geometry.size.height))
.background(Color.yellow)
}

Button(action: {}) {
Text("3")
.frame(width: self.getItemWidth(containerWidth: geometry.size.width), height: self.getItemHeight(containerHeight: geometry.size.height))
.background(Color.green)
}
}

HStack(alignment: .center, spacing: self.spacing) {
Button(action: {}) {
Text("0")
.frame(width: self.getItemWidth(containerWidth: geometry.size.width) * 2 + self.spacing, height: self.getItemHeight(containerHeight: geometry.size.height))
.background(Color.yellow)
}

Button(action: {}) {
Text(".")
.frame(width: self.getItemWidth(containerWidth: geometry.size.width), height: self.getItemHeight(containerHeight: geometry.size.height))
.background(Color.yellow)
}
}

}.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
}

#if DEBUG
struct ContentView_Previews : PreviewProvider {
static var previews: some View {
ContentView()
}
}
#endif

Looks like this: Live preview

SwiftUI: Two buttons with the same width/height

Here is run-time based approach without hard-coding. The idea is to detect max width of available buttons during drawing and apply it to other buttons on next update cycle (anyway it appears fluently and invisible for user).

Tested with Xcode 11.4 / tvOS 13.4

Required: Simulator or Device for testing, due to used run-time dispatched update

demo

struct ButtonsView: View {
@State private var maxWidth: CGFloat = .zero
var body: some View {
VStack {
Button(action: { print("PLAY tapped") }){
Text("Play")
.background(rectReader($maxWidth))
.frame(minWidth: maxWidth)
}.id(maxWidth) // !! to rebuild button (tvOS specific)

Button(action: { print("PAUSE tapped") }) {
Text("Pause Long Demo")
.background(rectReader($maxWidth))
.frame(minWidth: maxWidth)
}.id(maxWidth) // !! to rebuild button (tvOS specific)
}
}

// helper reader of view intrinsic width
private func rectReader(_ binding: Binding<CGFloat>) -> some View {
return GeometryReader { gp -> Color in
DispatchQueue.main.async {
binding.wrappedValue = max(binding.wrappedValue, gp.frame(in: .local).width)
}
return Color.clear
}
}

}

Make a View the same size as another View which has a dynamic size in SwiftUI

As @Asperi pointed out, this solves the problem: stackoverflow.com/a/62451599/12299030

This is how I solved it in this case:

import SwiftUI

struct TestScreen: View {
@State private var imageHeight = CGFloat.zero

var body: some View {
VStack {
HStack {
Image("blue-test-image")
.resizable()
.scaledToFit()
.frame(maxWidth: .infinity)
.background(GeometryReader {
Color.clear.preference(
key: ViewHeightKeyTestScreen.self,
value: $0.frame(in: .local).size.height
)
})
VStack {
Text("Some Text")
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.gray)
Text("Other Text")
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.gray)
}
.frame(maxWidth: .infinity, minHeight: self.imageHeight, maxHeight: self.imageHeight)
}
.onPreferenceChange(ViewHeightKeyTestScreen.self) {
self.imageHeight = $0
}
Spacer()
}
.padding()
}
}

struct ViewHeightKeyTestScreen: PreferenceKey {
static var defaultValue: CGFloat { 0 }
static func reduce(value: inout Value, nextValue: () -> Value) {
value += nextValue()
}
}

struct TestScreen_Previews: PreviewProvider {
static var previews: some View {
TestScreen()
}
}



Related Topics



Leave a reply



Submit