VStack inside ScrollView bottom alignment in SwiftUI - ios

Currently building a chat application and I need new messages to appear at the bottom of the screen. I also need to have messages aligned to the bottom. However, using VStack inside ScrollView features top alignment by default.
ScrollView {
VStack(alignment: .leading, spacing: 16) {
Spacer()
.frame(minWidth: 0, maxWidth: .infinity, minHeight:0, maxHeight: .infinity, alignment: Alignment.topLeading)
ForEach(notList, id: \.self) { not in
NotRow(not: not)
}
}
.padding()
.frame(minWidth: 0, maxWidth: .infinity, minHeight:0, alignment: Alignment.topLeading)
}
What should I do to fix this?

A bit late to answer that one but it might help someone else. You could use a good old double rotation trick to achieve the result you want. Other solutions does not work because the maximum size inside a scroll view is undefined as it depends on its child views to compute its own content size.
ScrollView {
VStack(alignment: .leading, spacing: 16) {
Spacer()
.frame(minWidth: 0, maxWidth: .infinity, minHeight:0, maxHeight: .infinity, alignment: Alignment.topLeading)
ForEach(notList, id: \.self) { not in
NotRow(not: not)
}
}
.padding()
.rotationEffect(Angle(degrees: 180))
}
.rotationEffect(Angle(degrees: 180))

ScrollView does not propose the same height to its children (VStack in this case) as it was proposed to it. It just asks for minimal size. The solution is to make its direct child respond with the minimum height which was proposed to ScrollView itself. We can achieve this by reading the proposed height via GeometryReader and setting that as minimum height of the child.
GeometryReader { reader in
ScrollView {
VStack {
/// Spacer() or Color.red will now behave as expected
}
.frame(minHeight: reader.size.height)
}
}

I think you're missing a maxHeight in VStack
VStack(alignment: .leading) {
Spacer().frame(maxWidth: .infinity)
// content here
}
.frame(maxHeight: .infinity) // <- this

If you remove Spacer() and put it after the closing braces of your ForEach, it will fix it. Here is how your updated code will look like:
ScrollView {
VStack(alignment: .leading, spacing: 16) {
.frame(minWidth: 0, maxWidth: .infinity, minHeight:0, maxHeight: .infinity, alignment: Alignment.topLeading)
ForEach(notList, id: \.self) { not in
NotRow(not: not)
}
Spacer()
}
.padding()
.frame(minWidth: 0, maxWidth: .infinity, minHeight:0, alignment: Alignment.topLeading)
}

Try changing all of your Alignment.topLeadings to Alignment.bottomLeading

Related

Get elements to fill width screen in LazyHStack

I have a LazyHstack in a horizontal scrollview :
struct HorizontalSpotsList: View {
var spots: [Spot]
var body: some View {
ScrollView(.horizontal){
LazyHStack(alignment: .center, spacing: 0){
if (!spots.isEmpty){
ForEach(spots.prefix(upTo: 3) , id: \.self) { spot in
Text("Test").frame(maxWidth: .infinity)
.background(RoundedRectangle(cornerRadius: 25)
.fill(Color.white)).border(Color.blue)
}
}
}.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity).border(Color.red)
}.frame(width: .infinity, height: 130).border(Color.green)
}
}
How can i get my lazyHStack elements to fill all the width screen ?
My LazyHstack doesn't seems to fill the width, i tried to apply .frame(width: .infinity) or .frame(maxWidth: .infinity) to different combinaisons of my scrollView / LazyHstack / Text element but it doesn't seems to work.
You could use Geometry Reader. You would have to apply a height however.
GeometryReader { geo in
ScrollView {
LazyHStack {
content
}.frame(width: geo.size.width, height: #)
}
}
Try adding Spacer() in between the Text elements.

Set the width of an Image to half the width of its HStack container view in SwiftUI?

So I just want to do something pretty simple which is have an Image take half the size of its container and a Vstack the other half.
Im currently doing this to force both views to take as much space as they can with maxWidth: .infinity, so they end up taking equal amounts of space:
struct ContentView: View {
var body: some View {
HStack {
Image("landscape")
.resizable()
.scaledToFill()
.frame(maxWidth: .infinity, maxHeight: 170)
VStack {
Text("Test")
Text("Test 2")
}
.frame(maxWidth: .infinity)
}
.frame(maxWidth: .infinity, maxHeight: 170)
.background(Color.red)
.padding()
}
}
But it ends up in this poor result, sadly:
Any tips? I saw GeometryReader might come in handy here but seems to push my Views all the way to the top and end up with unwanted results.
you need to add a fixedSize() modifier to HStack and keep it's vertical to true.
var body: some View {
HStack {
Image("1")
.resizable()
.scaledToFill()
.frame(maxWidth: .infinity, maxHeight: 170)
VStack {
Text("Test")
Text("Test 2")
}
.frame(maxWidth: .infinity)
}
.frame(maxWidth: .infinity, maxHeight: 170)
.background(Color.red)
.padding()
.fixedSize(horizontal: false, vertical: true)
}
you can read more about fixedsize() here
Now your HStack will be like this

alignmentGuide() not overriding child views

I initially align my child views to the .top by giving alignment: .top to my parent HStack.
I then want to have one child to the left (.leading) and the other child to the right (.trailing).
However this is not working, they are both in the center (screenshot below):
import SwiftUI
struct TestView: View {
var body: some View {
HStack{
Text("First")
.alignmentGuide(.leading) { d in d[.leading] }
Text("Second")
.alignmentGuide(.trailing) { d in d[.trailing] }
}.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .top)
.padding(.top, 50)
}
}
Any idea why?
I then want to have one child to the left (.leading) and the other child to the right (.trailing).
It can be achieved in much simpler way
HStack{
Text("First")
Spacer()
Text("Second")
}.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)

SWIFTUI How to capture VStack height with geometry (after the VStack was displayed)?

I'd like to ask you some help. I'm trying to capture VStack height using geometry and then based on that VStack height value, calculate its child element's height (inside VStack).
Image of my current view of VStack
I used .frame outside of VStack to fill the whole screen. Then I used .border to visually check if it actually fills the screen (it works fine)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: Alignment.topLeading)
The problem can be seen inside the VStack, the height is displayed as GCFloat 734. Even though the border size is a lot bigger.
struct BodyView: View {
#State var stackHeight : CGFloat = 0
var body: some View {
GeometryReader { geometry in
VStack {
Text("VStack size: \(self.stackHeight)")
.bold()
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal)
.background(Color.green)
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: Alignment.topLeading)
.onAppear{
self.stackHeight = geometry.size.height
}
.border(Color.purple, width: 5)
}
}
}
struct BodyView_Previews: PreviewProvider {
static var previews: some View {
BodyView()
}
}
How could I capture the actual size of the VStack when it finishes loading?
When I trigger (onAppear) the VStack height appears correct, however I need it to be captured instantly.
If I understand your questions, you're very close — you just need to use to GeometryProxy type:
import SwiftUI
struct BodyView: View {
var body: some View {
GeometryReader { geometry in
VStack {
Text("VStack size: \(geometry.size.height)") // no need for state var anymore
.bold()
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal)
.background(Color.green)
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: Alignment.topLeading)
.border(Color.purple, width: 5)
}
}
}
struct BodyView_Previews: PreviewProvider {
static var previews: some View {
BodyView()
}
}
A GeometryReader will lay itself out again if its size changes.

SwiftUI. How to set shrink behavior for Spacer?

How to set default spacing between rgb views (100pt) if their container (VStack) not conflicts with bottom black view.(like iPhone 11 Pro Max). BUT shrinks if there is no space for 100p height.(like iPhone SE on the screenshot)
My code:
struct ContentView: View {
var body: some View {
VStack(spacing: 0) {
VStack(spacing: 0) {
Rectangle()
.foregroundColor(.red)
.frame(height: 100)
Spacer()
.frame(minHeight: 10, maxHeight: 100)
Rectangle()
.foregroundColor(.green)
.frame(height: 100)
Spacer()
.frame(minHeight: 10, maxHeight: 100)
Rectangle()
.foregroundColor(.blue)
.frame(height: 100)
}
Spacer()
.frame(minHeight: 10, maxHeight: 600)
Rectangle() // keyboard
.frame(height: 200)
}
}
}
So the problem is:
Spacers with maxHeight: 100 have height = 10 (not 100) on iPhone 11 Pro Max. (BUT space between black view and VStack allows it)
How to make behavior I explained?
You need to use idealHeight alongside with .fixedSize modifier for Spacers:
Spacer()
.frame(minHeight: 10, idealHeight: 100, maxHeight: 600)
.fixedSize()
Use Spacer(minLength: 10) for the last spacer.
struct ContentView: View {
var body: some View {
VStack(spacing: 0) {
VStack(spacing: 0) {
Rectangle()
.foregroundColor(.red)
.frame(height: 100)
Spacer()
.frame(minHeight: 10, maxHeight: 100)
Rectangle()
.foregroundColor(.green)
.frame(height: 100)
Spacer()
.frame(minHeight: 10, maxHeight: 100)
Rectangle()
.foregroundColor(.blue)
.frame(height: 100)
}
Spacer(minLength: 10)
Rectangle() // keyboard
.frame(height: 200)
}
}
}
The problem in your code is that when you wrap a Spacer inside a frame like Spacer().frame(minHeight: 10, maxHeight: 600), it is first considered as a frame, then a Spacer inside that frame. And a frame has equal default layout priority as other views. So the parent will propose it the same amount of space as the inner VStack. By removing the frame modifier, the Spacer has the least layout priority, so the inner VStack will take as much space as possible except the minimum 10 points claimed by the spacer and 200 points for the rectangle.
I ran into a similar problem...
This fixed it for me:
Spacer().frame(minWidth: 0)

Resources