0

I'm having an Image holder that would load the thumdnail on init and allow for download later on. My issue is that the view is not updated with the images after I load them. After pressing the load button for the second time, my first images are then displayed.

I'm having trouble finding the reason behind this behaviour.

The image holder :

class MyImage: ObservableObject {
        private static let sessionProcessingQueue = DispatchQueue(label: "SessionProcessingQueue")

    @Published var thumbnail: UIImage?
    @Published var loaded: Bool

    var fullName: String {
        "\(folderName)/\(fileName)"
    }

    var onThumbnailSet: ((UIImage?) -> Void)

    private var folderName: String
    private var fileName: String

    private var cancelableThumbnail: AnyCancellable?

    private var thumbnailUrl: URL? {
        return URL(string: "\(BASE_URL)/thumbnail/\(fullName)")
    }

    private var downloadUrl: URL? {
        return URL(string: "\(BASE_URL)/download/\(fullName)")
    }

    init(folderName: String, fileName: String) {
        self.folderName = folderName
        self.fileName = fileName
        self.loaded = false
        self.loadThumbnail()
    }

    private func loadThumbnail() {
        guard let requestUrl = thumbnailUrl else { fatalError() }
        self.cancelableThumbnail = URLSession.shared.dataTaskPublisher(for: requestUrl)
            .subscribe(on: Self.sessionProcessingQueue)
            .map { UIImage(data: $0.data) }
            .receive(on: DispatchQueue.main)
            .sink(receiveCompletion: { (suscriberCompletion) in
                switch suscriberCompletion {
                case .finished:
                    break
                case .failure(let error):
                    print(error.localizedDescription)
                }
            }, receiveValue: { [weak self] (value) in
                self?.objectWillChange.send()
                self?.loaded.toggle()
                self?.thumbnail = value
            })
    }

The view :

struct MyView: View {
    @ObservedObject var imagesHolder: ImagesHolder = ImagesHolder()

    var body: some View {
        VStack {
            Button(action: {
                self.loadImages()

            }, label: {
                Text("Load images")
            })

            ForEach(imagesHolder.images, id: \.self) { image in
                if image.loaded {
                    Image(uiImage: image.thumbnail!)
                        .frame(width: 600, height: 600)
                } else {
                    Text("Not loaded")
                }
            }
        }
    }

    private func loadImages() -> Void {
        loadMediaList(
            onLoadDone: { myImages in
                myImages.forEach { image in
                    imagesHolder.append(image)
                }
            }
        )
    }
}

The observed object containing the array of loaded images :

class ImagesHolder: ObservableObject {
    @Published var images: [MyImage] = []

    func append(_ myImage: MyImage) {
        objectWillChange.send()
        images.append(myImage)
    }
}

And finally my data loader :

func loadMediaList(onLoadDone: @escaping (([MyImage]) -> Void)) -> AnyCancellable {
  let url = URL(string: "\(BASE_URL)/medias")

  guard let requestUrl = url else { fatalError() }
  
  return URLSession.shared.dataTaskPublisher(for: requestUrl)
      .subscribe(on: Self.sessionProcessingQueue)
      .map { parseJSON(data: $0.data) }
      .receive(on: DispatchQueue.main)
      .sink(receiveCompletion: { (suscriberCompletion) in
          switch suscriberCompletion {
          case .finished:
              break
          case .failure(let error):
              print(error.localizedDescription)
          }
      }, receiveValue: { images in
          onLoadDone(images);
      })
}
2
  • Why is MyImage an ObservableObject if it doesn't have any @Published properties? That aside, nested ObservableObjects are always going to be challenging to deal with. You'll be much better off storing your models in structs if you can and getting the updating behavior for free. See an answer I wrote on this yesterday: stackoverflow.com/a/69081334/560942 And the most common thread on the issue: stackoverflow.com/questions/58406287/… Commented Sep 7, 2021 at 22:38
  • I updated it, it was a last moment change miss. I will check the links your provided. Commented Sep 8, 2021 at 8:18

1 Answer 1

1

What I ended up doing and worked great for me was having a seperate view for the display of my Image like this :


struct MyImageView: View {

    @ObservedObject var image: MyImage

    init(image: MyImage) {
        self.image = image
    }

    var body: some View {
        if image.loaded {
            Image(uiImage: image.thumbnail!)
                .resizable()
        } else {
            ProgressView()
                .progressViewStyle(CircularProgressViewStyle())
                .frame(width: 100, height: 100, alignment: .center)
        }
    }
}

struct MyView: View {
    @ObservedObject var imagesHolder: ImagesHolder = ImagesHolder()

    var body: some View {
        VStack {
            Button(action: {
                self.loadImages()

            }, label: {
                Text("Load images")
            })

            ForEach(imagesHolder.images, id: \.self) { image in
                MyImageView(image: image)
            }
        }
    }

    private func loadImages() -> Void {
        loadMediaList(
            onLoadDone: { myImages in
                myImages.forEach { image in
                    imagesHolder.append(image)
                }
            }
        )
    }
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.