2

I'm really stumped by something I think that should be relatively easy, so i need a little bump in the right direction. I've searched in a lot of places and I get either the wrong information, or outdated information (a lot!).

I am working with Core Data and CloudKit to sync data between the user's devices. Images I save as CKAsset attached to a CKRecord. That works well. The problem is with retrieving the images. I need the images for each unique enitity (Game) in a list. So I wrote a method on my viewModel that retrieves the record with the CKAsset. This works (verified), but I have no idea how to get the image out and assign that to a SwiftUI Image() View. My current method returns a closure with a UIImage, how do I set that image to an Image() within a foreach. Or any other solution is appreciated. Musn't be that hard to get the image?

/// Returns the saved UIImage from CloudKit for the game or the default Image!
func getGameImageFromCloud(for game: Game, completion: @escaping (UIImage) -> Void ) {
    // Every game should always have an id (uuid)!
    if let imageURL = game.iconImageURL {
        let recordID = CKRecord.ID(recordName: imageURL)
        var assetURL = ""

        CKContainer.default().privateCloudDatabase.fetch(withRecordID: recordID) { record, error in
            if let error = error {
                print(error.getCloudKitError())
                return
            } else {
                if let record = record {
                    if let asset = record["iconimage"] as? CKAsset {
                        assetURL = asset.fileURL?.path ?? ""
                        DispatchQueue.main.async {
                            completion(UIImage(contentsOfFile: assetURL) ?? AppImages.gameDefaultImage)
                        }
                    }
                }
            }
        }
    } else {
        completion(AppImages.gameDefaultImage)
    }
}

This is the ForEach I want to show the Image for each game (but this needed in multiple places:

        //Background Tab View
        TabView(selection: $gamesViewModel.currentIndex) {
            ForEach(gamesViewModel.games.indices, id: \.self) { index in
                GeometryReader { proxy in
                    Image(uiImage: gamesViewModel.getGameImageFromCloud(for: gamesViewModel.games[index], completion: { image in
                         
                    }))
                        .resizable()
                        .aspectRatio(contentMode: .fill)
                        .frame(width: proxy.size.width, height: proxy.size.height)
                        .cornerRadius(1)
                }
                .ignoresSafeArea()
                .offset(y: -100)
            }
            .onAppear(perform: loadImage)
        }
        .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
        .animation(.easeInOut, value: gamesViewModel.currentIndex)
        .overlay(
            LinearGradient(colors: [
                Color.clear,
                Color.black.opacity(0.2),
                Color.white.opacity(0.4),
                Color.white,
                Color.systemPurple,
                Color.systemPurple
            ], startPoint: .top, endPoint: .bottom)
        )
        .ignoresSafeArea()

TIA!

DeveloperSammy
  • 167
  • 1
  • 11
  • Does this answer your question https://stackoverflow.com/a/62549678/12299030? – Asperi Aug 06 '22 at 13:24
  • Thanks for the input. But not really. Can't seem to figure out how to have getGameImageFromCloud executed for every game in the ForEach and then assign the result of the closure (UIImage) to my SwiftUI Image() View. Image(uiImage: getGameImageFromCloud(for: game) { image in } doesn't work!!?? – DeveloperSammy Aug 06 '22 at 13:38
  • Just make subview to wrap each one image. – Asperi Aug 06 '22 at 13:40
  • Could you show me how? I'm really not seeing it. The method needs to be run for each game in the ForEacht and show the corresponding Image – DeveloperSammy Aug 06 '22 at 13:48
  • You forgot to add your ForEach in question. – Asperi Aug 06 '22 at 13:52
  • Mentioned it in my opening, But added extra info to my post. So basically how can I run getGameImageFromCloud each iteration of the ForEach() and assign the image from the closure to a (my) SwiftUI Image() – DeveloperSammy Aug 06 '22 at 14:22

2 Answers2

2

So, let's go... extract ForEach image dependent internals into subview, like (of course it is not testable, just idea):

ForEach(gamesViewModel.games.indices, id: \.self) { index in
    GeometryReader { proxy in
        GameImageView(model: gamesViewModel, index: index)     // << here !!
            .frame(width: proxy.size.width, height: proxy.size.height)
            .cornerRadius(1)
            //.onDisappear { // if you think about cancelling
            //   gamesViewModel.cancelLoad(for: index)
            //}
    }
    .ignoresSafeArea()
    .offset(y: -100)
}
.onAppear(perform: loadImage)

and now subview itself

struct GameImageView: View {
  var model: Your_model_type_here
  var index: Int

  @State private var image: UIImage?    // << here !!

  var body: some View {
    Group {
      if let loadedImage = image {
        Image(uiImage: loadedImage)          // << here !!
          .resizable()
          .aspectRatio(contentMode: .fill)
      } else {
        Text("Loading...")
      }
    }.onAppear {
       model.getGameImageFromCloud(for: model.games[index]) { image in
         self.image = image
       }
    }
  }
}
Asperi
  • 228,894
  • 20
  • 464
  • 690
0

For completion's sake, my own version:

struct GameImage: View {
    var game: Game
    @EnvironmentObject var gamesViewModel: GamesView.ViewModel
    @State private var gameImage: UIImage?


var body: some View {
    Group {
        if let gameImage = gameImage {
            Image(uiImage: gameImage)
                .resizable()
                .aspectRatio(contentMode: .fill)
        } else {
            ZStack(alignment: .center) {
                Image(uiImage: AppImages.gameDefaultImage)
                    .resizable()
                    .aspectRatio(contentMode: .fill)

                ProgressView()
                    .foregroundColor(.orange)
                    .font(.title)
            }
        }
    }.onAppear {
        gamesViewModel.getGameImageFromCloud(for: game) { image in
            self.gameImage = image
        }
    }
}

}

DeveloperSammy
  • 167
  • 1
  • 11