Reputation: 245
So I've been trying to figure out how to find the height of an image in SwiftUI for a while now with no success. Essentially I have portrait/landscape images, which the portrait ones I want to display at about 600 pt tall, whereas the landscape images I want to display so the width of the image is used, so the height is whatever. That being said, I'm wanting it inside of a GeometryReader, as the offset I am using uses it so that when you scroll down, the image doesn't scroll, but if you scroll up, it does. I don't believe I can share images, but in any case, this should be enough code to run it successfully!
struct ContentView: View {
@Environment(\.presentationMode) var mode: Binding<PresentationMode>
var backButton: some View {
Button(action: {
withAnimation {
self.mode.wrappedValue.dismiss()
}
}) {
Image(systemName: "chevron.left.circle.fill")
.opacity(0.8)
.font(.system(size: 30))
.foregroundColor(.black)
.background(Color.gray.mask(Image(systemName: "chevron.left").offset(x: 9.4, y: 7.6)))
}
}
var body: some View {
ScrollView(showsIndicators: false) {
GeometryReader { geometry in
Image("image1")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(maxWidth: UIScreen.main.bounds.width, minHeight: 350, maxHeight: 600, alignment: .top)
.clipped()
.overlay(
Rectangle()
.foregroundColor(.clear)
.background(LinearGradient(gradient: Gradient(colors: [.clear, .white]), startPoint: .center, endPoint: .bottom))
)
.offset(y: geometry.frame(in: .global).minY > 0 ? -geometry.frame(in: .global).minY : 0)
}
.frame(minWidth: UIScreen.main.bounds.width, maxWidth: UIScreen.main.bounds.width, minHeight: 350, idealHeight: 600, maxHeight: 600)
//Message
VStack(alignment: .leading) {
Text("\(07/22/20) - Day \(1)")
.font(.body)
.fontWeight(.light)
.foregroundColor(Color.gray)
.padding(.leading)
Text("what I Love)
.font(.title)
.fontWeight(.bold)
.padding([.leading, .bottom])
Text("paragraph")
.padding([.leading, .bottom, .trailing])
}
}
.edgesIgnoringSafeArea(.all)
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: backButton)
}
}
Any help would be much appreciated!
EDIT: Here are two images for reference- Sorry I didn't realize you were able to, though I should've figured that!
Upvotes: 7
Views: 13821
Reputation: 1319
If I understood correctly, you want the size of the image so you can use it's dimensions in it's own frame? You could make a function that takes in an image name and returns your desired image while also setting @State
vars for the width
and height
which you can use in the frame.
@State var width: CGFloat = 0
@State var height: CGFloat = 0
func image(_ name: String) -> UIImage {
let image = UIImage(named: name)!
let width = image.size.width
let height = image.size.height
DispatchQueue.main.async {
if width > height {
// Landscape image
// Use screen width if < than image width
self.width = width > UIScreen.main.bounds.width ? UIScreen.main.bounds.width : width
// Scale height
self.height = self.width/width * height
} else {
// Portrait
// Use 600 if image height > 600
self.height = height > 600 ? 600 : height
// Scale width
self.width = self.height/height * width
}
}
return image
}
Usage:
GeometryReader { geometry in
Image(uiImage: image("image1"))
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: width, height: height)
.overlay(
Rectangle()
.foregroundColor(.clear)
.background(LinearGradient(gradient: Gradient(colors: [.clear, .white]), startPoint: .center, endPoint: .bottom))
)
.offset(y: geometry.frame(in: .global).minY > 0 ? -geometry.frame(in: .global).minY : 0)
edit: Added the DispatchQueue.main.async
block as a quick fix for 'Modifying state during view update' warning. Works but probably not the absolute best way to go about it. Might be better to put the image into it's own @State
and set it in the view's onAppear
method.
edit 2: Moved the image into it's own @State
var which I believe is a better solution as it will make the view more reusable as you can pass an image instead of hard coding the name of the image in the view.
@State var image: UIImage {
didSet {
let width = image.size.width
let height = image.size.height
DispatchQueue.main.async {
if width > height {
// Landscape image
self.width = width > UIScreen.main.bounds.width ? UIScreen.main.bounds.width : width
self.height = self.width/width * height
} else {
self.height = height > 600 ? 600 : height
self.width = self.height/height * width
}
}
}
}
Usage:
GeometryReader { geometry in
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: width, height: height)
.overlay(
Rectangle()
.foregroundColor(.clear)
.background(LinearGradient(gradient: Gradient(colors: [.clear, .white]), startPoint: .center, endPoint: .bottom))
)
.offset(y: geometry.frame(in: .global).minY > 0 ? -geometry.frame(in: .global).minY : 0)
Will have to update how the ContentView is declared by passing in a UIImage:
ContentView(image: UIImage(named: "image0")!)
Upvotes: 7