SwiftUI HStack with equal Height - swift

I want the Text("111") to have the equal height of the VStack containing 2222... and 333....
struct Test7: View {
var body: some View
{ HStack (alignment: .top) {
Text( "111") // Shall have equal Height
.background(Color.red)
VStack(alignment: .leading){. // of this VStack
Text("2222222")
.background(Color.gray)
Text("333333333")
.background(Color.blue)
}
}
.background(Color.yellow)}
}
I tried with a GeometryReaderbut didn't get it to work

Here is possible approach using .alignmentGuide
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.

use .frame(maxHeight: .infinity)
var body: some View {
HStack(alignment: .top) {
Text("111")
.frame(maxHeight: .infinity)
.background(Color.red)
VStack(alignment: .leading) {
Text("2222222")
.frame(maxHeight: .infinity)
.background(Color.gray)
Text("333333333")
.frame(maxHeight: .infinity)
.background(Color.blue)
}
}.background(Color.yellow)
.frame(height: 50)
}
result: demo

Related

How to do a "reveal"-style collapse/expand animation in SwiftUI?

I'd like to implement an animation in SwiftUI that "reveals" the content of a view to enable expand/collapse functionality. The content of the view I want to collapse and expand is complex: It's not just a simple box, but it's a view hierarchy of dynamic height and content, including images and text.
I've experimented with different options, but it hasn't resulted in the desired effect. Usually what happens is that when I "expand", the whole view was shown right away with 0% opacity, then gradually faded in, with the buttons under the expanded view moving down at the same time. That's what happened when I was using a conditional if statement that actually added and removed the view. So that makes sense.
I then experimented with using a frame modifier: .frame(maxHeight: isExpanded ? .infinity : 0). But that resulted in the contents of the view being "squished" instead of revealed.
I made a paper prototype of what I want:
Any ideas on how to achieve this?
Something like this might work. You can modify the height of what you want to disclose to be 0 when hidden or nil when not so that it'll go for the height defined by the views. Make sure to clip the view afterwards so the contents are not visible outside of the frame's height when not disclosed.
struct ContentView: View {
#State private var isDisclosed = false
var body: some View {
VStack {
Button("Expand") {
withAnimation {
isDisclosed.toggle()
}
}
.buttonStyle(.plain)
VStack {
GroupBox {
Text("Hi")
}
GroupBox {
Text("More details here")
}
}
.frame(height: isDisclosed ? nil : 0, alignment: .top)
.clipped()
HStack {
Text("Cancel")
Spacer()
Text("Book")
}
}
.frame(maxWidth: .infinity)
.background(.thinMaterial)
.padding()
}
}
No, this wasn't trying to match your design, either. This was just to provide a sample way of creating the animation.
Consider the utilization of DisclosureGroup. The following code should be a good approach to your idea.
struct ContentView: View {
var body: some View {
List(0...20, id: \.self) { idx in
DisclosureGroup {
HStack {
Image(systemName: "person.circle.fill")
VStack(alignment: .leading) {
Text("ABC")
Text("Test Test")
}
}
HStack {
Image(systemName: "globe")
VStack(alignment: .leading) {
Text("ABC")
Text("X Y Z")
}
}
HStack {
Image(systemName: "water.waves")
VStack(alignment: .leading) {
Text("Bla Bla")
Text("123")
}
}
HStack{
Button("Cancel", role: .destructive) {}
Spacer()
Button("Book") {}
}
} label: {
HStack {
Spacer()
Text("Expand")
}
}
}
}
The result looks like:
I coded this in under 5 minutes. So of course the design can be optimized to your demands, but the core should be understandable.
import SwiftUI
struct TaskViewCollapsible: View {
#State private var isDisclosed = false
let header: String = "Review Page"
let url: String
let tasks: [String]
var body: some View {
VStack {
HStack {
VStack(spacing: 5) {
Text(header)
.font(.system(size: 22, weight: .semibold))
.foregroundColor(.black)
.padding(.top, 10)
.padding(.horizontal, 20)
.frame(maxWidth: .infinity, alignment: .leading)
Text(url)
.font(.system(size: 12, weight: .regular))
.foregroundColor(.black.opacity(0.4))
.padding(.horizontal, 20)
.frame(maxWidth: .infinity, alignment: .leading)
}
Spacer()
Image(systemName: self.isDisclosed ? "chevron.up" : "chevron.down")
.padding(.trailing)
.padding(.top, 10)
}
.onTapGesture {
withAnimation {
isDisclosed.toggle()
}
}
FetchTasks()
.padding(.horizontal, 20)
.padding(.bottom, 5)
.frame(height: isDisclosed ? nil : 0, alignment: .top)
.clipped()
}
.background(
RoundedRectangle(cornerRadius: 8)
.fill(.black.opacity(0.2))
)
.frame(maxWidth: .infinity)
.padding()
}
#ViewBuilder
func FetchTasks() -> some View {
ScrollView(.vertical, showsIndicators: true) {
VStack {
ForEach(0 ..< tasks.count, id: \.self) { value in
Text(tasks[value])
.font(.system(size: 16, weight: .regular))
.foregroundColor(.black)
.padding(.vertical, 0)
.frame(maxWidth: .infinity, alignment: .leading)
}
}
}
.frame(maxHeight: CGFloat(tasks.count) * 20)
}
}
struct TaskViewCollapsible_Previews: PreviewProvider {
static var previews: some View {
TaskViewCollapsible(url: "trello.com", tasks: ["Hello", "Hello", "Hello"])
}
}

SwiftUI Shape Scale Size Such that HStack size does not increase

I'm trying to make the circles fit into the HStack such that the HStack size does not increase.
How can I make the circles fit without specifying a fixed frame?
struct ContentView: View {
var body: some View {
NavigationView {
Form {
HStack {
Circle()
.fill(Color.red)
.aspectRatio(1, contentMode: .fit)
Text("Hello")
}
HStack {
Circle()
.fill(Color.blue)
.aspectRatio(1, contentMode: .fit)
Text("Hello")
}
}
}
}
}
Here is a sample of various containers to chose from. SwiftUI will do all the layout, automatically handle rotations and device resolutions.
struct CirclesView: View {
var body: some View {
VStack(spacing: 0) {
Label("Circles", systemImage: "circle").font(.system(size: 24, weight: .black, design: .rounded)).foregroundColor(.pink)
HStack {
Circle()
.foregroundColor(.yellow)
.frame(width: 32, height: 32)
Text("This is a yellow circle")
Spacer()
}
Circle()
.foregroundColor(.orange)
.shadow(radius: 10)
.frame(width: 75)
Divider()
HStack {
VStack {
Circle().foregroundColor(.blue)
Text("Blue").font(.title3)
HStack {
Circle().foregroundColor(.purple)
Text("Purple").font(.caption)
}
}
.padding()
.background(Color.yellow)
ZStack(alignment: Alignment(horizontal: .center, vertical: .center)) {
Circle().foregroundColor(.green)
Text("Green").foregroundColor(.primary)
}
}
}
}
}

Sizing multiple vstack width in scroll view, SwiftUI

I have a VStack:
VStack(alignment: .leading) {
Text(question)
.fontWeight(.heavy)
.font(.system(.footnote, design: .rounded))
.padding(.bottom)
Text(answer)
.fontWeight(.semibold)
.font(.system(.title, design: .rounded))
}
.padding(35)
.overlay(RoundedRectangle(cornerRadius: 12).stroke(Color("darkGrey"), lineWidth: 2))
.padding([.leading,.top,.trailing])
.frame(minWidth: UIScreen.main.bounds.width - 5,
minHeight: 50,
maxHeight: .infinity,
alignment: .topLeading
)
And Im using it in my View like:
ScrollView(.horizontal, showsIndicators: false) {
Group {
HStack {
questionCard(question: "Cats or Dogs", answer: "Dogs")
questionCard(question: "Cats or Dogs", answer: "Dogs")
}
}
}
This results in the following:
I'm trying to get the width of the box to the end of the screen (with .trailing space) and then when I scroll to the next, it should be the same width.
You could do something like the following:
struct ContentView: View {
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
Group {
HStack {
QuestionCard(question: "Cats or Dogs", answer: "Dogs")
QuestionCard(question: "Cats or Dogs", answer: "Dogs")
}
}
}
}
}
struct QuestionCard: View {
let question: String
let answer: String
var body: some View {
HStack {
Spacer()
VStack(alignment: .leading) {//<- Change the alignment if needed
Text(question)
.fontWeight(.heavy)
.font(.system(.footnote, design: .rounded))
.padding(.bottom)
Text(answer)
.fontWeight(.semibold)
.font(.system(.title, design: .rounded))
}
Spacer()
}
.padding(35)
.overlay(RoundedRectangle(cornerRadius: 12).stroke(Color.black, lineWidth: 2))
.padding([.leading, .top, .trailing])
.frame(minWidth: UIScreen.main.bounds.width - 5,
minHeight: 50,
maxHeight: .infinity,
alignment: .top
)
}
}
So basically I`m just using an HStack and two Spacers in order to use all of the available space.
It looks like this:

Make SwiftUI Rectangle same height or width as another Rectangle

For a SwiftUI layout in a macOS app, I have three Rectangles as shown below:
The code to produce this layout is:
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
HStack {
ZStack {
Rectangle()
.fill(Color.purple)
.frame(width: 20)
Text("1")
.font(.subheadline)
.foregroundColor(.white)
}
ZStack {
Rectangle()
.fill(Color.orange)
Text("2")
.font(.subheadline)
.foregroundColor(.white)
}
}
HStack {
ZStack {
Rectangle()
.fill(Color.red)
.frame(height: 20)
Text("3")
.font(.subheadline)
.foregroundColor(.white)
}
}
}
.frame(minWidth: 400, minHeight: 250)
}
}
My objective is for Rectangle 1 to be the same height as Rectangle 2 and for Rectangle 3 to be the same width as Rectangle 2. The size relationships between the rectangles should stay the same as the window size is changed. When done correctly, the final result should look like the following:
How can I accomplish this in SwiftUI?
Here is a working approach, based on view preferences. Tested with Xcode 11.4 / macOS 10.15.6
struct ViewWidthKey: PreferenceKey {
typealias Value = CGFloat
static var defaultValue: CGFloat { 0 }
static func reduce(value: inout Value, nextValue: () -> Value) {
value = value + nextValue()
}
}
struct ContentView: View {
#State private var boxWidth = CGFloat.zero
var body: some View {
VStack {
HStack {
ZStack {
Rectangle()
.fill(Color.purple)
.frame(width: 20)
Text("1")
.font(.subheadline)
.foregroundColor(.white)
}
ZStack {
Rectangle()
.fill(Color.orange)
Text("2")
.font(.subheadline)
.foregroundColor(.white)
}
.background(GeometryReader {
Color.clear.preference(key: ViewWidthKey.self,
value: $0.frame(in: .local).size.width) })
}
HStack {
ZStack {
Rectangle()
.fill(Color.red)
.frame(height: 20)
Text("3")
.font(.subheadline)
.foregroundColor(.white)
}.frame(width: boxWidth)
}.frame(maxWidth: .infinity, alignment: .bottomTrailing)
}
.onPreferenceChange(ViewWidthKey.self) { self.boxWidth = $0 }
.frame(minWidth: 400, minHeight: 250)
}
}

How to set GeometryReader height according to its children height in SwiftUI?

In my SwiftUI application, I have a VStack (VStack 1) container that get full screen height. Inside this VStack, I have an other VStack (VStack 2) that get height according to its childrens (Text), and I want to put it at the bottom of this parent (VStack 1). On the VStack 2, I need to put a GeometryReader to get the height of VStack (VStack 2). The problem is that the GeometryReader get automaticaly full screen height. So the VStack 2 is placed on the middle of the screen. It's not that I want. I don't know if I can set the height of the GeometryReader with the same height of the VStack 2?
My test code:
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
GeometryReader { geometry in
VStack {
Text("Text n°1")
Text("Text n°2")
Text("Text n°3")
Text("Text n°4")
}
.border(Color(.red))
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: nil, alignment: .bottom)
.border(Color(.black))
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .bottom)
}
}
The result:
That I want:
Thank you for your help.
Following is to set child view height to parent view when using GeometryReader
import SwiftUI
struct ContentView: View {
#State private var totalHeight = CGFloat(100) // no matter - just for static Preview !!
var body: some View {
HStack(alignment:.top) {
Text("Title 1")
GeometryReader { geo in
VStack(spacing: 10) {
Text("Subtitle 1")
.frame(maxWidth: .infinity)
Text("Subtitle 2")
.frame(maxWidth: .infinity)
Text("Subtitle 3")
.frame(maxWidth: .infinity)
}
.background(Color.blue)
.frame(width: geo.size.width * 0.6)
.background(GeometryReader {gp -> Color in
DispatchQueue.main.async {
// update on next cycle with calculated height of ZStack !!!
self.totalHeight = gp.size.height
}
return Color.clear
})
}
.background(Color.yellow)
}
.frame(maxWidth: .infinity)
.frame(height: totalHeight)
.background(Color.green)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Ref - https://stackoverflow.com/a/61315678/815864
I think you're overcomplicating it by using a GeometryReader. Have you tried just using a Spacer()?
struct ContentView: View {
var body: some View {
VStack {
Text("Haha")
Spacer()
VStack {
Text("1")
Text("2")
Text("3")
}
}
}
}