Prevent view from overflowing in both directions - swift

I'm trying to build a user-expandable view that will show more of its content by increasing its height. To accomplish this I will add .clipped() so that the content shown out of its bounds will be hidden, just like you would add overflow: hidden; in CSS.
However, it seems like by default VStack is centering its children, so when the height is smaller than the sum of the children's height, they overflow in both the top and the bottom.
Here is an example of what I'm talking about:
struct ExandableView: View {
var body: some View {
VStack(spacing: 0) {
Rectangle()
.fill(Color.red)
.frame(height: 50)
.padding(.horizontal)
Rectangle()
.fill(Color.green)
.frame(height: 50)
.padding(.horizontal)
}
.frame(height: 90)
.background(Color.blue)
}
}
Is there any way to make it behave so that the red item (the first) is always inside the blue container and the children can only overflow from the bottom?

Change frame alignment according to ExandableView. like this
struct ExandableView: View {
var body: some View {
// Other VStack code
}
.frame(height: 90, alignment: isExpand ? .top : .center) //<--Here
.background(Color.blue)
}
}

Related

Override HStack alignment for one child

Is there a way to dynamically override the alignment property of an HStack in an individual element?
Consider this scenario
There is a parent HStack with alignment = bottom
There are 3 elements inside the HStack of different sizes
I want the 3rd element to align to the top of the HStack. This alignment is different from the Hstack's bottom alignment
var body: some View {
HStack(alignment: .bottom) {
Rectangle()
.fill(.yellow)
.frame(height: 100)
Rectangle()
.fill(.blue)
.frame(height: 20)
// I want this to go to the top of the HStack
Rectangle()
.fill(.green)
.frame(height: 50)
}
.background {
Color.red
}
}
I'm trying to get the HStack to respect the highest height of 100 and just alter the last element's alignment.
I've tried wrapping the 3rd element in another stack but that only works if I specify a maxHeight equal to the tallest height among the parent's children, 100.
This means these rectangles have to know about their sibling elements.
HStack {
Rectangle()
.fill(.green)
.frame(height: 50)
}
.frame(maxHeight: 100, alignment: .top)
You could try this:
var body: some View {
HStack(alignment: .bottom) {
Rectangle()
.fill(.yellow)
.frame(height: 100)
Rectangle()
.fill(.blue)
.frame(height: 20)
VStack {
Rectangle()
.fill(.green)
.frame(height: 50)
Spacer()
}
}
.background {
Color.red
}
}
If, for some reason, you want to limit the range of how much space can the Spacer take up, you can add a modifier as per following example:
VStack {
Rectangle()
.fill(.green)
.frame(height: 50)
Spacer()
.frame(minHeight: 10, maxHeight: 50)
}

How to make a clear SwiftUI view block the scrollview underneath it

So I have a ZStack that contains a ScrollView on the bottom and an HStack that's aligned at the top. It looks something like this:
ZStack {
ScrollView {
//Content
}
VStack {
HStack {
Spacer()
Circle()
.frame(width: 30, height: 30)
Spacer()
}
Spacer()
}
}
Now, I want that HStack to block any interaction with the ScrollView beneath it. I've noticed that when I set the background on the HStack to a non-clear color, it behaves as I'd like it to. However, if the background is clear, then the touches go through the HStack and interact with the ScrollView beneath it. I've also tried using .allowsHitTesting(true) on the HStack with no luck.
Any help is much appreciated!
https://i.stack.imgur.com/5Jzby.png
"...I've noticed that when I set the background on the HStack to a non-clear color, it behaves as I'd like it to.", then you could use Color.white.opacity(0.001)
instead of Color.clear or non-clear color
You can easily accomplish this by putting it in a VStack instead of a ZStack. The touches will be ignored as it's above the ScrollView. You can also have a look at .layoutPriority(), it may also be useful. Documentation is here.
struct ContentView: View {
var body: some View {
VStack {
HStack {
Spacer()
Circle()
.foregroundColor(Color.blue)
.frame(width: 30, height: 30)
Spacer()
}
.padding()
// or this does the same thing as the HStack two spacers
/*
Circle()
.foregroundColor(Color.blue)
.frame(width: 30, height: 30)
.frame(minWidth: 0, maxWidth: .infinity)
*/
Spacer()
ScrollView {
ForEach(1..<11) { value in
Text("\(value)")
}
}
}
}
}

How to move this to the top?

I just learned how to implement specific rounded corners, but now it seems nothing will align to the top of the screen, even with spacers. How can I get it to align to the top of the screen?
Additionally, I would like the green to ignore the top safe area, but it wasn't doing that earlier either.
import SwiftUI
struct Dashboard: View {
#State var bottomLeft: CGFloat = 25
var body: some View {
ZStack {
Color("background")
.edgesIgnoringSafeArea(.all)
VStack {
VStack {
Text("Good Morning, Sarah")
.font(Font.system(size: 36))
.foregroundColor(.standaloneLabelColor)
.fontWeight(.semibold)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal)
Text("We're glad to see you and hope you're doing well. Let's take on the day.")
.font(Font.system(size: 20))
.foregroundColor(.standaloneLabelColor)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal)
.padding(.bottom)
}
.background(Color.appColor)
.cornerRadius(bottomLeft, corners: .bottomLeft)
.padding(.bottom, 10)
}
Spacer()
}
}
}
Thanks in advance.
You can set the alignment on the ZStack to .top. You can then also remove the Spacer.
A Spacer does nothing in a ZStack - since it's on its own layer. But setting the alignment on the ZStack will align all views to the top. If this is not what you want, you can also just put the Spacer in the inner VStack and the contents of that will all be aligned to the top also.
Code:
ZStack(alignment: .top) {
Color("background")
.edgesIgnoringSafeArea(.all)
VStack {
/* ... */
}
}
Also, to ignore the safe area at the top when applying the rounded corners, you should clip the corners and use ignoresSafeArea() within background. This ensures that you are only ignoring the safe area for the background, and not the whole view. Do the following:
.background(
Color.appColor
.cornerRadius(bottomLeft, corners: .bottomLeft)
.ignoresSafeArea()
)
// .background(Color.appColor)
// .cornerRadius(bottomLeft, corners: .bottomLeft)
.padding(.bottom, 10)
I had to remove some bits from your code as they were producing errors on my end. Working with what I had, you needed to add a Spacer() below the appropriate VStack.
Since your content was stored in a VStack embedded in another VStack, the outer VStack was essentially where the entire view lived. Putting a Spacer beneath this pushes it up to the top of the screen.
You can additionally add padding to the top of the VStack to move the view lower if you do not want it touching the top of the screen.
Code below:
import SwiftUI
struct ContentView: View {
#State var bottomLeft: CGFloat = 25
var body: some View {
ZStack {
Color("background")
.edgesIgnoringSafeArea(.all)
VStack {
VStack {
Text("Good Morning, Sarah")
.font(Font.system(size: 36))
.foregroundColor(Color.blue)
.fontWeight(.semibold)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal)
Text("We're glad to see you and hope you're doing well. Let's take on the day.")
.font(Font.system(size: 20))
.foregroundColor(Color.blue)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal)
.padding(.bottom)
}
.background(Color.green)
.cornerRadius(bottomLeft)
.padding(.bottom, 10)
Spacer() //Added a spacer here
}
}
}
}

How can I create an "unfilled frame" in SwiftUI?

I'm sure this is something super-simple, but I cannot seem to figure it out. I am trying to create a "widget" that consists of three lines of text, stacked vertically. This information should be placed inside a "frame" or "border" so it resembles a card. There will be a row of these cards that will scroll horizontally.
Believe it or not, the only part of this I cannot figure out is how to draw the border around the widget. I've tried .border, but that snugs the border right up against the text. I know I can add padding, but what I really need is a fixed-size card so each element in the scrolling list is identical.
I've come closest using this:
.frame(width: geometry.size.width/1.3, height: 200)
.background(Color.white)
.border(Color.blue)
.cornerRadius(20)
...but the corners are all clipped. For reference, here's the complete code listing:
struct AccountTile: View {
var body: some View {
GeometryReader { geometry in
VStack(alignment: .leading, spacing: 8) {
Text("Account Balance").font(.largeTitle)
Text("Account Name").font(.headline)
HStack(spacing: 0) {
Text("There are ").font(.caption).foregroundColor(.gray)
Text("6 ").font(.caption).fontWeight(.bold).foregroundColor(.blue)
Text("unreconciled transactions.").font(.caption).foregroundColor(.gray)
}
}
.frame(width: geometry.size.width/1.3, height: 200)
.background(Color.white)
.border(Color.blue)
.cornerRadius(20)
}
}
}
...and here's what that code is producing:
This is almost what I'm looking for - I just need the border to be complete.
Use some padding and overlay to create your border. Here is the code (:
struct AccountTile: View {
var body: some View {
GeometryReader { geometry in
VStack(alignment: .leading, spacing: 8) {
Text("Account Balance").font(.largeTitle)
Text("Account Name").font(.headline)
HStack(spacing: 0) {
Text("There are ").font(.caption).foregroundColor(.gray)
Text("6 ").font(.caption).fontWeight(.bold).foregroundColor(.blue)
Text("unreconciled transactions.").font(.caption).foregroundColor(.gray)
}
}.frame(width: geometry.size.width/1.3, height: 200)
.background(Color.white)
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(Color.blue, lineWidth: 2))
}
}
}

Resize image to screen width using `Vstack` while maintaining aspect ratio

I am working with VStack to show an image (they will be several with different sizes from a json)
I need to show it to occupy the screen width (the width of the vstack and maintaining aspect ratio) and to be properly resized, respecting the height according to the width of the screen.
I have tried in different ways, but I manage to display the image correctly.
My View is:
struct ContentView: View {
var body: some View {
VStack {
GeometryReader { geometry in
VStack {
Text("Width: \(geometry.size.width)")
Text("Height: \(geometry.size.height)")
}
.foregroundColor(.white)
}
.padding()
.frame(alignment: .topLeading)
.foregroundColor(Color.white) .background(RoundedRectangle(cornerRadius: 10) .foregroundColor(.blue))
.padding()
GeometryReader { geometry in
VStack {
Image("sample")
.resizable()
//.frame(width: geometry.size.width)
.aspectRatio(contentMode: .fit)
}
.foregroundColor(.white)
}
.frame(alignment: .topLeading)
.foregroundColor(Color.white) .background(RoundedRectangle(cornerRadius: 10) .foregroundColor(.blue))
.padding()
}
.font(.title)
}
}
When I use .frame (width: geometry.size.width) by assigning the width of the geometry, the width is shown to the entire screen but the height is not maintaining aspect ratio. (looks crushed)
How can I get the dimensions of the image and find its proportion to use it with .aspectRatio (myratio, contentMode: .fit)
There is another way to display the image correctly, any suggestions
You need to eliminate the second GeometryReader, because having two children in the VStack that both accept as much space as they are offered is going to make the VStack unable to give the Image the correct amount of space.
You also need to raise the layout priority of the Image so that the VStack offers it space first, so it can take as much as it needs.
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
GeometryReader { geometry in
VStack {
Text("Width: \(geometry.size.width)")
Text("Height: \(geometry.size.height)")
}.foregroundColor(.white)
}.padding()
.background(
RoundedRectangle(cornerRadius: 10)
.foregroundColor(.blue))
.padding()
Image(uiImage: UIImage(named: "sample")!)
.resizable()
.aspectRatio(contentMode: .fit)
.layoutPriority(1)
}
}
}
import PlaygroundSupport
let host = UIHostingController(rootView: ContentView())
host.preferredContentSize = .init(width: 414, height: 896)
PlaygroundPage.current.liveView = host
Result: