Giter VIP home page Giter VIP logo

Comments (8)

OskarGroth avatar OskarGroth commented on May 27, 2024 1

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.

helje5 avatar helje5 commented on May 27, 2024 1

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.

ra1028 avatar ra1028 commented on May 27, 2024

Hi @davedelong
I'm not familiar with the development with macOS.
Feel free to pull-requests!

from differencekit.

helje5 avatar helje5 commented on May 27, 2024

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.

helje5 avatar helje5 commented on May 27, 2024

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.

paxos avatar paxos commented on May 27, 2024

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.

helje5 avatar helje5 commented on May 27, 2024

No, unfortunately not. Maybe the new Apple diffable datasources can deal with the problem? Didn't really try them yet.

from differencekit.

paxos avatar paxos commented on May 27, 2024

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)

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.