SwiftUI UITableView was told to layout its visible cells and other contents without in view hierarchy
Asked Answered
L

1

55

I have a list in a view (lets call it view A) where I push a view B via NavigationLink from view A and manipulate the CurrentSubjectValue to the initial view A. The moment the data is added and the didChangd.send(value) is dispatched to view A I get a warning that updating a cell that does not belong to a view. It seems that view A is removed from window which is unintuitive based on my previous UINavigationController behavior. Any idea?

View A (MainView)

struct MainView: View {

    @ObjectBinding var currentProfileIdStore = CurrentProfileIdStore.shared

    var body: some View {

        NavigationView {
            if (currentProfileIdStore.didChange.value == nil) {
                NavigationLink(destination: ProfilesView()){ Text("show profiles view")}
            } else {
                List {
                    Section {
                        ForEach(0..<1) { _ in
                            PresentationLink(destination: ProfilesView()) {
                                Text("Profiles")
                            }
                        }
                    }

                    Section {
                        ForEach(0..<1) { _ in
                            Text("Items")
                        }
                    }
                }.listStyle(.grouped)
            }
        }.navigationBarTitle(currentProfileIdStore.didChange.value ?? "unknown")

    }
}

Then profiles view is presented

struct ProfilesView : View {

    @ObjectBinding var profileManager = CurrentProfileIdStore.shared
    @ObjectBinding var profileProvider = ProfileProvider.shared

    var body: some View {

        NavigationView {

            List {
                Section {
                    ForEach(0..<1) { _ in
                        NavigationLink(destination: NewProfile()) {
                            Text("Add Profile")
                        }
                    }
                }


                Section {
                    ForEach(self.profileProvider.didChange.value) { profile in
                        VStack {

                            if profile.id == self.profileManager.didChange.value {
                                Image(systemName: "g.circle").transition(.move(edge: .top))
                            }

                            ProfileCellView(profile: profile)
                            Text(self.profileManager.didChange.value ?? "unknown").font(.footnote).color(.secondary)
                        }
                    }
                }

            }
            .navigationBarTitle("Profiles")
                .listStyle(.grouped)
        }
    }
}

and then we could add a new profile

struct NewProfile: View {

    @State var name: String = ""
    @State var successfullyAdded: Bool = false

    @ObjectBinding var keyboardStatus = KeyboardStatus()
    var profileProvider = ProfileProvider.shared

    var body: some View {

        NavigationView {
                VStack {
                    if self.successfullyAdded  {
                        Text("Successfully added please go back manually :)").lineLimit(nil)
                    }
                    Form {
                        Section(header: Text("Enter name")) {
                            TextField("Enter your name", text: $name)
                        }
                    }
                    if name.count > 3 {
                        Button(action: {
                            self.profileProvider.addProfile(new: Profile(name: self.name, id: UUID().uuidString)) {
                                assert(Thread.isMainThread)
                                self.successfullyAdded = true
                            }
                        }) {
                            Text("Add")
                        }.transition(.move(edge: .bottom))
                    }
                }.padding(.bottom, self.keyboardStatus.didChange.value)
        }.navigationBarTitle("Add Profile")

    }
}

and then there is provider

class ProfileProvider: BindableObject {

    enum Query {
        case all

        var filter: (Profile) -> Bool {
            switch self {
            case .all:
                return { p in true }
            }
        }
    }

    static var samples: [Profile] = []

    static var shared: ProfileProvider = {

        return ProfileProvider(query: .all)
    }()

    var didChange = CurrentValueSubject<[Profile], Never>([])

    private var query: Query?

    private init(query: Query? = nil) {

        self.query = query
        assert(Thread.isMainThread)

        dispatchDidChange()
    }

    func dispatchDidChange() {
        assert(Thread.isMainThread)
        if let valid = self.query {
            didChange.send(ProfileProvider.samples.filter {  valid.filter($0) })
        } else {
            didChange.send([])
        }
    }

    func addProfile(new: Profile, comp: () -> Void) {

        ProfileProvider.samples.append(new)
        comp()
        dispatchDidChange()
    }

keep in mind that multiple views share the same provider subject. so the issue happens when the new profile is added.

self.profileProvider.addProfile(new

there are two more issues, one that when back in mainView the button no longer works. Also when modally presented I don't yet know how to go back manually.

Lightly answered 14/7, 2019 at 13:33 Comment(5)
#1. It's a warning, right? The important question is - does it work as you expect it to? Remember, SwiftUI is barely in beta 3 of version 1! I get those warnings too. Hopefully, this will be improved in future betas. (1) To turn off these warnings, set OS_ACTIVITY_MODE to disable in your builds. Not necessarily recommending that - but it does get rid of noise. (2) If the behavior is not what you'd expect, post some code. So we can help.Dissected
#2. Could it be a timing issue? If so, look at a question I had answer Friday: #57008751Dissected
Consider adding some code. A minimal example that shows us the error. in action Otherwise, it's all speculation. See stackoverflow.com/help/how-to-askKesterson
I'm also seeing this. Xcode 11.2.1Bounded
Something odd about your code which may cause issues: You put your NavigationLink for adding a new profile in a ForEach for no reason at all. Whats more, the object you give the ForEach is not Identifiable. Which means that when your data changes the old NavigationLink shouldn't exist. Remove the ForEach it does nothing since it goes through 0..1 (one index). I don't know if this solves your problem but its useless code.. so remove it.Chicanery
R
0

I can share a simple example and you can adjust your code according to this

import SwiftUI
import Combine

class DataModel: ObservableObject {
    @Published var data: [String] = []

    func addData(_ item: String) {
        data.append(item)
        // didChange.send(value) equivalent handled by @Published
    }
}

struct ContentView: View {
    @StateObject var model = DataModel()

    var body: some View {
        NavigationView {
            List {
                ForEach(model.data, id: \.self) { item in
                    Text(item)
                }
                NavigationLink(destination: DetailView(model: model)) {
                    Text("Add Item")
                }
            }
        }
    }
}

struct DetailView: View {
    @ObservedObject var model: DataModel

    var body: some View {
        VStack {
            Button("Add New Item") {
                model.addData("New Item")
            }
        }
    }
}
  • DataModel is an observable object that holds the data and a method to add new data.
  • ContentView displays a list of data and has a NavigationLink to DetailView.
  • DetailView allows the user to add new data.
Renowned answered 27/6 at 12:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.