3

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?

Jaanus
  • 17,688
  • 15
  • 65
  • 110

1 Answers1

3

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
}
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • Thank you. Exactly what I needed, works. Just a detail, I am not sure what is the best SwiftUI practice - should the rowView function be inside the SomeView struct, or outside as a separate global function? – Jaanus Jan 01 '20 at 09:38
  • 1
    I think it depends on design... if those generated views are same kind of views then it can be factory method (static) of that view, if they are completely different that can be just factory global function, etc. – Asperi Jan 01 '20 at 09:43