I have a ZStack in which an Image is presented.
The image needs to be scaled in some cases.
I am failing on aligning the scaled image on the bottom of the ZStack, it is always presented in the middle.
I tried ZStack(alignment: .bottom) in combination with .alignmentGuide(.bottom) for the image, but this does not change the outcome.
Also putting a VStack around the image and placing a Spacer() above it does not change the result.
The HStack is not relevant and is only shown, because I need an ZStack in this construct. But The main issue is with the VStack, that it does not move after scaling in the Space of the ZStack.
It seems like .scaleEffect just uses position and frame of the original image and places the scaled image in the middle. Is this a limitation of scaleEffect? What other function can be used?
This is my View (reduced code): // I colored the background purple, to show the full size of the ZStack
var body: some View {
ZStack(alignment: .bottom) {
Color.purple
Image(battlingIndividual.getMonster().getStatusImageName(battlingIndividual.status))
.resizable()
.scaledToFill()
.scaleEffect(battlingIndividual.getMonster().size.scaleValue)
HStack() {
SkillViews(battlingIndividual: battlingIndividual)
Spacer()
}
}
}
The outcome is this:
But it should look like this:
EDIT: I added a Background to the image, in order to show that the image is centered in the ZStack.
Solution:
We don´t need an alignment in this case, we need an anchor:
.scaleEffect(battlingIndividual.getMonster().size.scaleValue, anchor: .bottom)
Solution Image:
I figured it out.
.scaleEffect uses its own anchor, which can be set to .bottom.
scaleEffect(_:anchor:) Apple Developer
Therefore I needed only to add "ancor: .bottom" to the scaleEffect.
.scaleEffect(battlingIndividual.getMonster().size.scaleValue, anchor:
.bottom)
for the following result:
I assume this view container ZStack is a one cell view, so you need to align not ZStack which tights to content, but entire HStack containing those monster cells, like
HStack(alignment: .bottom) { // << here !!
ForEach ... {
MonsterCellView()
}
}
Please, put your Image in a VStack and a Spacer() above the image and your Images will be on the bottom of the Stack. The alignment .bottom is only to aline multiple views with each other, but you are not having multiple views in your Stack. The HStack doesn't count for the alignment.
If I try this out in my example and scale the image down,
import SwiftUI
struct ContentView: View {
var body: some View {
ZStack {
Color.purple
VStack {
Spacer()
Image(systemName: "ladybug")
.resizable()
.scaledToFit()
.frame(width: 100, height: 100, alignment:
HStack {
Image(systemName: "hare")
.resizable()
.frame(width: 50, height: 50)
Image(systemName: "hare")
.resizable()
.frame(width: 50, height: 50)
Image(systemName: "hare")
.resizable()
.frame(width: 50, height: 50)
Spacer()
}
}
}
}
}
I get this.
Kind regards,
MacUserT
Related
My goal is to have a vertical paginated tabview with a scrollview inside. Scrolling as soon as you finish the scrollview you pass to the other tab and if the content of the scrollview has a lower height than the screen, scrolling passes directly to the next tab.
var body: some View {
GeometryReader { proxy in
TabView {
ForEach(colors, id: \.self) { color in
ScrollView(.horizontal, showsIndicators: false) {
HStack(alignment: .bottom, spacing: 20) {
ForEach(0..<15) { i in
//GeometryReader { block in
VStack(alignment: .leading) {
Text("Block test test test test test test \(i)")
}
.rotationEffect(.degrees(-90))
.frame(width: 70, height: proxy.size.width, alignment: .leading)
.background(Color.green)
.id(i)
//}
//.offset(y: proxy.size.width / 2)
}
}
.frame(height: proxy.size.height)
.background(Color.blue)
}
.frame(width: proxy.size.height, height: proxy.size.width)
.background(Color.pink)
}
.frame(width: proxy.size.width,height: proxy.size.height)
}
.frame( width: proxy.size.height, height: proxy.size.width)
.rotationEffect(.degrees(90), anchor: .topLeading)
.offset(x: proxy.size.width)
.tabViewStyle(
PageTabViewStyle(indexDisplayMode: .never)
)
}
}
These are the steps I followed:
created a tabview with horizontal scrolls inside
Rotated the tabview by 90°
Rotated the Vstacks inside the scrollview by -90°
The result is exact and the scrolling of the contents is continuous passing smoothly between scroll and tab, but the only problem is that I can't control the dimensions of the Vstacks inside the scrollview and therefore I can't have Vstacks with different heights in based on the content.
I tried to add a GeometryReader { block for the VStacks but besides not giving me the correct measurements of the VStacks it breaks the layout completely.
How can I get the dimensions of each Vstack correctly?
I have a VStack that contains an Image and a Text. I am setting the width (and height) of the Image to be half of the screen's width:
struct PopularNow: View {
let item = popular[0]
var body: some View {
VStack(spacing: 5) {
Image(item.image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: getRect().width/2, height: getRect().width/2)
Text(item.description)
.font(.caption)
.fontWeight(.bold)
.lineLimit(0)
}
.padding()
.background(Color.gray.opacity(0.1))
.cornerRadius(15)
}
func getRect() -> CGRect {
return UIScreen.main.bounds
}
}
The problem is that the Text pushes and causes the VStack to expand instead of respecting the Image's width. How can I tell the Text to not grow horizontally more than the Image width and to grow "vertically" (i.e. add as many lines it needs)? I know that I can add the frame modifier to VStack itself, but it seems like a hack. I want to tell the Text to only take as much space width wise as VStack already has, not more.
This is what it looks like right now, as you can see the VStack is not half the screen's size, it's full screen size because the Text is expanding it.
Try to fix its size, like
VStack(spacing: 5) {
Image(item.image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: getRect().width/2, height: getRect().width/2)
Text(item.description)
.font(.caption)
.fontWeight(.bold)
.lineLimit(0)
}
.fixedSize() // << here !!
// .fixedSize(horizontal: true, vertical: false) // alternate
.padding()
At an early stage of SwiftUI, it was easy to frame a view relative to its parent. That's gone, and we're left with GeometryReader.
I'll start with GeometryReader as the parent: It position its subview in the top left and that's fine.
As a subview, its size is unclear. The idea is to avoid giving 'hardcoded' height. Here's an image, wrapped in a GeometryReader, where I attempt to set a Text view under the image:
struct ContentView1: View {
var body: some View {
VStack {
GeometryReader { proxy in
Image("image")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: proxy.size.width * 0.8)
}
.background(Color.green)
Text("Text under image")
Spacer() // I'm being ignored
}
}
}
The GeometryReader has taking higher priority than the Spacer. At this point I can only give GeometryReader a hardcoded hight.
Let's try scaledToFit():
VStack {
GeometryReader { proxy in
Image("image")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: proxy.size.width * 0.8)
}
.background(Color.green)
.scaledToFit() // Scaled to fit 'something', who knows
Text("Text under image")
Spacer()
}
Unless GeometryReader is the top 'view':
struct ContentViewSuper: View {
var body: some View {
GeometryReader { proxy in
VStack {
Image("image")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: proxy.size.width * 0.8)
Text("Text under image")
Spacer()
}
}
.background(Color.green)
}
}
What's the right way to size GeometryReader? Use hardcode height? Use as the parent view of the screen? Is there another way to create a subview with GeometryReader that is more predictable in size?
Until a better answer comes along, this is what I have:
GeometryReader is meant best used as either the superview (not a subview), or a background view layout. Otherwise, a specific size (height) needs to be used.
GeometryReader will use flexible preferred size, it will not wrap its content. The size will behave differently than other views (higher tolerance to Spacer for example).
Used as a superview, the geometry, similar to UIKit, will place the (0,0) origin at the top left.
Added on the 24th of July:
This line of code fixes the space in the detail view. However... in the list view the title has become a lot smaller too.
.navigationBarTitle(Text("Egg management"), displayMode: .inline)
Added on the 23th of July:
Thanks to the tips I made a lot of progress. Especially the tip to add borders does wonders. You see exactly what happens!
However, there seems to be a difference between the Xcode Preview canvas, the simulator and the physical device. Is this a bug because -after all- it is still beta? Or is there anything I can do?
As you can see in the images... only in the Xcode Preview canvas the view connects to the top of the screen.
I believe it has something to do with the tabbar. Since when I look at the Xcode Preview canvas with the tabbar... that space above is also there. Any idea how to get rid of that?
Original postings:
This is my code for a detailed list view:
import SwiftUI
struct ContentDetail : View {
#State var photo = true
var text = "Een kip ..."
var imageList = "Dag-3"
var day = "3.circle"
var date = "9 augustus 2019"
var imageDetail = "Day-3"
var weight = "35.48"
var body: some View {
VStack (alignment: .center, spacing: 10) {
Text(date)
.font(.title)
.fontWeight(.medium)
ZStack (alignment: .topLeading){
Image(photo ? imageDetail : imageList)
.resizable()
.aspectRatio(contentMode: .fit)
.background(Color.black)
.padding(.trailing, 0)
.tapAction {
self.photo.toggle() }
HStack {
Image(systemName: day)
.resizable()
.padding(.leading, 10)
.padding(.top, 10)
.frame(width: 40, height: 32)
.foregroundColor(.white)
Spacer()
Image(systemName: photo ? "photo" : "pencil.circle")
.resizable()
.padding(.trailing, 10)
.padding(.top, 10)
.frame(width: 32, height: 32)
.foregroundColor(.white)
}
}
Text(text)
.lineLimit(6)
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
.padding(.leading, 6)
} .padding(20)
}
}
#if DEBUG
struct ContentDetail_Previews : PreviewProvider {
static var previews: some View {
ContentDetail()
}
}
#endif
Also included is the preview canvas. What I don't get is how I can make sure the text and photo are aligned to the top (instead of the middle). I tried with Spacers, padding etc.
I must be overseeing something small I guess... but. Can somebody point me in the right direction? Thanks.
Added:
After both answers I added a Spacer() after the last text. In Xcode in the preview canvas everything looks okay now. But on my connected iPhone 7 Plus there are some problems: the view is not aligned to the top, and the image is cropped (icon on the right is gone; white banding to the right).
Adding a Spacer() after the last text shifts everything to the top. Tested on iPhone Xr simulator (not preview).
...
Text(text)
.lineLimit(6)
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
.padding(.leading, 6)
Spacer()
}
To remove the space at the top:
VStack {
...
}
.padding(20)
.navigationBarTitle("TITLE", displayMode: .inline)
Think in terms of what a Spacer() does. It "moves" the views as far apart as it can - at least, without a specific space.
So you have this:
VStack {
Text
ZStack {
Image
HStack {
Image
Spacer()
Image
}
}
Text
}
All told, going from inner to outer, you have a horizontal stack of two images placed as far apart (the spacer is between them) inside of a "Z axis" stack that places an image on top of them, inside of a vertical stack that has some text above it.
So if you want to move everything in that vertical stack to the top, you simply need to add one last spacer:
VStack {
Text
ZStack {
Image
HStack {
Image
Spacer()
Image
}
}
Text
Spacer() // <-- ADD THIS
}
Last note: Don't be afraid to adding additional "stacks" to your view. In terms of memory footprint, it's really just a single view with no performance hit.
EDIT: I took your original view and changed everything to placeholders...
var body: some View {
VStack (alignment: .center, spacing: 10) {
Text("Text #1")
.font(.title)
.fontWeight(.medium)
ZStack (alignment: .topLeading) {
Text( "Image #1")
HStack {
Text("Image #2")
Spacer()
Text("Image #3")
}
}
Text("Text #2")
.lineLimit(6)
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
.padding(.leading, 6)
} .padding(20)
}
As expected, everything is vertically centered. Adding a Spacer() below "Text #2" throws everything to the top. A couple of thoughts:
Starting there, and add in your Image views one by one. Add in the modifiers like that also.
I don't have the specific images you are rendering, so maybe put a noticeable background color on various things (orange is my personal favorite) and see if the top Image is actually on top but the image makes it appear as though it isn't. A border would work pretty well too.
I'm trying to center a bunch of views in a VStack within a ScrollView in SwiftUI. To simplify things, I'm just trying to get it to work with a single Text view. Here's what I've come up with so far:
var body: some View {
ScrollView(alwaysBounceVertical: true){
HStack(alignment: .center) {
Spacer()
Text("This Is a Test")
Spacer()
} //HStack
.background(Color.green)
} //ScrollView
.background(Color.gray)
}
This results in this:
I want the text to be in the middle like this:
So the HStack should be full-width and the Text should be centered within it. It seems like this should be easy, but I don't get what I'm doing wrong. :)
Using GeometryReader, you can get information about the size of the containing view and use that to size your view.
var body: some View {
GeometryReader { geometry in <--- Added
ScrollView(alwaysBounceVertical: true){
HStack(alignment: .center) {
Spacer()
Text("This Is a Test")
Spacer()
} //HStack
.frame(width: geometry.size.width) <--- Added
.background(Color.green)
} //ScrollView
.background(Color.gray)
}
}
edit: after looking into this more, it seems that part of the problem is that you are using a ScrollView. If you remove that parent, the spacers in the HStack will automatically cause stretching to fill the view. I'm guessing the automatic stretching doesn't happen in ScrollViews because there's no finite limit to how big it can be, how much would it stretch? (because a ScrollView can scroll in any direction)
This seems to be a bug in Xcode 11.0 beta, ScrollView content wouldn't fill the scroll view. If you replace the ScrollView with a List it will work as expected. But if you have to use a scroll view, one workaround is to fix the scroll view's content width.
So your code will look something like this:
ScrollView(alwaysBounceVertical: true) {
HStack(alignment: .center) {
Spacer()
Text("This Is a Test")
Spacer()
} // HStack
.frame(width: UIScreen.main.bounds.width) // set a fixed width
.background(Color.green)
} // ScrollView
.background(Color.gray)
Result:
You should use the modifier frame and set its maxWidth: .infinity.
So it tells its parent: "the wider, the better" :)
var body: some View {
ScrollView(.vertical, showsIndicators: true){
HStack(alignment: .center) {
Spacer()
Text("This Is a Test")
.frame(maxWidth: .infinity) // <- this
Spacer()
} //HStack
.background(Color.green)
} //ScrollView
.background(Color.gray)
}
And this works regardless its parent.
Scrollview or whatever View it's set in.
Paul is doing a great job clarifying it to all of us here:
https://www.hackingwithswift.com/quick-start/swiftui/how-to-give-a-view-a-custom-frame
Answer compatible with Xcode 12.1 (12A7403)
I hope this helps 👍
dsa