SwiftUI Alignment Bottom - swift

I have the following code:
struct CircleView: View {
var body: some View {
ZStack(alignment: .bottom) {
VStack{
Spacer()
Circle().frame(width: UIScreen.main.bounds.width/5)
}
}
}
}
Why is it not aligning the circle on the bottom? I have a spacer which should take up all free space in the ZStack?
Thanks in advance.

Because Circle is a Shape and does not have own content size, so consumes all provided. You limited width, but not height, so Circle consumed all height (blue rect is all circle, but drown only where fit).
So if you want to make it aligned, you have to limit it completely, like below
ZStack(alignment: .bottom) {
VStack{
Spacer()
Circle()
.frame(width: UIScreen.main.bounds.width/5, height: UIScreen.main.bounds.width/5)
}
}

Related

How to horizontally center content of horizontal ScrollView?

I have a horizontal ScrollView, and in it an HStack, like so:
ScrollView(.horizontal) {
HStack {
Circle()
.frame(width: 60, height: 60)
...
}
With this code, the contents of the HStack are aligned to the left of the screen, but I would like to get them in the middle of the ScrollView. How can I accomplish this?
all you need to do is to define the size of ScrollView and the HStack so when you define the size of ScrollView and define the size of HStack the view and the important thing is to make the alignment HStack to the center so that the View like a circle inside the Hstack center in it
struct ContentView: View {
var body: some View {
ScrollView(.horizontal) {
HStack() {
Circle()
.frame(width: 60, height: 60)
// the alignment is center here
}.frame(width: 500, height: 60,alignment: .center)
}.frame(width: 500, height: 500)
}
}
here the alignment is leading so the circle go to left
struct ContentView: View {
var body: some View {
ScrollView(.horizontal) {
HStack() {
Circle()
.frame(width: 60, height: 60)
// the alignment is leading here
}.frame(width: 500, height: 60,alignment: .leading)
}.frame(width: 500, height: 500)
}
}
here the alignment is trailing so the circle go to right
struct ContentView: View {
var body: some View {
ScrollView(.horizontal) {
HStack() {
Circle()
.frame(width: 60, height: 60)
// the alignment is trailing here
}.frame(width: 500, height: 60,alignment: .trailing)
}.frame(width: 500, height: 500)
}
}
Assuming you want to center ScrollView's content when there is free space (because otherwise scrolling behaviour would be rather broken) here is a possible approach (w/o hardcode) - to use preference key to read scroll view's width on screen and apply it as a minimum width of content's container, the center alignment is applied by default.
Tested with Xcode 13.2 / iOS 15.2
struct DemoView: View {
#State private var scrollWidth = CGFloat.zero
var body: some View {
ScrollView(.horizontal) {
HStack {
Circle()
.frame(width: 60, height: 60)
}
.frame(minWidth: scrollWidth) // << here (cetner by default) !!
}
.border(Color.green) // << for demo
.background(GeometryReader {
// read width of ScrollView on screen
Color.clear
.preference(key: ViewWidthKey.self, value: $0.frame(in: .local).size.width)
})
.onPreferenceChange(ViewWidthKey.self) {
self.scrollWidth = $0 // << store width
}
}
}

Swift UI stacked bordered views consistent border thickness

In my Swift UI project, I have a number of vertically stacked views each with a black border. See this example visual:
Note that the top and bottom borders merge together to form a sort of thicker border. How do I make the merged top / bottom borders look the same thickness as the horizontal borders? In CSS I would simply make the thickness of the top and bottom borders half of the horizontal borders, but Swift doesn't have the capability of changing border widths for each edge as far as I know.
struct test: View {
var body: some View {
ScrollView {
ScrollViewReader { value in
ForEach(0..<100) { i in
Text("Example \(i)")
.font(.title)
.frame(width: 200, height: 200)
.id(i)
.border(Color.black, width: 4)
}
.onAppear() {
value.scrollTo(99)
}
}
}
}
}
On each edge, you can just overlay a Rectangle. The rectangles on the top and bottom are half the height, so when together, it is the same size as the width ones.
Code:
ScrollView {
ScrollViewReader { value in
VStack(spacing: 0) {
ForEach(0..<100) { i in
Text("Example \(i)")
.font(.title)
.frame(width: 200, height: 200)
.id(i)
.overlay(
Rectangle().frame(height: 2),
alignment: .top
)
.overlay(
Rectangle().frame(height: 2),
alignment: .bottom
)
.overlay(
Rectangle().frame(width: 4),
alignment: .leading
)
.overlay(
Rectangle().frame(width: 4),
alignment: .trailing
)
}
}
.onAppear() {
value.scrollTo(99)
}
}
}
A little bit of a hacky solution but managed to have the result with a negative spacing in a VStack wrapping the view
struct test: View {
var body: some View {
ScrollView {
ScrollViewReader { value in
VStack(spacing: -4){
ForEach(0..<100) { i in
Text("Example \(i)")
.font(.title)
.frame(width: 200, height: 200)
.id(i)
.border(Color.black, width: 4)
}
}
.onAppear() {
value.scrollTo(99)
}
}
}
}
}
I think the easiest way would be to add a negative padding. Just make the padding value half of the border width. Some other notes:
Make sure to add a VStack or LazyVStack inside your ScrollView - otherwise, you won't be able to have fine control over spacing, alignment, or layout.
I recommend LazyVStack because you have 100 Texts, which is a lot. When you have lots of elements such as Text, use LazyVStack to improve performance.
struct ContentView: View {
var body: some View {
ScrollView {
ScrollViewReader { value in
LazyVStack(spacing: 0) { /// better performance + set spacing to 0
ForEach(0..<100) { i in
Text("Example \(i)")
.font(.title)
.frame(width: 200, height: 200)
.id(i)
.border(Color.black, width: 4)
.padding(.vertical, -2) /// make this half of the border width
}
}
.onAppear() {
value.scrollTo(99)
}
}
}
}
}
Result:

Prevent view from overflowing in both directions

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)
}
}

SwiftUI - setting the frame of view

When I learn new stuff like SwiftUI (beta 6)
I want to start from the basic.
I just want to set frame to subview like in UIKit.
What I'm missing here ? (this is from Simulator)
1. the subview is not in 0,0 position.
2. why at least the start of the word is not inside the border ?
UPDATE :
how to set the text view in 0,0 position ? (just like on UIKit)
I thought my question is very clear, but for some reason, it's not.
I think it's important to understand why your solution doesn't work because at a first glance it seems correct and it seems that SwiftUI works in some weird ways (because, of course, we are all used to UIKit).
You tried:
struct ContentView: View {
var body: some View {
VStack {
Text("Hello World")
.position(CGPoint(x: 0, y: 0))
.frame(width: 50, height: 100)
.border(Color.red, width: 4)
}
}
}
And you got:
First of all, the position modifier says:
Fixes the center of the view at the specified point in its parent’s
coordinate space.
Two things are important here:
The view is moved based on its centre, not based on its top-left corner
The view is moved in the parent's coordinate space
But who is the Text's parent? A view modifier in SwiftUI is something that applies to a View and returns a View. Modifiers are applied from the last one to the first one (in reverse order respect to how you see them). In your case:
So: The centre of the Text is positioned at (0,0) respect to a Frame 50x100 with a red Border. The resulting View is placed in the centre of the screen because of the VStack (it's the VStack default behaviour). In other words: the position's parent (position returns a View, every modifier returns a View) is the Frame 50x100 placed in the centre of the screen.
If you want to position the top-left corner of the Text at (0,0) in the Frame coordinate space you should use the Spacer modifier this way:
struct ContentView: View {
var body: some View {
VStack {
Text("Hello World")
Spacer()
}
.frame(width: 50, height: 100)
.border(Color.red, width: 4)
}
}
And you'll get:
If you want, instead, the top-left corner of the Frame to be at (0,0) respect to the whole View I think the simplest way is:
struct ContentView: View {
var body: some View {
HStack {
VStack {
Text("Hello World")
.frame(width: 50, height: 100)
.border(Color.red, width: 4)
Spacer()
}
Spacer()
}
.edgesIgnoringSafeArea(.all)
}
}
And you'll get:
Do Like this way
struct ContentView: View {
var body: some View {
VStack{
Text("Hello World")
.frame(width: 50, height: 100)
.border(Color.red, width: 4)
.padding()
Spacer()
}
}
}
Below is output
if you want to remove space on top add .edgesIgnoringSafeArea(.top) for your view like below
struct ContentView: View {
var body: some View {
VStack{
Text("Hello World")
.frame(width: 50, height: 100)
.border(Color.red, width: 4)
.padding()
Spacer()
}
.edgesIgnoringSafeArea(.top)
}
}

SwiftUI: Prevent Image() from expanding view rect outside of screen bounds

What I'm trying to achieve
I'm trying to create a SwiftUI view where an image should expand the entire screen (edgesIgnoringSafeArea(.all)), and then overlay a view on top of that, that also fills the entire screen, but respects the safe area.
What I've tried
This is my code, which comes close:
struct Overlay: View {
var body: some View {
VStack {
HStack {
EmptyView()
Spacer()
Text("My top/right aligned view.")
.padding()
.background(Color.red)
}
Spacer()
HStack {
Text("My bottom view")
.padding()
.background(Color.pink)
}
}
}
}
struct Overlay_Previews: PreviewProvider {
static var previews: some View {
ZStack {
Image(uiImage: UIImage(named: "background")!)
.resizable()
.edgesIgnoringSafeArea(.all)
.aspectRatio(contentMode: .fill)
Overlay()
}
}
}
The issue and tested solutions
The issue is that the image is not clipped it looks like, so it expands the parent view to a width larger than the screen width, which then makes the top right aligned red text box float off screen (see image).
I tried using .clipped() in various places, with no luck. I would preferably avoid using GeometryReader if possible.
Q: How can I make the image view only fill the screen?
You have to limit the frame size of the out-of-bounds Image before it is being picked up by the ZStack to avoid the ZStack to grow and so the Overlay to go out of position.
edit: aheze shows with his answer a way around using GeometryReader by putting the Image into the background of Overlay() with .background(Image()..). This avoids the usage of ZStack and GeometryReader completely and is possibly a cleaner solution.
Based on parent view size
struct IgnoringEdgeInsetsView2: View {
var body: some View {
ZStack {
GeometryReader { geometry in
Image("smile")
.resizable()
.aspectRatio(contentMode: .fill)
.edgesIgnoringSafeArea(.all)
.frame(maxWidth: geometry.size.width,
maxHeight: geometry.size.height)
}
Overlay()
}
}
}
Based on screen size
struct IgnoringEdgeInsetsView: View {
var body: some View {
ZStack {
Image("smile-photo")
.resizable()
.aspectRatio(contentMode: .fill)
.edgesIgnoringSafeArea(.all)
.frame(maxWidth: UIScreen.main.bounds.width,
maxHeight: UIScreen.main.bounds.height)
Overlay()
}
}
}
No need to mess with GeometryReader. Instead, you can prevent the image from overflowing by using the .background() modifier.
struct ContentView: View {
var body: some View {
Overlay()
.background( /// here!
Image("City")
.resizable()
.aspectRatio(contentMode: .fill)
.ignoresSafeArea()
)
}
}
Result: