swiftuiobservableobjectproperty-wrapper-published

What's the proper way to set up a View model using an @Published property of an Array of my custom type and using that in my custom View?


In the following files where I am attempting to use the data from an API call in a MVVM type pattern to display the data in my view. I am getting an error on the view when I try to create a List view. I have tried using a dollar sign "$" on the reference to the viewModel and on the reference to the array, but neither is working. I've watched countless videos and read quite a few tutorials on this, but nothing is solving my error. Any help or advice would be greatly appreciated.

The error messages I am getting are: In ReturnsListView where I try to use a List, I get 2 messages:

  1. "Cannot convert value of type '[ReturnItem]' to expected argument type 'Binding'" and
  2. "Generic parameter 'Data' could not be inferred"

Also in ReturnsListView, but on the line where I try to access "returnItem.productImageUrl" I get the third message: 3. "Cannot convert value of type 'Binding' to expected argument type 'String'"

import Foundation

struct ReturnItem: Codable, Identifiable {

    let id = UUID().uuidString
    var puNbr: Int
    var puLineNbr: Int
    var packCd: String
    var puQty: Int
    var partialReturn: Bool
    var itemDescription: String
    var itemSize: String
    var itemPack: Int
    var reasonType: String
    var reasonSubtype: String
    var invoiceNbr: Int
    var productImageUrl: String
    var formattedRetailUPC: String
    var puStatus: String
    var editable: Bool
    
    enum CodingKeys: CodingKey {
        case puNbr, puLineNbr, packCd, puQty, partialReturn,
             itemDescription, itemSize, itemPack, reasonType, reasonSubtype,
             invoiceNbr, productImageUrl, formattedRetailUPC, puStatus, editable
    }
}

Then I have set up this ViewModel:

import Foundation

@MainActor
class ReturnsViewModel: ObservableObject {

    @Published var returnItemsArray: [ReturnItem] = []
    @Published var displayAPIError: Bool = false
    @Published var errorText: String = ""
    
    func getDataWithAlamoFire() async {
        Service.sharedInstance.returnsApi { returnItems in
            self.returnItemsArray = returnItems
            
        } failure: { alertType, errorText in
            print("🪵 ReturnsListAPI AlertType is: \(alertType)")
            print("‼️ ReturnsListAPI ErrorText is: \(errorText)")
            self.displayAPIError = true
            self.errorText = errorText
        }
    }
}

And finally here is my ReturnsListView:

import SwiftUI

struct ReturnsListView: View {
    
    @StateObject var returnsVM = ReturnsViewModel()
    @State var partialReturnToggle = false
    @State var showAlert = false
    
    var imageDimensions = 96.0
    var iPadImageDimentions = 80.0
    var iPadTextSize = 15.0
    
    var body: some View {
        NavigationView {
            VStack(alignment: .center) {
// Getting the first two errors on the line below
// 1. "Cannot convert value of type '[ReturnItem]' to expected argument type 
// 'Binding<Data>'"
// and
// 2. "Generic parameter 'Data' could not be inferred"
                List(returnsVM.returnItemsArray) { returnItem in
                    HStack { // main return list row
                        // Left side of row with Image and Partial Pack Toggle
                        VStack(alignment: .center) {
                            if displayImages {
// Getting the following message on the line below:
// "Cannot convert value of type 'Binding<Subject>' to expected argument type 'String'"
                                Image(uiImage: getReturnsImage(
                                    productImageUrlString: returnItem.productImageUrl,
                                    width: imageDimensions,
                                    height: imageDimensions)
                                )
                                .resizable()
                                .aspectRatio(contentMode: .fit)
                                .frame(width: imageDimensions, height: imageDimensions)
                            }
                            VStack{
                                Text("Partial Pack")
                                Toggle("Partial Pack Toggle", isOn: $partialReturnToggle)
                                    .labelsHidden()
                                    .toggleStyle(SwitchToggleStyle(tint: .orange))
                            }
                        }
                        .padding(.trailing)
                        
                        // Right side of row with Item details and quantity buttons
                        VStack(alignment: .center) {
                            Text(returnItem.itemDescription ?? "") // item description
                                .fontWeight(.black)
                                .frame(alignment: .center)
                            HStack(alignment: .center) {
                                Spacer()
                                Text(returnItem.puStatus ?? "") // pickup status
                                    .font(.system(size: 17))
                                    .fontWeight(.bold)
                                    .foregroundColor(.red)
                                Spacer()
                            }
                            HStack {
                                Text(returnItem.itemSize ?? "") // itemSize
                                    .font(.system(size: 20))
                                Spacer()
                                if let itemPack = returnItem.itemPack {
                                    Text("(\(itemPack) pack)") // itemPack
                                        .font(.system(size: 20))
                                }
                            }
                            HStack(alignment: .leading) {
                                Text(returnItem.formattedRetailUPC ?? "") // formattedRetailUPC
                                    .font(.system(size: 20))
                                Text(returnItem.invoiceNbr) // invoice number
                                    .font(.system(size: 20))
                            }
                        }
                    }
                    .listRowBackground(Color(UIColor.tertiarySystemBackground))
                }
                .listStyle(.plain)
                .ignoresSafeArea()
                .offset(y: -16)
            }
            .background((Color(UIColor.tertiarySystemBackground)))
        }
        .navigationViewStyle(.stack)
        .task {
            await returnsVM.getDataWithAlamoFire()
        } // NaviationView
    } // End of Body
}

func getReturnsImage(productImageUrlString: String, width: Double, height: Double) -> UIImage {
    if productImageUrlString.endsWith("nulljpg") {
        return UIImage(named: "placeHolderImage")!
    } else {
        let formattedImageUrlString = productImageUrlString.replacingOccurrences(of: " ", with: "%20")
        if let productImageUrl = URL.init(string: formattedImageUrlString) {
            print("🪵 Formatted ProductImageURL is: \(productImageUrl)")
            let frame = CGRect(x: 0, y: 0, width: width, height: height)
            let prodUIImageView = UIImageView(frame: frame)
            prodUIImageView.af.setImage(withURL: productImageUrl)
            return prodUIImageView.image ?? UIImage(named: "placeHolderImage")!
        } else {
            return UIImage(named: "placeHolderImage")!
        }
    }
}

#Preview {
    ReturnsListView(navHeight: 42, infoString: "Test Customer - 888777", backPressed: {})
}

Solution

  • These errors are more often than not caused by a typo.

    If you comment out sections of your code you’ll find the specific issue.

    Short very specific views and reducing the need for type checking helps get more specific errors.