iOS13 DiffableDataSource Invalid parameter not satisfying: indexPath || ignoreInvalidItems - ios

I'm converting my collection view to new iOS13 UICollectionViewDiffableDataSource... so I need to update cell information on demand.
Here is my code:
let snap = self.diffDataSouce.snapshot
snap?.reloadItems(withIdentifiers: [itemToUpdate]) //reload cell info
self.diffDataSouce.apply(snap, animatingDifferences: true)
But I get Invalid parameter not satisfying: indexPath || ignoreInvalidItems ...why?
My current snap contains itemToUpdate and also my array of models...
I think it's because snap.indexOfItemIdentifier(itemToUpdate) returns not found (NSNotFound)...but that's should be impossible according data model.
Have you some hints?

Your data model has to conform to Hashable and Equatable, so that the diffing algorithm can track changes between snapshots.
If there is an issue with a collision between two objects, or you have implemented either of those protocols in such a way as to allow for two objects to appear equal to the diffing algorithm, you will get a runtime assert exception.
I'd track down exactly how your model object have inherited or implemented those protocols.

For my case, reloading a hashable item was the problem. Instead I should have deleted hashable item and inserted anew. Following are the specifics.
My diffable ItemIdentifierType was of AnyHashable type as in:
var dataSource: UICollectionViewDiffableDataSource<AHashableEnum, AnyHashable>!
And whenever I wanted to reload a single item like the author of this exchange, I was experiencing the crash:
var currentSnapshot = dataSource.snapshot()
currentSnapshot.reloadItems([hashableItemThatGotUpdated])
dataSource.apply(currentSnapshot, animatingDifferences: true)
I realized that since ItemIdentifierType is of AnyHashable type, reloading an item is not permitted if its hashvalue has changed. Because the hash value of the new item does not exist in the current snapshot and therefore is not reloadable. Instead I should have deleted the old item from the current snapshot and inserted the new Hashable identifer instead:
var currentSnapshot = dataSource.snapshot()
currentSnapshot.insertItems(identifiers: [NewHashableItemIdentifier], beforeItem: OldHashableItemIdentifier)
currentSnapshot.deleteItems(identifiers: [OldHashableItemIdentifier])
dataSource.apply(currentSnapshot, animatingDifferences: true)

Have you implemented the static func == for your model? I had a similar issue using structs where equality is evaluated among all properties

I ran into this problem when I was attempting to reload items that had been deleted from my snapshot. This will get you the same or a similar error. Make sure you data model and snapshot are in sync to avoid this error.

In my case I was comparing two instances of different types.
public enum ChatSectionEnum: Hashable {
case loading
case messages(String)
var sectionId: AnyHashable {
switch self {
case .loading:
return UUID() // UUID type
case .messages(let id):
return id // String type
}
}
public static func == (lhs: ChatSectionEnum, rhs: ChatSectionEnum) -> Bool {
lhs.sectionId == rhs.sectionId // Error
}
public func hash(into hasher: inout Hasher) {
hasher.combine(sectionId)
}
}

Related

Binding model and view: how to observe object properties

I have a view structured like a form that creates a model object. I am trying to bind the form elements (UIControl) to the model properties, so that the views auto-update when their corresponding model property is changed, and the model update when the controls are changed (two way binding). The model can change without the view knowing because multiple views can be linked to one same model property.
Approach 1: Plain Swift
My problem is the following: to observe changes to the model properties, I tried to use KVO in Swift, and specifically the observe(_:changeHandler:) method.
class Binding<View: NSObject, Object: NSObject, ValueType> {
weak var object: Object?
weak var view: View?
var objectToViewObservation: NSKeyValueObservation?
var viewToObjectObservation: NSKeyValueObservation?
private var objectKeyPath: WritableKeyPath<Object, ValueType>
private var viewKeyPath: WritableKeyPath<View, ValueType>
init(betweenObject objectKeyPath: WritableKeyPath<Object, ValueType>,
andView viewKeyPath: WritableKeyPath<View, ValueType>) {
self.objectKeyPath = objectKeyPath
self.viewKeyPath = viewKeyPath
}
override func bind(_ object: Object, with view: View) {
super.bind(object, with: view)
self.object = object
self.view = view
// initial value from object to view
self.view![keyPath: viewKeyPath] = self.object![keyPath: objectKeyPath]
// object --> view
objectToViewObservation = object.observe(objectKeyPath) { _, change in
guard var view = self.view else {
// view doesn't exist anymore
self.objectToViewObservation = nil
return
}
guard let value = change.newValue else { return }
view[keyPath: self.viewKeyPath] = value
}
// view --> object
viewToObjectObservation = view.observe(viewKeyPath) { _, change in
guard var object = self.object else {
// object doesn't exist anymore
self.viewToObjectObservation = nil
return
}
guard let value = change.newValue else { return }
object[keyPath: self.objectKeyPath] = value
}
}
}
However some of the properties of my model have types CustomEnum, CustomClass, Bool?, and ClosedRange<Int>, and to use observe I had to mark them as #objc dynamic, which yielded the error:
Property cannot be marked #objc because its type cannot be represented in Objective-C
Approach 2: Using RxSwift rx.observe
I turned to RxSwift and the rx.observe method thinking I could work around this problem, but the same thing happened (at runtime this time).
// In some generic bridge class between the view and the model
func bind(to object: SomeObjectType) {
object.rx
.observe(SomeType.self, "someProperty")
.flatMap { Observable.from(optional: $0) }
.bind(to: self.controlProperty)
.disposed(by: disposeBag)
}
Approach 3: Using RxSwift BehaviorRelays?
This is my first experience with RxSwift, and I know I should be using BehaviorRelay for my model, however I don't want to change all my model properties as my model object is working with other framework. I could try to implement a bridge then, to transform model properties into BehaviorRelay, but I would come across the same problem: how to listen for model changes.
In this question, there were no answer as to how to listen for property changes without refactoring all model properties to RxSwift's Variable (currently deprecated).
Approach 4: Using didSet Swift property observer?
The didSet and willSet property observers in plain Swift would allow listening for changes, however this would require to mark all the properties in the model with these observers, which I find quite inconvenient, since my model object has a lot of properties. If there is a way to add these observers at runtime, this would solve my problem.
I believe that what I am trying to achieve is quite common, having a set of views that modify a model object, however I can't find a way to properly link the model to the view, so that both auto-update when needed.
Basically, I'm looking for an answer to one of the following questions:
Is there something I overlooked, is there a better way to achieve what I want?
or How to overcome the "Property cannot be marked #objc" problem?
or How to bridge my model object to BehaviorRelay without changing my model?
or How to add didSet observers at runtime?
You said:
I believe that what I am trying to achieve is quite common, having a set of views that modify a model object, however I can't find a way to properly link the model to the view, so that both auto-update when needed.
Actually it's not at all common. One idea you don't mention is to wrap your entire model into a Behavior Relay. Then the set of views can modify your model object.
Each of your views, in turn, can observe the model in the behavior relay and update accordingly. This is the basis of, for example, the Redux pattern.
You could also use your approach #3 and use property wrappers to make the code a bit cleaner:
#propertyWrapper
struct RxPublished<Value> {
private let relay: BehaviorRelay<Value>
public init(wrappedValue: Value) {
self.relay = BehaviorRelay(value: wrappedValue)
}
var wrappedValue: Value {
get { relay.value }
set { relay.accept(newValue) }
}
var projectedValue: Observable<Value> {
relay.asObservable()
}
}
But understand that the whole reason you are having this problem is not due to Rx itself, but rather due to the fact that you are trying to mix paradigms. You are increasing the complexity of your code. Hopefully, it's just a temporary increase during a refactoring.
Old Answer
You said you want to make it "so that the views auto-update when their corresponding model property is changed, and the model update when the controls are changed (two way binding)."
IMO, that way of thinking about the problem is incorrect. Better would be to examine each output independently of all other outputs and deal with it directly. In order to explain what I mean, I will use the example of converting °F to °C and back...
This sounds like a great reason to use 2-way binding but let's see?
// the chain of observables represents a view model
celsiusTextField.rx.text // • this is the input view
.orEmpty // • these next two convert
.compactMap { Double($0) } // the view into an input model
.map { $0 * 9 / 5 + 32 } // • this is the model
.map { "\($0)" } // • this converts the model into a view
.bind(to: fahrenheitTextField) // • this is the output view
.disposed(by: disposeBag)
fahrenheitTextField.rx.text
.orEmpty
.compactMap { Double($0) }
.map { ($0 - 32) * 5 / 9 }
.map { "\($0)" }
.bind(to: celsiusTextField.rx.text)
.disposed(by: disposeBag)
The above code handles the two-way communication between the text fields without two-way binding. It does this by using two separate view models (The view model is the code between the text Observable and the text Observer as described in the comments.)
We can see a lot of duplication. We can DRY it up a bit:
extension ControlProperty where PropertyType == String? {
func viewModel(model: #escaping (Double) -> Double) -> Observable<String> {
orEmpty
.compactMap { Double($0) }
.map(model)
.map { "\($0)" }
}
}
You may prefer a different error handling strategy than what I used above. I was striving for simplicity since this is an example.
The key though is that each observable chain should be centered on a particular effect. It should combine all the causes that contribute to that effect, implement some sort of logic on the inputs, and then emit the needed output for that effect. If you do this to each output individually you will find that you don't need two-way binding at all.

self captured by a closure before all members were initialized - but I did initialize them

This is a toy example but it reduces exactly the situation I'm in:
class MyDataSource: UITableViewDiffableDataSource<String,String> {
var string : String?
init(string:String?) {
self.string = string
super.init(tableView: UITableView()) { (_, _, _) -> UITableViewCell? in
print(self.string) // error
return nil
}
}
}
I'm trying to make my table view data source self-contained, and my way of doing that (so far) is to subclass UITableViewDiffableDataSource. This works fine except when I try to give my subclass a custom initializer. The toy example shows the problem.
The way I want to populate the cell absolutely depends upon a value that can change later in the life of the data source. Therefore it cannot be hard-coded into the cell provider function. I cannot refer here simply to string, the value that was passed in the initializer; I must refer to self.string because other code is going to have the power to change this data source's string instance property later on, and I want the cell provider to use that new value when that happens.
However, I'm getting the error "self captured by a closure before all members were initialized". That seems unfair. I did initialize my string instance property before calling super.init. So it does have a value at the earliest moment when the cell provider method can possibly be called.
While I'm not entirely sure why Swift doesn't allow this (something to do with capturing self to create the closure before the actual call to super.init is made), I do at least know of a workaround for it. Capture a weak local variable instead, and after the call to super.init set that local variable to self:
class MyDataSource: UITableViewDiffableDataSource<String,String> {
var string : String?
init(string:String?) {
self.string = string
weak var selfWorkaround: MyDataSource?
super.init(tableView: UITableView()) { (_, _, _) -> UITableViewCell? in
print(selfWorkaround?.string)
return nil
}
selfWorkaround = self
}
}
The only problem with this, though, is that if the closure is executed during the call to super.init, then selfWorkaround would be nil inside the closure and you may get unexpected results. (In this case, though, I don't think it is - so you should be safe to do this.)
Edit: The reason we make the local variable weak is to prevent the self object from being leaked.
you can access self via tableView.datasource and it will sort most of the problem.
Expanding on Abhiraj Kumar's answer from Feb 16 2020, here's an example of using the TableView provided to "reach back" to get the data source you attached to the table... i.e. "self":
class MyDataSource: UITableViewDiffableDataSource<String,String> {
var string : String?
init(string:String?) {
self.string = string
super.init(tableView: UITableView()) { (tableView, _, _) -> UITableViewCell? in
// Very sketchy reach-through to get "self", forced by API design where
// super.init() requires closure as a parameter
let hack_self = tableView.dataSource! as! MyDataSource
let selfDotStr = hack_self.string
print("In closure, self.string is \(selfDotStr)")
return nil // would return a real cell here in real application
}
}
}

RxSwift, chain dependent downloads returning same Observable type

I'm downloading a list of books from an API, and for each book I want to download its cover image.
I'm stuck with what I think it's a very simple and common task in Rx. I've done a research but I cannot find the solution because all the related questions are about how to return another Observable; for example get Github repositories and then get the issues for each one, but returning Observable<Issue> not a modified Observable<Repository>, that is my case. In my case I want to modify the previous observable result (Observable<[Book]>) with the result of another request returning different observable (Observable<Data>).
For now I have the first part, download books:
func search(query: String) -> Observable<[Book]> {
return networkSession.rx
.data(request: URLRequest(url: url))
.map({ try JSONDecoder().decode([Book].self, from: $0) })
}
Book is a simple struct:
struct Book {
let title: String
let authors: [String]
let coverImageURL: URL
var coverImage: Data?
}
After that I'm able to download each image but I don't know how to assign it to each object in order to return the same Observable<[Book]> type without messing with nested observables and a zillion of compile time errors. I'm pretty sure this is a common scenario to use flatMap but it is still kind of obscure to me.
Thank you so much!
Perhaps one should split the Book struct into two structs. The first one is called BookInfo here and an array of them is downloaded with calling the function search.
struct BookInfo {
let title: String
let authors: [String]
let coverImageURL: URL
}
Composing a BookInfo instance together with the coverImage data could result in Book struct like this:
struct Book {
let bookInfo: BookInfo
let coverImage: Data
}
The RxSwift chain would look like this:
self.search(query: "<someQuery>")
.flatMap { (bookInfos: [BookInfo]) -> Observable<BookInfo> in
Observable.from(bookInfos)
}
.flatMap { (bookInfo: BookInfo) -> Observable<Book> in
//use the coverImageURL from the closure parameter and fetch the coverImage
//return an Observable<Book>
}
.observeOn(MainScheduler.instance)
.do(onNext: { (book: Book) in
//e.g. add a book in main thread to the UI once the cover image is available
})
.subscribe()
.disposed(by: disposeBag)
Instead of using type inference I added explicit type specifications, so it is more obvious what kind of elements are passed through the chain.
UPDATE
If you want to use just one Book struct, you could use this:
self.search(query: "<someQuery>")
.flatMap { (books: [Book]) -> Observable<Book> in
Observable.from(books)
}
.flatMap { (book: Book) -> Observable<Book> in
//use the book.coverImageURL from the closure parameter and fetch the coverImage
//since the closure parameter 'book' is a let constant you have to construct a new Book instance
//return an Observable<Book>
}
...
Please note that the closure parameters are let constants. This means that you cannot change the coverImage property even though it is defined as var in the structure. Instead, you must create a new instance of the Book structure, fill it with the values from the Closure parameter, and add the coverImage data. Accordingly, if you want to go this route, you could also optionally change coverImage to be a let constant.
I guess I personally would a have a slight preference for the first approach with the two structs.

Make `Collection` protocol unique by Element property

I have been trying to make this extension generic, without any success. Any ideas how to improve it? (I would like to get rid of the hardcoded WKBackForwardListItem and url part and just accept any property) But I am afraid this is not possible just yet?
extension Collection where Iterator.Element: WKBackForwardListItem {
func unique() -> [Iterator.Element] {
var seen: [Iterator.Element] = []
for element in self {
if seen.map({ $0.url }).contains(element.url) == false {
seen.append(element)
}
}
return seen
}
}
Ideally the signature will end up like this
func unique(by property: Property) -> [Iterator.Element] {
Update
Given the nature of the array, an WKWebView history. It is very important to preserve the order of the array. So every use of Set is out of the question.
Update 2
Resulting code was, I lost the Collection protocol, but that is ok for this example.
extension Array where Iterator.Element: Equatable, Iterator.Element: Hashable {
func unique() -> [Iterator.Element] {
return NSOrderedSet(array: self).array as! [Iterator.Element]
}
}
Thanks!
You don't need to create this extension of Collection. What you need is a Set. The only thing you need to make sure is that the items in your array conform to the Equatable protocol.
Sample:
let food = ["taco", "apple", "pizza", "taco"]
let uniqueFood = Set(food) // ["taco", "apple", "hot dog"]
Then all you need to do is create an Array from that Set:
let foodArray = Array()
For your use case WKBackForwardListItem does conform to Equatable so you won't need to do anything special for that.
In order to maintain the order of your data Apple has provided NSOrderedSet. This has the uniqueness that Set offers while maintaining the order Array provides.
I hope this helps.
You're looking for NSOrderedSet (and NSMutableOrderedSet). It has the uniqueness of Set with the ordering of Array! See Apple's documentation.

Cannot convert value of type 'Option' to expected argument type '#noescape (Option) throws -> Bool'

I have a class called Option. This is a Realm object so it's a subclass of Realm's own base class Object.
In a view controller I have an array property that holds a bunch of Option objects.
private var options = [Option]()
In a method down the view controller, I need to check if a certain Option object is contained within the aforementioned options array.
Previously (Swift 1.2), I have the checking logic like this.
func itemSelected(selection: Object) {
let option = selection as! Option
if !contains(options, option) {
// act accordingly
}
}
Now I'm converting the project to Swift 2 (I have updated the Realm's version to Swift 2 version as well). I updated the code to this.
func itemSelected(selection: Object) {
let option = selection as! Option
if !options.contains(option) {
// act accordingly
}
}
But now I'm getting the following compile error!
Cannot convert value of type 'Option' to expected argument type '#noescape (Option) throws -> Bool'
I can't figure out why. Any ideas?
This is because the contains function now expects a closure rather than an element on all non-equatable types. All you need to do is change it to the following
if !(options.contains{$0==option}) {
// act accordingly
}
What this does is it passes a closure in to the function, which returns true only if that closure satisfies any of its elements. $0 stands for the current element in the array that the contains function is testing against, and it returns true if that element is equal to the one that you are looking for.
While the first answer, that indicates this issue occurs because the contains method needs to operate on an Equatable type, is true, that's only half the story. The Realm Object class inherits NSObject, which conforms to Equatable (thus this should work without a closure). For more discussion on this, you can refer to this issue on the Realm GitHub page: https://github.com/realm/realm-cocoa/issues/2519. The Realm developers indicate that they believe this is a bug in Swift.
Ultimately, the suggested workaround is to re-declare the conformance to Equatable and Hashable, like so (this is copied verbatim from GitHub user bdash's comment on the previously posted issue):
public class A: Object, Equatable, Hashable {
}
public func ==(lhs: A, rhs: A) -> Bool {
return lhs.isEqual(rhs)
}
You'd replace the all instances of type A with type Option in that sample.
I've tested this solution, and it works for me in XCode 7.2.1, using Swift version 2.1.1.

Resources