GithubHelp home page GithubHelp logo

osteslag / changeset Goto Github PK

View Code? Open in Web Editor NEW
802.0 17.0 46.0 550 KB

Minimal edits from one collection to another

License: MIT License

Ruby 2.51% Swift 97.49%
ios uicollectionview uitableview minimal-edits changeset macos changes change diff delta

changeset's People

Contributors

adamhrz avatar bwhiteley avatar hiltonc avatar iamkevb avatar klaaspieter avatar messi avatar nachosoto avatar ole avatar osteslag avatar perlfly avatar raven avatar sibljon avatar siemensikkema avatar spacyricochet avatar tonyarnold avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

changeset's Issues

Suggestion in consideration of UIKit behaviour

I have some thoughts and suggestions, but I'm not sure about if we need them resolved. Why I want to raise the issue is because maybe someone out there might be interested. They are mostly concerning substitution/reload and how UIKit and its documentation seems to treat it inconsistently.

Expose edit's origin (source) index, at least for substitution.

My translation from changeset's edit operations to UITableView's row operations is as follow:

Edit Row
Insert Insert
Delete Delete
Move Move
Substitution Reload

When executing row operations between beginUpdates and endUpdates, like delete, reload index is expected to be the ones prior to any updates. As stated in its API documentation:

The indexes that UITableView passes to the method are specified in the state of the table view prior to any updates.

Changeset provides the index after update instead. Making it unavailable to be mixed with other operations within beginUpdates and endUpdates.

Makes reducing edits to move optional.

As of 1.0.x, in order to use it with UITableView's row operation, I have to do something like this.

        var deletedIndexPaths = [NSIndexPath]()
        var insertedIndexPaths = [NSIndexPath]()
        var movedIndexPaths = [(from: NSIndexPath, to: NSIndexPath)]()
        var reloadIndexPaths = [NSIndexPath]()

        for edit in changeset.edits {
            switch edit.operation {
            case .Insertion:
                let insertedIndexPath = NSIndexPath(forRow: edit.destination, inSection: 0)
                insertedIndexPaths.append(insertedIndexPath)
            case .Deletion:
                let deletedIndexPath = NSIndexPath(forRow: edit.destination, inSection: 0)
                deletedIndexPaths.append(deletedIndexPath)
            case .Substitution:
                let reloadIndexPath = NSIndexPath(forRow: edit.destination, inSection: 0)
                reloadIndexPaths.append(reloadIndexPath)
            case .Move(origin: let origin):
                let fromIndexPath = NSIndexPath(forRow: origin, inSection: 0)
                let toIndexPath = NSIndexPath(forRow: edit.destination, inSection: 0)
                movedIndexPaths.append((from: fromIndexPath, to: toIndexPath))
            }
        }

        self.tableView.beginUpdates()

        if deletedIndexPaths.count > 0 {
            self.tableView.deleteRowsAtIndexPaths(deletedIndexPaths, withRowAnimation: .Automatic)
        }

        if insertedIndexPaths.count > 0 {
            self.tableView.insertRowsAtIndexPaths(insertedIndexPaths, withRowAnimation: .Automatic)
        }

        for (from, to) in movedIndexPaths {
            self.tableView.moveRowAtIndexPath(from, toIndexPath: to)
        }

        self.tableView.endUpdates()

        if reloadIndexPaths.count > 0 {
            self.tableView.reloadRowsAtIndexPaths(reloadIndexPaths, withRowAnimation: .Automatic)
        }

Reload is outside the beginUpdates and endUpdates for reason stated previous. But if I exposes the origin index for substitution edit operation, theoretically, I probably can wrap all operations in beginUpdates and endUpdates.

But this is not the case. It seems a little counter intuitive but the exception thrown by UITableView is as follows:

2016-01-23 22:58:35.504 ChangesetTest[3247:111068] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to perform an insert and a move to the same index path (<NSIndexPath: 0xc000000003800016> {length = 2, path = 0 - 28})'

It stated that we are trying to attempt an insert and a move row operation but those code have not change. Funny thing is that if I commented out the reload row operation code (which is possible here as their value will just remain outdated), it will no longer complain.

I believe this is more of a wording bug, UIKit probably meant insert and reload of the same index. But this I can't be sure.

The funny things is that the API documentation for move only mention that it can work with insert and delete, but avoid mentioning reload.

As for UICollectionView, its API documentation for batch update only mention that it can work with insert, delete and move but did not mention about reload. Which will make the current solution for UITableView seems correct.

If I remove the reduce edit step. I can make it work nicely with reload in table view.

But the thing about reload is that it is the least obvious row operation visually. And there are several other options that are reasonable as well. For example, we can get visible rows and update the cell manually (no animation), or just not batching it like above (not an issue as well).

Trees

Hey, I just read the readme. I like this a lot. Do you think this could be applied to trees as well? Imagine I have a tree of lightweight structs. Can I compare two trees and find the minimum changes required going from one to the other?

Xcode project warnings

Xcode had two warnings when opening this project;

  • Set to recommended project settings.
  • Testing would give a warning that XCTest is not App Extension Safe, even though the Tests target was marked as only accepting App Extension Safe API's.

Ineffective RAM usage

Approximately three hundreds of items Contact was loaded to a Collection View but Changeset created and destroyed ~97'000 objects to calculate changes.
What is the complexity of this algorithm?

2017-01-26_12-32-17
2017-01-26_12-33-01

Changeset produces bad edit steps, crashes UICollectionView.

I have a couple of examples of edit steps that are incorrect.

'64927513' -> '917546832':
delete 4 at index 1
replace with 1 at index 1
replace with 4 at index 4
move 6 from index 0 to 5
insert 8 at index 6
insert 2 at index 8
UICollectionView error: attempt to delete and reload the same index path (path = 0 - 1)

'8C9A2574361B' -> '897A34B215C6':
replace with 3 at index 4
delete 5 at index 5
move 7 from index 6 to 2
replace with B at index 6
replace with 2 at index 7
replace with 5 at index 9
move C from index 1 to 10
insert 6 at index 11
UICollectionView error: attempt to perform a delete and a move from the same index path (path = 0 - 6)

How to use Changeset in protocols?

I can't figure out how to make the compiler happy when trying to express something like this:

public protocol ChangesetApplying: class {
    associatedtype T
    func apply(changeset: Changeset<T>)
}

The above will complain that Type 'Self.T' does not conform to protocol 'Collection'.

Changing it to associatedtype T: Collection yields a different error: Type 'Self.T.Iterator.Element' does not conform to protocol 'Equatable'.

Changing it to associatedtype T: Collection where T.Iterator.Element: Equatable, T.IndexDistance == Int yields the error 'where' clause cannot be attached to an associated type declaration, which appears to be the subject of SE-0142.

Any ideas?

IndexDistance is deprecated

Xcode has been warning me that IndexDistance has been deprecated, the reason being that IndexDistance is now always defined as an Int.

.../Pods/Changeset/Sources/Changeset.swift:13:79: 'IndexDistance' is deprecated: all index distances are now of type Int

.../Pods/Changeset/Sources/Changeset.Edit.swift:16:31: 'IndexDistance' is deprecated: all index distances are now of type Int

Fix insertion indices

Version 1 of Changeset was implemented from my recollection of how UITableView interprets indices when batch inserting and deleting rows and sections in a beginUpdates/endUpdates block. I believed everything was expressed in source indices, before any changes; both insertions and deletions. It turns out, however, only deletions are. Insertions are deferred, thus insertion indices should be relative to any changes caused by deletions.

This is what Apple’s Table View Programming Guide for iOS says on the subject:

[UITableView] defers any insertions of rows or sections until after it has handled the deletions of rows or sections. The table view behaves the same way with reloading methods called inside an update block—the reload takes place with respect to the indexes of rows and sections before the animation block is executed. This behavior happens regardless of the ordering of the insertion, deletion, and reloading method calls.

Deletion and reloading operations within an animation block specify which rows and sections in the original table should be removed or reloaded; insertions specify which rows and sections should be added to the resulting table. The index paths used to identify sections and rows follow this model. Inserting or removing an item in a mutable array, on the other hand, may affect the array index used for the successive insertion or removal operation; for example, if you insert an item at a certain index, the indexes of all subsequent items in the array are incremented.

TODO

  • Fix Changeset.editDistance
  • Update tests
  • Update README

Sample with custom models?

This is a great lib and I was exited to implement it but I'm kind of lost in regards of using this with my actual models, normally you will not have just a String to be presented 🤐 .. Any chance for a sample about how to use it with custom models?

Should deletes be inserted in descending order?

I'm using Changeset v2.0, and I've run into an issue where I'm applying a batch delete which produces something akin to:

[0] { operation = .delete, value = …, destination = 0 }
[1] { operation = .delete, value = …, destination = 1 }
[2] { operation = .delete, value = …, destination = 2 }
[3] { operation = .delete, value = …, destination = 3 }

If I process this in order by applying the deletes to a table, this will (pretty obviously) crash when it tries to remove destination 3 (which in the target collection, no longer exists).

Is this something that Changeset should be handling for me? I don't recall this being an issue with v1.

Carthage support?

Are there any plans for supporting Carthage? I don't really want to add Pods to my project, and the Swift Package Manager option is currently failing with:

  ~ swift package generate-xcodeproj
Fetching https://github.com/osteslag/Changeset.git
Completed resolution in 4.39s
Cloning https://github.com/osteslag/Changeset.git
Resolving https://github.com/osteslag/Changeset.git at 3.1.1
warning: PackageDescription API v3 is deprecated and will be removed in the future; used by package(s): Changeset
'Changeset' /Users/pcferreira/Projects/OnboardingReactiveSwift/.build/checkouts/Changeset.git--4643115965670364357: error: package has unsupported layout; found loose source files: /Users/pcferreira/Projects/OnboardingReactiveSwift/.build/checkouts/Changeset.git--4643115965670364357/Tests/ChangesetTests.swift
'OnboardingReactiveSwift' /Users/pcferreira/Projects/OnboardingReactiveSwift: error: product dependency 'Changeset' not found

Indices are (wrongly?) assumed to be integers and zero-based

I have a comment regarding terminology.

The indices the library computes for the Edit steps are currently always integers and always zero-based (i.e. the first index is 0). This works great for the intended main use case (table and collection view updates) because the index paths of a table or collection view are also zero-based and composed of integers.

However, it doesn't reflect the reality of Swift's collection types. For example, ArraySlice uses integer indices, but they aren't zero-based. If you compute the changeset for two ArraySlice instances, the result is arguably wrong or at least confusing:

let source = [2,3,4,5].dropFirst() // ArraySlice<Int>
let target = [3,4,5,6].dropFirst() // ArraySlice<Int>
let edits = Changeset.edits(from: source, to: target)
print(edits)
// Prints "[delete 3 at index 0, insert 6 at index 2]"

If you tried to apply this changeset literally to the source collection (i.e. delete the element at index 0 from source), youʼd get a crash because source doesnʼt have an "index" 0.

In a way, a similar issue is when you compute changesets for strings (as most of the unit tests do) because string indices are not integers, so saying something like "insert "a" at index 5" doesn't strictly make sense in Swift.

I find this a little confusing and misleading. Changeset appears to be generic for any collection, and yet it doesn't follow Swift's conventions where the term "index" has a very specific meaning.

When I first noticed this "problem" I wanted to suggest replacing the integer indices in Edit and EditOperation with the collection's index type (i.e. something like T.Index). On second thought, this doesn't make sense because a changeset would have to compute "virtual" indices for a collection that doesn't really exist, and since collections are free to invalidate any existing index upon mutation, I don't think you could compute the indices in a generic way. Moreover, using real collection indices would make supporting table/collection view updates harder, not easier.

After writing all this, I realize that the way this works is actually the best solution for the given problem. Having written this, I'm going to submit it too, even though I don't have a specific suggestion. Feel free to close this issue, and maybe it will help others understand this better when they find it in search.

My only suggestion would be to make it clear that Changeset and Edit aren't concerned with indices, but with offsets. I think if the debugDescription read "delete x at offset 0" etc. I would have been less confused. This is exemplified by the use of enumerated() to compute the column value in edits(from:to:), since enumerated() computes offsets from 0 and not indices (see http://khanlou.com/2017/03/you-probably-don't-want-enumerated/ for more info on enumerated() and how it's often misinterpreted).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.