UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: List, ForEach not updating correctly only showing every other updated

Forums > SwiftUI

So I am using Core data and Swiftui and everything have been working fine but in this one view I have a List, ForEach that is giving not working.

So for testing purpose my code currently look like this:


@ObservedObject var viewModel = NewLearningWhyViewModel()

VStack {
ForEach(viewModel.whys, id: \.self) { why in
                Text(why.why)
            }

List {
     ForEach(viewModel.whys, id: \.self) { why in
                    Text(why.why)
                }
            }

           Button(action: {
                viewModel.createWhy(why: "Test", count: viewModel.whys.count, learning: learn)
                viewModel.fetchWhy(predicate: NSPredicate(format: "parentLearning == %@", learn))
            }){
                Text("Add")
                    .font(.title)
                    .foregroundColor(.white)
            }
            }

The problem is my List { ForEach, first time I press add button it shows the new why, second time i press the button the whole list goes away, the third time I press the button the list shows again with all 3 items.

To test the problem I added that first ForEach part and that shows the correct item at all times, so there is not a problem with the viewmodel or adding the items, the items are added and it is published from the viewmodel since that part is updated.

Does anyone have any clue why my List { ForEach only show every other time?

2      

Since the problem lies somewhere in here:

           Button(action: {
                viewModel.createWhy(why: "Test", count: viewModel.whys.count, learning: learn)
                viewModel.fetchWhy(predicate: NSPredicate(format: "parentLearning == %@", learn))
            }){

we need to see your NewLearningWhyViewModel code.

2      


protocol NewLearningWhyViewModelProtocol {

    func createWhy(why: String, count: Int, learning: LearningMO)
    func fetchWhy(predicate: NSPredicate)
    var whys: [WhyMO] { get set }
    func limitText(index: Int)
    var textLimit: Int { get }
}

final class NewLearningWhyViewModel: ObservableObject {
    @Published var textLimit = 30

    @Published var whys = [WhyMO]() 

    var dataManager: DataManagerProtocol

    init(datamanger: DataManagerProtocol = DataManager.shared) {
        self.dataManager = datamanger
    }
}

extension NewLearningWhyViewModel: NewLearningWhyViewModelProtocol {
    func limitText(index: Int) {
        if whys[index].why.count > textLimit {
            whys[index].why = String(whys[index].why.prefix(textLimit))
            print("inside")
            print(whys[index].why)
            update()
        }
    }

    func createWhy(why: String, count: Int, learning: LearningMO) {
        print("before")
        print(whys)
        dataManager.createWhy(why: why, count: count, learning: learning)
        print("after")
        print(whys)
        fetchWhy(predicate: NSPredicate(format: "parentLearning == %@", learning))
    }

    func fetchWhy(predicate: NSPredicate) {
        whys = dataManager.fetch(WhyMO.self, predicate: predicate, sortDescriptors: [NSSortDescriptor(keyPath: \WhyMO.order, ascending: true)], limit: nil)

        print(whys)
    }

}

Datamanager fetch

func fetch<T>(_ objectType: T.Type, predicate: NSPredicate?, sortDescriptors: [NSSortDescriptor]?, limit: Int?) -> [T] where T : NSManagedObject {
        let result: Result<[T], Error> = dbHelper.fetch(objectType, predicate: predicate, sortDescriptors: sortDescriptors, limit: limit)

        switch result {
        case .success(let object):
            return object
        case .failure(let error):
            fatalError(error.localizedDescription)
        }
    }

fbhealer fetch

func fetch<T: NSManagedObject>(_ objectType: T.Type, predicate: NSPredicate? = nil, sortDescriptors: [NSSortDescriptor]? = nil, limit: Int? = nil) -> Result<[T], Error> {
            let request = objectType.fetchRequest()
            request.predicate = predicate
        if let sort = sortDescriptors {
            request.sortDescriptors = sort
        }
            if let limit = limit {
                request.fetchLimit = limit
            }
            do {
                print(request)
                let result = try context.fetch(request)

                return .success(result as? [T] ?? [])
            } catch {
                return .failure(error)
            }
        }

2      

One or two style things I would mention is that fetchWhy updates an instance variable but doesnt return anything, you could return the whys array and then set the instance from the return value. This makes the method more testable.

Also why expose a method, even to yourself that takes a NSPredicate as an input paramter? I know the underlying API takes a predicate but from an API point of view NSPredicate is more flexible and Apple have to open the whole database up to muptiple query types, but depending on how many different searches you have to make you may not have that restriction, and could make that cleaner at the callsite i.e.

fetchWhy(learning, type:.parentLearning)

which takes the search term and some Enum


enum WhySearchType:String {
    case parentLearning
    case studentLearning
}

Then within that function you can build up the predicate from

NSPredicate(format: "\(whySearchType.rawValue) == %@", learning)

Back to what is happening here. It might well be in SwiftUI or Core Data, or an intergration between the two.

There are two UI views showing the whys, though, the VSTack and the List. Are you saying that both disapper, or one? I also notice that the createWhy also fetches a result and updates the @published whys. The Add button calls createWhy followed by fetchWhy, which means you get two calls to the databse in rapid succession. Maybe that is causing an issue with coredata which falls over, returns the whys as an empty array, and those are published to your list.

It could also be a threading issue. At least on UIKit you have to be careful about threading and I think that you may be getting into conflict here.

So try not calling the fetchWhy in the button.

(Note: you dont seem to be printing out the newly found whys in createWhy, after the fetchWhy either but before. If you move that print you will probaly see an empty array every second button press).

2      

Thanks for all the help, I got tips from stackoverflow that solved this, no clue why it worked... Adding .id(UUID()) to the list and suddenly it started working correctly...

Huge thanks to @eoinnorris I will rework some of it :)

2      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.