7
votes

Short: The Images in my view are not updating after the first load. The URL remains the same as the previous loaded view, however the rest of the view that doesn't fetch a URL or data from storage is updated.

Full: I have two Views, a ListView and a DetailView.

In the ListView I display a list of type List. The detail view is supposed to show each Profile from List.profiles. I do this by storing each string uid in List.profiles and calling model.fetchProfiles to fetch the profiles for each list selected.

On the first selected List model.fetchProfiles returns the documents and model.profiles displays the data fine in the DetailView.

When first loading the DetailView the ProfileRow on appear is called and logs the profiles fetched. Then the ProfileRow loads the imageURL from the imagePath and uses it like to fetch the image.

Console: Load List1

CARD DID APPEAR: Profiles []
CARD DID APPEAR: SortedProfiles [] CARD ROW
CARD ROW DID APPEAR: Profiles profiles/XXXXXX/Profile/profile.png
CARD ROW DID APPEAR: SortedProfiles profiles/XXXXXX/Profile/profile.png
Get url from image path: profiles/XXXXXX/Profile/profile.png
Image URL: https://firebasestorage.googleapis.com/APPNAME/profiles%XXXXXXX

When selecting the second List from ListView the ProfileRow didAppear is not called due to;

if model.profiles.count > 0 {
  print("CARD ROW DID APPEAR: Profiles \(model.profiles[0]. imgPath)")     
  print("CARD ROW DID APPEAR: Sorted  \(model.sortedProfiles[0].imgPath)")
}

and won't ever again when selecting a List in ListView, however the rest of the profile data in the ProfileRow is displayed such as name so the data must be fetched.

The ImagePath is the same as the first view loading the exact same image. All other properties for the Profile such as name are loaded correctly.

Console: Load List2

CARD DID APPEAR: Profiles []
CARD DID APPEAR: SortedProfiles [] CARD ROW
Get url from image path: profiles/XXXXXX/Profile/profile.png Image URL: https://firebasestorage.googleapis.com/APPNAME/profiles%XXXXXXX

If I then navigate to List1 then the image for List2 appears, if I reselect List2 the image appears fine. The image show is correct on first load, and when selecting another list it always the one from before.

Can anyone help me out ?

First View

struct ListViw: View {
  @EnvironmentObject var model: Model

  var body: some View {
    VStack {
        
        ForEach(model.lists.indices, id: \.self) { index in
            NavigationLink(
                destination: DetailView()
                            .environmentObject(model)
                            .onAppear() {
                                model.fetchProfiles()
                            }
            ) {
                 ListRow(home:model.lists[index])
                    .environmentObject(model)
            }
            .isDetailLink(false)
        }
    }
  }

}

DetailView Card

struct ProfilesCard: View {

@EnvironmentObject var model: Model

var body: some View {
    VStack(alignment: .trailing, spacing: 16) {                
            if !model.sortedProfiles.isEmpty {
                VStack(alignment: .leading, spacing: 16) {
                    ForEach(model.sortedProfiles.indices, id: \.self) { index in
                        ProfileRow(
                            name: "\(model.sortedProfiles[index].firstName) \(model.sortedProfiles[index].lastName)",
                            imgPath: model.sortedProfiles[index].imgPath,
                            index: index)
                            .environmentObject(model)
                    }
                }
                .padding(.top, 16)
            }
        
    }//End of Card
    .modifier(Card())
    .onAppear() {
        print("CARD DID APPEAR: Profiles \(model.profiles)")
        print("CARD DID APPEAR: SORTED \(model.sortedTenants)")
    }
}
}



struct ProfileRow: View {

@EnvironmentObject var model: Model

@State var imageURL = URL(string: "")

var name: String
var imgPath: String
var index: Int

private func loadImage() {
    print("load image: \(imgPath)")
    DispatchQueue.main.async {
        fm.getURLFromFirestore(path: imgPath,  success: { (imgURL) in
                   print("Image URL: \(imgURL)")
            imageURL = imgURL
               }) { (error) in
                   print(error)
        }
    }
}

var body: some View {
    VStack(alignment: .leading, spacing: 12) {
        HStack(alignment: .center, spacing: 12) {

                   KFImage(imageURL,options: [.transition(.fade(0.2)), .forceRefresh])
                       .placeholder {
                           Rectangle().foregroundColor(.gray)
                       }
                       .resizable()
                       .aspectRatio(contentMode: .fill)
                       .frame(width: 32, height: 32)
                       .cornerRadius(16)


                // Profile text is always displayed correctly
            Text(name)
                    .modifier(BodyText())
                    .frame(maxWidth: .infinity, alignment: .leading)
        }
    }
    .onAppear() {
        print("CARD ROW")

        // Crashes if check is not there
        if model.profiles.count > 0 {

            print("CARD ROW DID APPEAR: Profiles \(model.profiles[0]. imgPath)")

            print("CARD ROW DID APPEAR: Sorted  \(model.sortedProfiles[0].imgPath)")
        }
        
       loadImage()
    }
  }
}

Model

class Model: ObservableObject {

  init() {
      fetchData()
  }

  @Published var profiles: [Profile] = []
  var sortedProfiles: [Profile] {return profiles.removeDuplicates } 

  @Published var list: List? {
      didSet {
          fetchProfiles()
      }
  }

  func fetchData() {
    if let currentUser = Auth.auth().currentUser {
        
        email = currentUser.email!

            db.collection("lists")
                .whereField("createdBy", isEqualTo: currentUser.uid)
                .addSnapshotListener { (querySnapshot, error) in
                guard let documents = querySnapshot?.documents else {
                    return
                }

                self.lists = documents.compactMap { queryDocumentSnapshot -> List? in
                    return try? queryDocumentSnapshot.data(as: List.self)
                }
            }

    }
 }

  func fetchProfiles() {
    profiles.removeAll()
    
    for p in list!.profiles {
        firestoreManager.fetchProfile(uid: t, completion: { [self] profile in
            profiles.append(profile)
        })
    }
  }

}

Update

What I have tried so far is to use didSet for the ImgPath or ImgURL but still not luck. Also have tried using model.profiles directly.

2

2 Answers

1
votes

In all callbacks with Firestore API make assignment for published or state properties on main queue, because callback might be called on background queue.

So, assuming data is returned and parsed correctly, here is as it should look like

for p in list!.profiles {
    firestoreManager.fetchProfile(uid: t, completion: { [self] profile in
        DispatchQueue.main.async {
           profiles.append(profile)
        }
    })
}

also I would recommend to avoid same naming for your custom types with SDK types - there might be very confusing non-obvious errors

// List model below might conflict with SwiftUI List
return try? queryDocumentSnapshot.data(as: List.self)
0
votes

As per my knowledge its not the problem from firebase end, because the ones data fetched the new data is updated. You are facing problem of image caching. Caching is a technique that stores a copy of a given resource. So when the image is loaded for first time it get cached and whenever you are reloading images are displayed from cache instead of loading from URL. This is done for more network usage.

You can programatically clear cache by adding following code before your image loading.

Alamofire uses NSURLCache in the background so you just have to call:

NSURLCache.sharedURLCache().removeAllCachedResponses()

Update for Swift 4.1

URLCache.shared.removeAllCachedResponses()