Comments (8)
DifferenceKit is not really suited for NSOutlineView
because its algorithm revolves around Index Paths while NSOutlineView
has an arbitrary hierarchy where a child of Any?
can have any number of children. Adding support is possible but would not be as easy as NSTableView
/NSCollectionView
.
from differencekit.
This is my first shot at it, just for reference or in case someone wants to work on it. It doesn't work quite right yet, the changeset indices need to be adjusted according to the modifications which took place (I didn't managed to think through the index management yet).
I'll see whether I can make it work properly later.
OK, the main flaw with the stuff below is that ArraySection
can't really be used as an item, because it has no identity (the Swift bridge boxes it, but item lookup fails).
public extension NSOutlineView {
/// Applies multiple animated updates in stages using `StagedChangeset`.
///
/// - Note: There are combination of changes that crash when applied simultaneously in `performBatchUpdates`.
/// Assumes that `StagedChangeset` has a minimum staged changesets to avoid it.
/// The data of the data-source needs to be updated synchronously before `performBatchUpdates` in every stages.
///
/// - Parameters:
/// - stagedChangeset: A staged set of changes.
/// - deleteRowsAnimation: An option to animate the row deletion.
/// - insertRowsAnimation: An option to animate the row insertion.
/// - interrupt: A closure that takes an changeset as its argument and returns `true` if the animated
/// updates should be stopped and performed reloadData. Default is nil.
/// - setData: A closure that takes the collection as a parameter.
/// The collection should be set to data-source of UICollectionView.
func reload<C, M, E>(
using stagedChangeset: StagedChangeset<C>,
deleteRowsAnimation: NSTableView.AnimationOptions = .effectFade,
insertRowsAnimation: NSTableView.AnimationOptions = .slideDown,
interrupt: ((Changeset<C>) -> Bool)? = nil,
setData: (C) -> Void
)
where C: Collection, C.Element == ArraySection<M, E>,
M: Differentiable, E: Differentiable
{
if case .none = window, let sections = stagedChangeset.last?.data {
setData(sections)
return reloadData()
}
for changeset in stagedChangeset {
if let interrupt = interrupt, interrupt(changeset), let sections = stagedChangeset.last?.data {
setData(sections)
return reloadData()
}
beginUpdates()
defer { endUpdates() }
setData(changeset.data)
if !changeset.sectionDeleted.isEmpty {
removeItems(at: IndexSet(changeset.sectionDeleted),
inParent: nil, withAnimation: deleteRowsAnimation)
}
if !changeset.sectionInserted.isEmpty {
insertItems(at: IndexSet(changeset.sectionInserted),
inParent: nil, withAnimation: insertRowsAnimation)
}
if !changeset.sectionUpdated.isEmpty {
for index in changeset.sectionUpdated {
let sectionItem = child(index, ofItem: nil)
reloadItem(sectionItem)
}
}
for (source, target) in changeset.sectionMoved {
moveItem(at: source, inParent: nil, to: target, inParent: nil)
}
func groupBySection(_ pathes: [ ElementPath ]) -> [ Int : IndexSet ]
{
guard !pathes.isEmpty else { return [:] }
if pathes.count == 1, let first = pathes.first {
return [ first.section: [ first.element ] ]
}
var elementIndicesBySection = [ Int: IndexSet ]()
for path in pathes {
elementIndicesBySection[path.section, default: .init()]
.insert(path.element)
}
return elementIndicesBySection
}
if !changeset.elementDeleted.isEmpty {
for (section, elements) in groupBySection(changeset.elementDeleted) {
let sectionItem = child(section, ofItem: nil)
removeItems(at: elements, inParent: sectionItem, withAnimation: deleteRowsAnimation)
}
}
if !changeset.elementInserted.isEmpty {
for (section, elements) in groupBySection(changeset.elementInserted) {
let sectionItem = child(section, ofItem: nil)
insertItems(at: elements, inParent: sectionItem, withAnimation: insertRowsAnimation)
}
}
if !changeset.elementUpdated.isEmpty {
// To group or not to group, that is the question.
for (section, elements) in groupBySection(changeset.elementUpdated) {
let sectionItem = child(section, ofItem: nil)
for index in elements {
let elementItem = child(index, ofItem: sectionItem)
reloadItem(elementItem)
}
}
}
for (source, target) in changeset.elementMoved {
let sourceSectionItem = child(source.section, ofItem: nil)
let targetSectionItem = child(target.section, ofItem: nil)
moveItem(at: source.element, inParent: sourceSectionItem,
to: target.element, inParent: targetSectionItem)
}
}
}
}
Sample datasource using ArraySection:
final class ArraySectionOutlineViewDataSource
<Model: Differentiable, Element: Differentiable>
: NSObject, NSOutlineViewDataSource
{
typealias Section = ArraySection<Model, Element>
var sections : [ Section ]
init(_ sections: [ Section ]) { self.sections = sections }
func outlineView(_ ov: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
(item as? Section)?.elements.count ?? sections.count
}
func outlineView(_ ov: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
(item as? Section)?.elements[index] ?? sections[index]
}
func outlineView(_ ov: NSOutlineView, isItemExpandable item: Any) -> Bool {
return item is Section
}
}
P.S.: The NSTableView.reload
methods probably needs an assert(!self is NSOutlineView)
, so that people don't accidentally try to use them w/ an outline view (which is a NSTableView
subclass).
from differencekit.
Hi @davedelong
I'm not familiar with the development with macOS.
Feel free to pull-requests!
from differencekit.
Hm, it would still be useful for 1-level outline views which are quite common in sourcelist's and such (e.g. the one in Finder). Also IndexPath can have arbitrary depth.
I might look into this, or is there anything extra which is problematic wrt NSOutlineView?
from differencekit.
Played with that for quite a while, but I think I'm giving up for now, it is so messy :-)
In case anyone comes along and wants to take a look, I put it over here: AnyOutlineView.
The base project "Any" enables the OutlineView (hence the name) - it is very inefficient and slow, but should get the job done at SwiftUI-scale (i.e. a 100 items in a set). It builds a shadow tree within the subclass and adds a datasource method to identify values. I intentionally made this very generic (i.e. did not use DifferenceKit).
The AnyOutlineView should actually work. Very little testing though. My thinking was that this would be the first required step to apply the (value typed) staged changes.
Then there is a DifferenceKit
branch which adds an AnyOutlineView datasource which works on top of ArraySection
. Plus a reload
function similar to the one above.
That almost works for the source/target test set (sample included). But only almost :-) I think it is running into the "eventually consistent" index differences between UIKit and AppKit. E.g. inserting an index conflicts with moving an index (which apparently needs the updated index).
So I started the IndexShifter branch. But that didn't work out in 5 minutes either and I lost patience :-)
Maybe it is still useful to someone. Maybe the work required to get it done is very little. Or very much. No idea :-)
from differencekit.
I am in the same boat, where I "almost" got this to work. I end up with duplicated sections at some point. Did you ever figure out a reliable solution? :)
from differencekit.
No, unfortunately not. Maybe the new Apple diffable datasources can deal with the problem? Didn't really try them yet.
from differencekit.
No, unfortunately not. Maybe the new Apple diffable datasources can deal with the problem? Didn't really try them yet.
Unfortunately, they did not add anything for NSOutlineView either. Not sure if (and how) the collection view datasource could be reused.
from differencekit.
Related Issues (20)
- CollectionDifference API implementation HOT 1
- Compute changeset in the background
- To do the insert and delete operations at once
- Delete .swift-version
- Crash when I reload collectionView HOT 1
- Help needed with crash - NSInternalInconsistencyException HOT 1
- Buggy default reload method HOT 1
- How can I reload a table that is behind on the navigation stack? HOT 1
- UICollectionView reload animation
- Duplicate items with some edge cases in AppKitExtension HOT 13
- Why DifferenceKit is so much faster than Apple's Foundation diffing? HOT 2
- Animation option for row reloading is not a valid option in AppKitExtension
- Does it work seamlessly when using an UICollectionView with SwiftUI cells? HOT 2
- tvOS UICollectionView.reload crash
- The algorithm cannot get the best diff results? HOT 1
- `Differentiable` requres `Equatable` AND `Hashable`, but docs say otherwise
- Using only the diffing algorithm
- Invalid batch updates detected
- How to apply ChageSet to an array?
- Use UITableView reconfigureRows and UICollectionView reconfigureItems for ChangeSet.elementUpdated
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from differencekit.