5

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!

Model Couple Holding Hands

Ramen Noodles

Jabinator1
  • 225
  • 4
  • 12
  • Could you insert an image to understand what you want as a final result? Otherwise it is difficult to be able to help you. – Gius Jul 13 '20 at 16:26
  • @Gius Sorry, I didn't realize I could! I'm still new to this haha! But I just a Portrait and landscape image! – Jabinator1 Jul 13 '20 at 16:50
  • If I understand correctly, you want the portrait images to be displayed with a maximum height of 600, while the landscape images you want to display them with the maximum width, right? – Gius Jul 13 '20 at 17:12

1 Answers1

6

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")!)
clawesome
  • 1,223
  • 1
  • 5
  • 10
  • 1
    Yes this works perfectly! The only thing I changed was the self.width in the else part of the statement to equal UiScreen.main.bounds.width so that it fit all my width of portrait images! Thank you so much!! – Jabinator1 Jul 13 '20 at 20:31
  • Although After further testing- it seems that this doesn't work outside the preview, which is weird! I am getting an issue of "Modifying state during view update, this will cause undefined behavior." with my images not loading on the app- any idea on how to fix that? – Jabinator1 Jul 13 '20 at 20:45
  • Whoops, I should have run it in the sim, sorry about that! I'll take a look and see what needs to be changed. – clawesome Jul 13 '20 at 21:09
  • You're totally good! I believe it has to do that the view is inside of a NavigationLink maybe? It's weird that it works in the preview but not the sim though! haha – Jabinator1 Jul 13 '20 at 21:10
  • 1
    I updated the answer so the width and height are updated on the main thread. This will fix the `Modifying state` warning but there might be a better way to go about it. Will update if I come up with a better solution. – clawesome Jul 13 '20 at 21:21
  • Awesome, it works! and sounds good, thank you so much for all your help! – Jabinator1 Jul 13 '20 at 21:25
  • 1
    Good to hear. I made a second update where the `image` is in it's own `@State` var which makes it a bit more reusable but you will have to pass a UIImage into the ContentView when you're using it. – clawesome Jul 13 '20 at 21:33