In this code, is it a problem usage of geometryReader both in view and in cells? is there a different way to achieve same result?). to be clear if I have 5 cells, I had hard time keeping the image in the same position even if text is on single line. I need to avoid getting some cell with image close to top and other at a lower position
I'd like to keep these requirements:
the images should be at the same level in each cells, if the text is on a single line, I need the image is same "level" of cells where text is on two lines.
I'd like to avoid hard coded sizes, this way hopefully the views are more flexible
import SwiftUI
struct TestForBackgroundImage: View {
@EnvironmentObject var classFromEntryPoint: ClassFromEntryPoint
private static let minCommonWidth: CGFloat = 100
let columns = [GridItem(.adaptive(minimum: minCommonWidth))]
var body: some View {
ScrollView {
LazyVGrid(columns: columns, spacing: 10) {
ForEach(0...100, id: \.self) { item in
ItemView(item: item)
.onTapGesture {
print(item)
}
}
}
.padding()
}
.backgroundImage2()
}
private struct ItemView: View {
let item: Int
var body: some View {
GeometryReader { geometry in
VStack(alignment: .center) {
Image(systemName: "apple.logo")
// Image(AppConstants.FixedImages.backgroundImage.rawValue)
.resizable()
.scaledToFill()
.foregroundStyle(.secondary)
.frame(width: geometry.size.width * 0.4, height: geometry.size.width * 0.4)
//this one is here for testing multiple text lenghts to be certain the image is always at the same level in each cell
if item % 2 == 0 {
Text("short: \(item)")
.frame(height: geometry.size.width * 0.25)
.lineLimit(2)
.minimumScaleFactor(0.8)
.multilineTextAlignment(.center)
.padding(.horizontal, 5)
.padding(.top, 5)
} else {
Text("middle lenght text: \(item)")
.frame(height: geometry.size.width * 0.25)
.lineLimit(2)
.minimumScaleFactor(0.8)
.multilineTextAlignment(.center)
.padding(.horizontal, 5)
.padding(.top, 5)
}
}
.frame(width: geometry.size.width, height: geometry.size.width)
.background(.teal)
.shadow(color: .black.opacity(0.3), radius: 10, x: 0, y: 10)
}
.aspectRatio(1, contentMode: .fit)
}
}
}
struct BackgroundImage2: ViewModifier {
func body(content: Content) -> some View {
GeometryReader { geometry in
content
.background(
Image(AppConstants.FixedImages.backgroundImage.rawValue)
.resizable()
.scaledToFill()
.opacity(0.4)
.offset(x: -geometry.size.width / 2, y: geometry.size.height / 2)
)
}
}
}
extension View {
func backgroundImage2() -> some View {
modifier(BackgroundImage2())
}
}
If you don't need the 1:1 aspect ratio, you can just scaleToFit
the image, and add a Spacer
between the text and the image (or after the text, depending on what alignment you want):
// ItemView's body:
VStack {
Image(systemName: "apple.logo")
.resizable()
.scaledToFit()
.foregroundStyle(.secondary)
Spacer()
if item % 2 == 0 {
Text("short: \(item)")
.lineLimit(2)
.minimumScaleFactor(0.8)
.multilineTextAlignment(.center)
.padding(.horizontal, 5)
.padding(.top, 5)
} else {
Text("middle lenght text: \(item)")
.lineLimit(2)
.minimumScaleFactor(0.8)
.multilineTextAlignment(.center)
.padding(.horizontal, 5)
.padding(.top, 5)
}
}
.background(.teal)
.shadow(color: .black.opacity(0.3), radius: 10, x: 0, y: 10)
The spacer pushes the image and text apart from each other, making sure that the images always align at the top.
If you are not satisfied with this, then I suggest keeping the GeometryReader
. Making the Text
1/4 of the height of the VStack
is very reasonable.
Otherwise, you can put an invisible 2-line text, and overlay the actual text on top of it. IMO, this is more of a hack when compared to GeometryReader
.
VStack{
Image(systemName: "apple.logo")
.resizable()
.scaledToFit()
.foregroundStyle(.secondary)
// this takes up 2 lines
Text("\n")
.lineLimit(2)
.minimumScaleFactor(0.8)
.multilineTextAlignment(.center)
.padding(.horizontal, 5)
.padding(.top, 5)
.frame(maxWidth: .infinity)
.overlay { // specify the alignment here, if needed
Group {
if item % 2 == 0 {
Text("short: \(item)")
} else {
Text("middle lenght text: \(item)")
}
}
.lineLimit(2)
.minimumScaleFactor(0.8)
.multilineTextAlignment(.center)
.padding(.horizontal, 5)
.padding(.top, 5)
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.aspectRatio(1, contentMode: .fit)
.background(.teal)
.shadow(color: .black.opacity(0.3), radius: 10, x: 0, y: 10)