0

This is my simple View:

struct Element: Identifiable {
    let id = UUID()
    let text: String
    init(text: String) {
        self.text = text
    }
}

struct ServicesView: View {
    @FetchRequest(sortDescriptors: [SortDescriptor(\.date, order: .reverse)], animation: .easeIn)
    private var results: FetchedResults<Month>
    var elements = [Element(text: "a"), Element(text: "b")]
    var body: some View {
        NavigationView {
            List(elements) { element in
                NavigationLink {
                    YearView(months: [])
                } label: {
                    Text("abc")
                }
            }
            .navigationTitle("Service")
        }
    }
}

This way the app is not working correctly, because it pops up my View (YearView) from NavigationView when app goes to inactive mode and YearView is already presented.

But, when I change to:

@State var elements = [Element(text: "a"), Element(text: "b")]

it works VERY correctly. I don't know why it makes a difference, but OK. I understand WHAT is wrong.

Unfortunately I need elements as computed property... and as a @State (for above issue). But @State cannot be used for computed properties. How can I resolve it?

My Element here (Group in production code) is created base on fetched Months.

This is my real code:

struct Group: Identifiable { //Element in above example
    let months: [Month]
    var id = UUID()
    var descriptiveYear: String {
        months.first?.descriptiveYear ?? ""
    }
    init(months: [Month]) {
        self.months = months
    }
}

private var elements: [Group] {
    var groups = [[Month]]()
    var months = [Month]()
    results.forEach { month in
        guard months.isEmpty else {
            if month.currentYear == months.first?.currentYear {
                months.append(month)
            } else {
                groups.append(months)
                months = [month]
            }
            return
        }
        months.append(month)
    }
    return groups.map { Group(months: $0) }
}
halfer
  • 19,824
  • 17
  • 99
  • 186
Bartłomiej Semańczyk
  • 59,234
  • 49
  • 233
  • 358
  • You aren't showing enough code to reproduce this, but it's presumably because `Group` is `Hashable` or `Identifiable` -- when you use `@State`, the `Group`s never change. When you don't use it, the group IDs change on every render, making the `NavigationLink` think that it is _new_ information – jnpdx Jan 25 '23 at 23:58
  • Why do you need a `State`? The `FetchRequest` would trigger a redraw when when the changes are of type value, `State` can't observe CoreData objects – lorem ipsum Jan 25 '23 at 23:59
  • @loremipsum I have investigated that State resolve my issue with popping View from NavigationView when app goes to inactive mode. I have a `Month` entity in Core Data. `Group` is a wrapper for some of them. I need to create and display Groups base on Months. Group is a set of months. – Bartłomiej Semańczyk Jan 26 '23 at 00:04
  • @jnpdx I will prepare simplified code – Bartłomiej Semańczyk Jan 26 '23 at 00:05
  • Why not just use SectionedFetchRequest? It seems like you are going around in circles with this question haven't you asked about this before? – lorem ipsum Jan 26 '23 at 00:19
  • Yep -- my previous diagnosis looks right. The new `Element`s get new IDs on every render. If you don't want that to happen, you should provide consistent, stable IDs – jnpdx Jan 26 '23 at 01:20
  • @loremipsum Yes, I have asked, but SectionedFetchRequest will not be best, because I need to display only first item from sections... not all of them. If you know how to do it... I will do it... – Bartłomiej Semańczyk Jan 26 '23 at 16:18
  • @jnpdx you were totally right. It was indeed, the reason... II provided consistent stable id and... it works...;) Thank you... – Bartłomiej Semańczyk Jan 26 '23 at 16:34

1 Answers1

0

These are all Property wrappers that can be used to update a View.

@AppStorage
@FetchRequest
@GestureState
@Namespace
@NSApplicationDelegateAdaptor
@Published
@ScaledMetric
@SceneStorage
@State
@StateObject
@UIApplicationDelegateAdaptor
@Binding
@Environment
@EnvironmentObject
@FocusedBinding
@FocusedValue
@ObservedObject

In your case, to replace @State, it is recommended to use @StateObject or @ObservedObject to separate the business logic from the View and notify the View of changes through their @Published properties.

Please take a look. https://www.hackingwithswift.com/quick-start/swiftui/how-to-use-observedobject-to-manage-state-from-external-objects

Quang Hà
  • 4,613
  • 3
  • 24
  • 40
  • It doesnt work, because property wrapper cannot be applied to a computed property. It is for any property wrapper. – Bartłomiej Semańczyk Jan 26 '23 at 16:27
  • yes, I know, so why do we must to use a computed property, why don't we compute data in a function then publish value to a properties that tight to UI? – Quang Hà Jan 27 '23 at 01:12