Get elements to fill width screen in LazyHStack - ios

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.

Related

SwiftUI: Build View from bottom alignment in scrollView

Is there a way to build UI elements in SwiftUI inside scroll view with bottom Alignment?
My use case: I have a screen where the screen has
Spacer (What ever is left after allocating below elements)
LogoView
Spacer() - 30
Some Text - 4/5 lines
Spacer() - 50 (this will be calculated off of GR size Height)
HStack with Two Button - This should be pinned to bottom of view / ScrollView
I would like to know how Can I pin the HStack view to ScrollView Bottom
I've replicated my setup and reproduced my problems in a Swift playground like so
struct ContentView: View {
var body: some View {
GeometryReader { gr in
ScrollView {
VStack {
Spacer()
Image(systemName: "applelogo")
.resizable()
.frame(width: gr.size.width * 0.5, height: gr.size.height * 0.3, alignment: .center)
Spacer().padding(.bottom, gr.size.height * 0.01)
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")
.fontWeight(.regular)
.foregroundColor(.green)
.multilineTextAlignment(.center)
.padding(.top, gr.size.height * 0.05)
.padding(.horizontal, 40)
.layoutPriority(1)
Spacer()
.frame(minHeight: gr.size.height * 0.12)
HStack {
Button(action: {
}, label: {
Text("Button 1")
})
.foregroundColor(Color.white)
.padding(.vertical)
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.blue)
.cornerRadius(8)
Button(action: {
}, label: {
Text("Button 2")
})
.foregroundColor(Color.white)
.padding(.vertical)
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.blue)
.cornerRadius(8)
}
.padding(.horizontal, 20)
}
//.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
}
}
I understand that when scrollView is introduced in SwiftUI view Spacer length is changed to Zero, Would like to know what's the best way to achieve this
struct SampleView: View {
var body: some View {
GeometryReader { gr in
VStack {
ScrollView {
VStack {
// Fills whatever space is left
Rectangle()
.foregroundColor(.clear)
Image(systemName: "applelogo")
.resizable()
.frame(width: gr.size.width * 0.5, height: gr.size.height * 0.3, alignment: .center)
.padding(.bottom, gr.size.height * 0.06)
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")
.fontWeight(.regular)
.foregroundColor(.green)
.multilineTextAlignment(.center)
.padding(.horizontal, 40)
.layoutPriority(1)
// Fills 12 %
Rectangle()
.frame(height: gr.size.height * 0.12)
.foregroundColor(.clear)
HStack {
Button(action: {
}, label: {
Text("Button 1")
})
.foregroundColor(Color.white)
.padding(.vertical)
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.blue)
.cornerRadius(8)
Button(action: {
}, label: {
Text("Button 2")
})
.foregroundColor(Color.white)
.padding(.vertical)
.frame(minWidth: 0, maxWidth: .infinity)
.background(Color.blue)
.cornerRadius(8)
}
.padding(.horizontal, 20)
.padding(.bottom, 20)
}
// Makes the content stretch to fill the whole scroll view, but won't be limited (it can grow beyond if needed)
.frame(minHeight: gr.size.height)
}
}
}
}
}
I wanted the scrollview to appear bottom to top instead of top to bottom. This had my answer: https://www.thirdrocktechkno.com/blog/implementing-reversed-scrolling-behaviour-in-swiftui/
TLDR: .rotationEffect(Angle(degrees: 180)).scaleEffect(x: -1.0, y: 1.0, anchor: .center) on the scrollview AND on the items in the scrollview

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.

How do I do this simple layout in SwiftUI?

I need a MainView that is centered on the screen, and a flexible HeaderView that takes up the remaining space between the MainView and the top of the screen (see below). How do I accomplish this in SwiftUI?
Starter code:
struct TestView: View {
var body: some View {
ZStack {
//Center Line
Divider()
VStack {
Text("HeaderView")
.border(Color.orange, width: 6)
Text("MainView")
.frame(width: 400, height: 200, alignment: .center)
.border(Color.red, width: 6)
}
}
}
}
VStack {
// Let the HeaderView expand to whatever is available, in both directions
Text("HeaderView")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.border(Color.orange, width: 6)
Text("MainView")
.frame(maxWidth: .infinity) // Added to allow this view to expand horizontally
.frame(height: 200) // alignment is not needed here.
.border(Color.red, width: 6)
// And then add a Spacer() at the end that also has flexible height
Spacer()
.frame(maxHeight: .infinity)
}
Since Main is fixed in height, it will get its requested height first. Then since Header and Spacer are equally prioritized and flexible, they will each get half of what remains, causing Main to be centered vertically.

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)

VStack inside ScrollView bottom alignment in SwiftUI

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

Resources