Home > OS >  SwiftUI, CloudKit and Images
SwiftUI, CloudKit and Images

Time:08-07

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!

CodePudding user response:

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
       }
    }
  }
}

CodePudding user response:

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
        }
    }
}

}

  • Related