PredicateKit icon indicating copy to clipboard operation
PredicateKit copied to clipboard

SwiftUI improvements: SectionedFetchRequest and dynamic changes

Open RCCoop opened this issue 2 years ago • 4 comments

As of iOS 15 & MacOS 12, there are some new tools we can work with in SwiftUI:

SectionedFetchRequest and SectionedFetchResults which are like FetchRequest and FetchedResults just with added section support.

And the ability to dynamically update a FetchRequest's (or SectionedFetchRequest's) predicates through the nsPredicate property.

It would be nice to add PredicateKit support for these. I'd be happy to make a PR after I have some time to work on this. Shouldn't be too difficult, just a little time-consuming!

RCCoop avatar Jan 15 '24 21:01 RCCoop

Awesome. Looking forward to reviewing the PR 👍.

ftchirou avatar Jan 17 '24 19:01 ftchirou

Almost done! So far it works to just copy and make small modifications to your existing code, but a test case is throwing an unexpected exception.

Here is a SwiftUI.SectionedFetchRequest extension:

@available(iOS 15.0, watchOS 8.0, tvOS 15.0, macOS 12.0, *)
extension SectionedFetchRequest where Result: NSManagedObject {
  public init(fetchRequest: FetchRequest<Result>, sectionIdentifier: KeyPath<Result, SectionIdentifier>, animation: Animation? = nil) {
    let entityName = Result.entity().name ?? String(describing: Result.self)
    let fetchRequestBuilder = NSFetchRequestBuilder(entityName: entityName)
    self.init(fetchRequest: fetchRequestBuilder.makeRequest(from: fetchRequest), sectionIdentifier: sectionIdentifier, animation: animation)
  }
}

And here is a test case:

  func testSectionedFetchRequestPropertyWrapperWithNoPredicate() throws {
    struct ContentView: View {
      @SwiftUI.SectionedFetchRequest(
        fetchRequest: FetchRequest()
          .sorted(by: \.billingInfo.accountType, .ascending)
          .sorted(by: \.name, .ascending),
        sectionIdentifier: \.billingInfo.accountType
      )
      var users: SectionedFetchResults<String, User>

      var body: some View {
        List(users, id: \.id) { section in
          Section(section.id) {
            ForEach(section, id: \.objectID) { user in
              Text(user.name)
            }
          }
        }
      }
    }

    let view = ContentView().environment(\.managedObjectContext, .default)
    let request = try XCTUnwrap(
      Mirror(reflecting: view).descendant("content", "_users") as? SwiftUI.SectionedFetchRequest<String, User>
    )

    XCTAssertEqual(request.projectedValue.wrappedValue.nsPredicate, NSPredicate(value: true))
  }

Both of those are almost exactly copied from your code from SwiftUI.FetchRequest, but the final XCTAssertEqual throws an exception when calling request.projectedValue.

The exception code is Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0) and a warning in the console says Accessing StateObject's object without being installed on a View. This will create a new instance each time.

There must be something different in the underlying property wrapper of SectionedFetchRequest vs FetchRequest. Do you have any ideas how we'd get around that?

RCCoop avatar Jan 18 '24 15:01 RCCoop

There must be something different in the underlying property wrapper of SectionedFetchRequest vs FetchRequest.

Indeed, SectionedFetchRequest has a property _controller of type StateObject<FetchConroller<String, User, SectionedFetchResult<String, User>> (in your example). Trying to read the value of the request must be accessing this property under the hood. This property should be accessed only after the view has been added to the view hierarchy. Otherwise we get the crash you're seeing.

Do you have any ideas how we'd get around that?

Off the top of my head, no. But feel free to open a PR without this test. I'll look into it and figure out something before merging. Might take some time though.

ftchirou avatar Jan 28 '24 22:01 ftchirou

Thanks! I'll open up the PR now.

RCCoop avatar Jan 29 '24 17:01 RCCoop

Changes merged in 31dc2a5182dd4a7ac7375913fde7b28b07a28dee. See https://github.com/ftchirou/PredicateKit/pull/25#issuecomment-2038404030.

ftchirou avatar Apr 04 '24 22:04 ftchirou