2

I have a large set of URLs to images. I display the files' thumbnails in a LazyVStack. I have wrapped up the 'ThumbnailView' and the 'ThumbnailGenerator' in a struct and class respectively. However, when I ran the code I discovered that it kept re-initaiting the ThumbnailGenerators. After some investigation I found that after removing an HStack in the main view's hierarchy the problem went away.

Any thoughts as to why this might happen. (BTW I did log this with Apple, but still feel I am doing something wrong here myself.)

I have stripped the code back to the bare essentials here, replacing the thumbnail generation code with a simple sleep statement, to demonstrate the bug in action. Run it with the HStack in and it will print out the date continuously. Take it out and it works as expected.


@main
struct ExperimentApp: App {
    var body: some Scene {
        WindowGroup {
            LazyVIssue()
                .frame(width: 200, height: 140)
                .padding(100)
        }
    }
}

struct LazyVIssue: View {
    var body: some View {
        ScrollView {
            LazyVStack {
                ForEach(0..<10) { i in
                    HStack { /// <---- REMOVE THIS HSTACK AND IT WORKS
                        ThumbnailView()
                        Text("Filename \(i)")
                    }.padding()
                }
            }
        }
    }
}

struct ThumbnailView: View {
    @StateObject private var thumbnailGenerator : ThumbnailGenerator
    
    init() {
        _thumbnailGenerator = StateObject(wrappedValue: ThumbnailGenerator())
    }
    
    var body: some View {
        thumbnailGenerator.image
    }
}

final class ThumbnailGenerator: ObservableObject {
    var image : Image
    
    init() {
        print("Initiating", Date())
        image = Image(systemName: "questionmark.circle.fill")
        DispatchQueue.global(qos: .userInteractive).async { [weak self] in
            guard let self = self else { return }
            sleep(1)    /// Simulate some work to fetch  image
            self.image = Image(systemName: "camera.circle.fill")
            DispatchQueue.main.async {
                self.objectWillChange.send()
            }
        }
    }
}

2 Answers 2

1

I'm not sure why this is happening but I've seen had some funky things happen like this as well. If you initialize the ThumbnailGenerator() outside of the ThumbnailView init, I believe the issue goes away.

init(generator: ThumbnailGenerator) {
        _thumbnailGenerator = StateObject(wrappedValue: generator)
    }
Sign up to request clarification or add additional context in comments.

Comments

1

Well, it is not clear for now what's going on here definitely (it is something about LazyVStack caching), but there is workaround - move everything into single row view.

Tested with Xcode 12.1 / iOS 14.1

struct LazyVIssue: View {
    var body: some View {
        ScrollView {
            LazyVStack {
                ForEach(0..<10) { i in
                    ThumbnailView(i)     // << single row view !!
                }
            }
        }
    }
}

struct ThumbnailView: View {
    @StateObject private var thumbnailGenerator : ThumbnailGenerator
    let row: Int

    init(_ row: Int) {
        self.row = row
        _thumbnailGenerator = StateObject(wrappedValue: ThumbnailGenerator())
    }
    
    var body: some View {
        HStack {
            thumbnailGenerator.image
            Text("Filename \(row)")
        }.padding()
    }
}

1 Comment

Thank you - this works in the example code here. Yet in my actual app (MacOS 11.0.1 Code 12.2) it still keeps getting called. Let me have a further experiment and see if I can get it to settle down. Thanks!

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.