This code works fine
ForEach(items.filter({$0.matchesSearch(text: searchText) }), id: \.id) { item in
but moving the collection out of the ForEach
let takenOut = items.filter({$0.matchesSearch(text: searchText) })
ForEach(takenOut, id: \.id) { item in
results in this error
Generic struct 'ForEach' requires that 'LazyFilterSequence<Results<T>>.SubSequence'
(aka 'LazyFilterSequence<Slice<Results<T>>>') conform to 'RandomAccessCollection'
any ideas why?
i need to take the sequence out so i can use it's count
whole code for the context
struct SearchSectionView<T: DatabaseItem & Identifiable & SearchMatchingSupport>: View {
@Binding var searchText: String
@ObservedResults(
T.self,
where: { $0.recordState == .published }
) var items
var body: some View {
Section("Results") {
let o = items.sorted(byKeyPath:"name", ascending: true).filter({$0.matchesSearch(text: searchText)})
ForEach(o, id: \.id) { item in
Label(item.suggestionText(), systemImage: "circle").searchCompletion("\(item.id)")
}
}
}
}
CodePudding user response:
Why does assigning the result of filter
to a let
constant cause it to no longer work?
Short Answer:
filter
returns different results based upon what the receiver expects.
Long Answer
Consider this example:
let arr = ["apple", "apricot", "banana", "pear"]
let filtered = arr.lazy.filter { $0.hasPrefix("a") }
func count(arr: [String]) -> Int {
arr.count
}
// This works
count(arr: arr.lazy.filter { $0.hasPrefix("a") })
// This doesn't work
count(arr: filtered)
Here it is running in a Playground. When filter is passed directly to count
, it works. When filter is assigned to the constant filtered
, it doesn't work when passed to count
.
So, what is going on here?
In the first call to filter
, it returns a LazyFilterSequence<String>
.
When passed to count
which is expecting a [String]
, filter
returns a [String]
.
To make this example more similar to your case, let's make count
generic:
func count<T: RandomAccessCollection>(arr: T) -> Int {
arr.count
}
Now, like ForEach
, count
is expecting something that adopts RandomAccessCollection
. In this case, Swift still chooses the version of filter
that returns an Array
:
Conclusion
The conclusion is, there are different versions of filter
, and the one you get is based upon the expectations of the receiver. In this example, when the result was assigned to filtered
, Swift returned a version that required the least up front work. That way, if only the first item in the result is needed, a lot of work is saved.
In the second case where we are passing the filtered result to count()
, Swift does the extra work to turn the result back into an Array
which is what count
expects.
In your case, ForEach
expects something that adopts RandomAccessCollection
. Swift chooses a version of filter
that returns a value that adopts that protocol.
Note:
You can use let
to store the search result, you just need to give your constant
the explicit type that is passed to ForEach
. For example, in my case:
let filtered: [String] = arr.lazy.filter { $0.hasPrefix("a") }
count(arr: filtered) // this now works
[Option]-click on your call to filter
to find the type that filter
is returning and then use that type in your let
statement.
CodePudding user response:
This filtering shouldn't be in the view, move it to your model, so that the view's model becomes sortedAndFilteredStuff
and not stuff that the view has to then process itself. You'll get errors, sometimes hard-to-understand errors, if you put regular swift code inside a ViewBuilder
block.