1

I have an issue in SwiftUI where my image from a downloadURL doesn't publish to the view for visible items. The SwiftUI elements that are off the view do get updated as I scroll. The issue is that initially, the image URL is "" (blank) when the view renders. Thefore, the default image is rendered, as expected. Although, when the downloadURL task completes, the image is not updated. If I use a combination of willSet and/or objectWillChange.send() on the attribute, 1 image gets updated, but only 1.

High level flow:

  • Get Series data file from Firebase
  • Convert series file data to Series objects within the modelView
  • For each Series object, get the image from Firebase asynchronously

Here is the view code

import SwiftUI
import struct Kingfisher.KFImage
import Firebase

struct SeriesView: View {
    
    // Firebase auth
    @EnvironmentObject var authState: AuthenticationState
    
    // Listen for view model state changes, i.e. series
    @ObservedObject var seriesList = SeriesList()
        
    private func signoutTapped() {
        authState.signout()
    }
    
    // search bar
    @State var searchText = ""
    @State var isSearching = false
    
    // firebase
    //@State private var imageURL = URL(string: "")
    @State private var imageURLString = ""
    let storage = Storage.storage()
    
    // view boday
    var body: some View {
        NavigationView {
            ScrollView {
                SearchBar(searchText: $searchText, isSearching: $isSearching)
  
                LazyVGrid(columns: [
                    GridItem(.flexible(minimum: 100, maximum: 200), spacing: 16, alignment: .top),
                    GridItem(.flexible(minimum: 100, maximum: 200), spacing: 16, alignment: .top),
                    GridItem(.flexible(minimum: 100, maximum: 200), spacing: 16)
                ], alignment: .leading, spacing: 16, content: {
                    // filter for search entry
                    ForEach(seriesList.seriesArray//) { series in
                                .filter({"\($0.uniqueId)".contains(searchText)
                                                            || "\($0.recordName)".contains(searchText)
                                                            || "\($0.seriesName)".contains(searchText)
                                                            || "\($0.fromYear)".contains(searchText)
                                                            || "\($0.toYear)".contains(searchText)
                                                            || searchText.isEmpty}), id: \.self) { series in
                        SeriesInfo(series: series)
                    }
                }).padding(.horizontal, 12)
            }
            .navigationBarTitle(kMyToyBoxText)
            .navigationBarItems(trailing: Button(action: signoutTapped, label: {
                Image(systemName: "person.circle")
                Text("Logout")
            }))
        }
    }
}
struct SeriesInfo: View {
    
    let series: Series
    
    var body: some View {
        NavigationLink(
            destination: DetailView(series: series.seriesName)) {
            VStack(alignment: .leading, spacing: 4) {
                KFImage(URL(string: series.imageFirebaseURLString))
                    .onSuccess { r in
                    }
                    .onFailure { error in
                    }
                    .placeholder {
                        // Placeholder while downloading.
                        kMyToyBoxLogoImage
                            .resizable()
                            .font(.largeTitle)
                            .opacity(0.3)
                            .scaledToFit()
                            .cornerRadius(22)
                    }
                    .cancelOnDisappear(true) // cancel if scrolled past
                    .resizable()
                    .scaledToFit()
                    .cornerRadius(22)
                Text(series.seriesName)
                    .font(.system(size: 10, weight: .semibold))
                    .padding(.top, 4)
                // convert to strings to avoid commas
                Text("\(String(series.fromYear)) - \(String(series.toYear))")
                    .font(.system(size: 9, weight: .regular))
                
                Spacer()
            }
        }
    }
}

Images Default image rendered

Scroll down and images are available

My Series class (also tried as a struct, but it alter the behavior)

// series class
class Series: Identifiable, ObservableObject {

    var id = UUID()
    let uniqueId: String
    let recordName: String
    let seriesName: String
    let fromYear: Int
    let toYear: Int
    let isMyToyBoxActive: Bool
    let logoImageName: String
    
    // generated data and needs to be published
    @Published var imageFirebaseURLString: String
    
    init (uniqueId: String, recordName: String, seriesName: String, fromYear: Int, toYear: Int, isMyToyBoxActive: Bool, logoImageName: String, imageFirebaseURLString: String = "") {
        self.uniqueId = uniqueId
        self.recordName = recordName
        self.seriesName = seriesName
        self.fromYear = fromYear
        self.toYear = toYear
        self.isMyToyBoxActive = isMyToyBoxActive
        self.logoImageName = logoImageName
        self.imageFirebaseURLString = imageFirebaseURLString
    }
}
extension Series: Hashable {

    public func hash(into hasher: inout Hasher) {
         hasher.combine(ObjectIdentifier(self).hashValue)
    }
}

extension Series: Equatable {

    public static func ==(lhs: Series, rhs: Series) -> Bool {
        return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
    }
}

ModelView code - this code functions as expected to populate the view except the photo data from the downloadURL call

// Series listener and data retriever
class SeriesList: ObservableObject {
    @Published var seriesArray = [Series]()
    
    init() {
        
       //... code removed for getting Series file data from Firebase

       let series: Series = self.getSeriesFromString(stringSeries: $0)
       self.seriesArray.append(series)

        // set the image
        let storageRefImage = storage.reference().child(kSeriesImageRoot)
        let seriesImageRef = storageRefImage.child(series.logoImageName)
                                
        // local image
        seriesImageRef.downloadURL { url, error in
            if let error = error {
            }
            else
            {
                series.imageFirebaseURLString = url!.absoluteString // <-- code to update the image URL
            }
        }

    }
}

1 Answer 1

1

Make it observed in info view as well

struct SeriesInfo: View {
    
    @ObservedObject var series: Series     // << here !!
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you, works like a charm now. Its amazing how responsive SwiftUI is!

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.