I have created a CardView/Tile view to display as a two column. I have calculated the cardView width and height according to screen size of different devices.
The problem I am facing is adjustment of Image. I also have a text just below to Image.
Overall Image is fixed to its position but whenever text comes in two lines limitline(2)
the image little move to upward (top) position. I set aspect ratio but its not working properly.
Please help me out what I am doing wrong exactly. Can I set image according to screen size as I did for Card/Tile view? It picture you can see first tile image is moved up side.
Code:
import SwiftUI
struct TileView: View {
@StateObject var viewModel: CartViewModel
// MARK: - Constants
private struct ViewConstants {
static let screenWidth = Constants.DeviceConfig.screenWidth
static let cardRatio = 133.0/148.0
static let cardWidth = screenWidth / 2 - 24
static let cardHeight = cardWidth/cardRatio
static let imageWidth: CGFloat = 106
static let imageHeight: CGFloat = 106
}
var body: some View {
VStack(spacing: 8) {
Image(viewModel.image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(height: ViewConstants.imageHeight)
Text(viewModel.title)
.lineLimit(2)
.multilineTextAlignment(.center)
.padding(.horizontal, 16)
}
.frame(width: ViewConstants.cardWidth, height: ViewConstants.cardHeight)
.background(Color.gray)
.cornerRadius(10)
}
}
Image:
This is happening because the VStack
with the image and the text has more height when the text goes over two lines.
You are setting a frame on the VStack
like this:
.frame(width: ViewConstants.cardWidth, height: ViewConstants.cardHeight)
The alignment here is the default of .center
, so the middle of the VStack
is vertically centered. This means, when there are two lines of text, the image is going to be a little higher than when there is only one line.
Here are some ways to fix:
1. Apply a minimum height to the text
By setting a minimum height on the text, you can make sure it always occupies enough space to extend over two lines:
Text(viewModel.title)
.lineLimit(2)
.multilineTextAlignment(.center)
.padding(.horizontal, 16)
.frame(minHeight: 40, alignment: .top) // <- HERE
2. Use hidden text to establish the footprint
If you don't like using a fixed minimum height, then you can determine the minimum height needed by using some hidden text. Then you can show your actual (visible) text in an overlay:
Text(".\n.")
.hidden()
.frame(maxWidth: .infinity)
.overlay(alignment: .top) {
Text(viewModel.title)
.lineLimit(2)
.multilineTextAlignment(.center)
.padding(.horizontal, 16)
}
The hidden text consists of a dot, a newline character and another dot. This is sufficient for finding the height needed for two lines of text.
3. Align to the top of the frame
If the height of the tile is fixed (which it appears to be) then you could decide what space you want between the top of the tile and the image and apply this as top padding to the image. Then you can use .top
alignment when you set the frame on the VStack
:
VStack(spacing: 8) {
Image(...)
// other modifiers as before
.padding(.top, 20) // <- ADDED
Text(...)
// other modifiers as before
}
.frame(width: ViewConstants.cardWidth, height: ViewConstants.cardHeight, alignment: .top) // alignment added
EDIT So you said in a comment that the spacing between the text and the bottom of the tile should be 20. This can be defined using padding. Here is how you can use a combination of techniques 1 + 3 to use all the space available with spaces of 20 above and below:
maxWidth: .infinity, maxHeight: .infinity
so that it can use all of the space available inside the frame of the VStack
.Image(viewModel.image)
.resizable()
.aspectRatio(contentMode: .fit)
.padding(.horizontal) // if needed
.frame(maxWidth: .infinity, maxHeight: .infinity)
.padding(.top, 20)
Text(viewModel.title)
.lineLimit(2)
.multilineTextAlignment(.center)
.frame(minHeight: 40, alignment: .top)
.padding(.horizontal, 16)
.padding(.bottom, 20)