UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Unable to get proxy.size.height when applying onAppear inside a GeometryReader code block.

Forums > SwiftUI

I implemented code in onAppear that resizes the image by the height value via the GeometryReader closure parameter size before the image is shown. However, when I build the app, a blank image appears.

If I resize the height value using the code UIScreen.main.bounds.height * 0.5, the image works fine.

Could it be that onAppear is executed before the view appears, so it can't get the proxy.size.height value of the GeometryReader?

Is there any way to work around this? (I would like to implement the resizing code in onAppear to avoid wasting resources, since the image is redrawn every time it is scrolled).


@State private var uiImage = nil

GeometryReader { geo in
  let size = geo.size
  let minY = geo.frame(in: .named("SCROLL")).minY
  let progress = minY / (height * (minY > 0 ? 0.5 : 0.77))

  Group {
      if let image = UIImage(data: item.unwrappedImageData) {
          Image(uiImage: uiImage ?? UIImage())
              .resizable()
              .aspectRatio(contentMode: .fill)
              .frame(width: size.width, height: size.height + (minY > 0 ? minY : 0), alignment: .top)
              .clipped()
              .onAppear { // ⁉️
                  guard let image = UIImage(data: item.unwrappedImageData) else { return }
                  DispatchQueue.global().async {
                      // downSizing -> ❌ can't size.height * 0.5
                      let resizeImage = image.resize(height: UIScreen.main.bounds.height * 0.5)
                      DispatchQueue.main.async {
                      // update Image
                          uiImage = resizeImage
                      }
                  }
              }
      } 
  }
  .overlay {
      ZStack(alignment: .bottom) {
          Rectangle().fill(
              .linearGradient(colors: [
                  Color.customBackgroundColor.opacity(0 - progress),
                  Color.customBackgroundColor.opacity(0.1 - progress),
                  Color.customBackgroundColor.opacity(0.3 - progress),
                  Color.customBackgroundColor.opacity(0.5 - progress),
                  Color.customBackgroundColor.opacity(0.7 - progress),
                  Color.customBackgroundColor
              ], startPoint: .top, endPoint: .bottom)
          }
          .padding(.horizontal, 14)
          //Moving with ScrollView
          .opacity(1 - (progress < 0 ? -progress : progress))
          .padding(.bottom, 10)
          .offset(y: minY < 0 ? minY : 0)
      }
  }
  .offset(y: -minY)
}

2      

@Bnerd  

Why you want to resize your image?

2      

extension UIImage {

    func resize(height: CGFloat) -> UIImage {
        let scale = height / self.size.height
        let width = self.size.width * scale
        let newSize = CGSize(width: width, height: height)
        let renderer = UIGraphicsImageRenderer(size: newSize)

        return renderer.image { _ in
            self.draw(in: CGRect(origin: .zero, size: newSize))
        }
    }
}

Using the original image as is will use a lot of memory, so I want to show the image after resizing before showing it on the screen.

2      

Hacking with Swift is sponsored by RevenueCat.

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

@Bnerd  

Are you loading a list using a ForEach? If yes, then you could load the visible items and unload the non visible items.

2      

Yes, the first screen, the main view, used List(ForEach). Doesn't List do lazy processing automatically?

The code in my question used a Scroll View rather than a List because the second screen, the one that appears when you tap on a particular item in the first screen, only shows a picture of that item and some information about it. I want to resize the image only once, when it is onAppear, because the view is constantly redrawn due to the shadow handling that dynamically changes with the scroll offset.

2      

@Bnerd  

As far as I know Lists, LazyVGrid they load in a "lazy" manner but they do not unload.. In my App I have a View that loads ALL the saved images in the App, the more I was scrolling the more the Ram consumption was going off the roof..eventually I applied the rule I mentioned above. What I did is to control/record the ids of the images that were visible on the screen, once they appear they were entering a temporary images array once dissappeared they were being removed, keeping the ram under control ;)

2      

So how do we know if we've gotten rid of the images stored in the temporary array?

Did you nil the visible images in onDisappear?

I'm having the same problem. The memory is building up...🫨🫠

2      

@Bnerd  

"Did you nil the visible images in onDisappear?" -> Exactly!

2      

This helped me free up a little more memory, thank you.

My app is also storing images along with the information of the clothes (I store them directly in coredata as a binary data type).

That's why scrolling through the list is taking 20-40% CPU. After running the app for a few minutes, I can feel the heat. I don't know what part of the app is causing the heat. The item edit screen, detail screen, shows the image downscaled via Image I/O.

Additionally, what do you prefer between List and ScrollView + LazyVstack? As items are added, the amount of scrolling increases, and I'm torn between List and ScrollView + LazyVStack.

2      

@Bnerd  

Why do you store your images as Data? I store them in the FileManager and just call them using their location string. Personally I use ScrollView+LazyVStack since I like to use custom cells.

3      

Because of CoreData + CloudKit support, I saved the image directly to CoreData. If I store only URLs in Core Data and images in Documents, won't the images stored in Documents be deleted when the user deletes the app?

2      

If I build my app when I have 100 items, it will use the

ScrollView + LazyVStack

  • CPU jumps to 41% as soon as we build it for the first time.
  • With each scroll, it rises to 22-40%.

List

  • CPU spikes to 61% on first build.
  • It rises to 26-56% with each scroll, averaging around 40%.

Each row has an image view, three text views. The problem is that removing the image from each row consumes similar CPU.😂 How to upload an image here...

2      

@Bnerd  

If you delete the App yes the images will be deleted from your phone. If you use CloudKit you should be able to save locally the images on the device and call them using their URLs and push a copy also to the cloud. Once you delete the app the images leave from the phone but if you are syncing with cloudkit you should be able once you download / install the app back to get your data + images back. I haven't use CloudKit so maybe I am not the best to answer this.

If you want share the code that loads each Item view, maybe the culprit is there.

2      

"call them using their URLs and push a copy also to the cloud" This part... I think you need to study more.

"If you want share the code that loads each Item view, maybe the culprit is there."

I'm not sure what you mean by this sentence, do you mean the @ObservedObject property wrapper in each child view?

2      

@Bnerd  

Like I said, I never used CloudKit but from a quick search I did, plus chatGPT confirmed it can be done. Most probably I explained very poorly what I meant.

"If you want share the code that loads each Item view, maybe the culprit is there." Somewhere you have a ForEach that draws the Item view for each item, maybe there you have something that is more CPU intensive.

2      

I've been searching for days and going through the code from scratch to find the cause, but I don't know.🥲

2      

Hacking with Swift is sponsored by RevenueCat.

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.