core-dataswiftuifetchrequest

How do I transform SwiftUI fetch request results based on related objects?


I am building a SwiftUI list where I need a dynamic predicate. The approach is discussed here: https://www.hackingwithswift.com/books/ios-swiftui/dynamically-filtering-fetchrequest-with-swiftui

Here is my code so far:

struct SomeView: View {

    var collection: Collection
    var messages: FetchRequest<Message>

    init(collection: Collection) {
        let predicate : NSPredicate = NSPredicate(format: "collection = %@", collection)
        self.collection = collection
        self.messages = FetchRequest<Message>(entity: Message.entity(), sortDescriptors: [NSSortDescriptor(key: "date", ascending: true)], predicate: predicate)
    }

    var body: some View {
        List(messages.wrappedValue, id: \.uniqueIdentifier) { message in
            // construct the UI
        }
    }
}

So far so good.

What I can’t figure out how to do: I need to transform the messages elements based on some other messages in the results (let’s say based on previous message for simplicity). messages[0] should look a particular way. messages[1] look depends on messages[0]. messages[2] depends on messages[1] and so on. I cannot precompute this, since it may vary across time. It should be computed in the context of this specific fetch request/result.

I could express this as some transient computed property on the Message object, which the view code could then use to branch out. I could have a function where I give a particular message and the array of messages, the function looks up the message and other messages and sets the state of a given message based on that. However, SwiftUI limits what I can do in View code, I can’t execute functions this way.

I can run map or flatmap where I access the wrappedValue, but those don’t let me access other elements of the collection to make decisions (I think?).

How would I run this kind of transformation in this context?


Solution

  • If I correctly understood your description (and taking into account that FetchedResults is a RandomAccessCollection) I would go with the following approach

    var body: some View {
        List(messages.wrappedValue, id: \.uniqueIdentifier) { message in
            rowView(for: message, from: messages.wrappedValue)
        }
    }
    
    func rowView(for message: Message, from result: FetchedResults<Message>) -> some View {
       // having .starIndex, .endIndex, .position, etc. do any dependent calculations here
       // and return corresponding View
    }