πŸ’» A fast and flexible O(n) difference algorithm framework for Swift collection.

Overview

A fast and flexible O(n) difference algorithm framework for Swift collection.
The algorithm is optimized based on the Paul Heckel's algorithm.

Swift5 Release CocoaPods Carthage Swift Package Manager
CI Status Platform Lincense


Made with ❀️ by Ryo Aoyama and Contributors


Features

πŸ’‘ Fastest O(n) diffing algorithm optimized for Swift collection

πŸ’‘ Calculate diffs for batch updates of list UI in UIKit, AppKit and Texture

πŸ’‘ Supports both linear and sectioned collection even if contains duplicates

πŸ’‘ Supports all kind of diffs for animated UI batch updates


Algorithm

This is a diffing algorithm developed for Carbon, works stand alone.
The algorithm optimized based on the Paul Heckel's algorithm.
See also his paper "A technique for isolating differences between files" released in 1978.
It allows all kind of diffs to be calculated in linear time O(n).
RxDataSources and IGListKit are also implemented based on his algorithm.

However, in performBatchUpdates of UITableView, UICollectionView, etc, there are combinations of diffs that cause crash when applied simultaneously.
To solve this problem, DifferenceKit takes an approach of split the set of diffs at the minimal stages that can be perform batch updates with no crashes.

Implementation is here.


Getting Started

Basic Usage

The type of the element that to take diffs must be conform to the Differentiable protocol.
The differenceIdentifier's type is generic associated type:

struct User: Differentiable {
    let id: Int
    let name: String

    var differenceIdentifier: Int {
        return id
    }

    func isContentEqual(to source: User) -> Bool {
        return name == source.name
    }
}

In the case of definition above, id uniquely identifies the element and get to know the user updated by comparing equality of name of the elements in source and target.

There are default implementations of Differentiable for the types that conforming to Equatable or Hashable:

// If `Self` conforming to `Hashable`.
var differenceIdentifier: Self {
    return self
}

// If `Self` conforming to `Equatable`.
func isContentEqual(to source: Self) -> Bool {
    return self == source
}

Therefore, you can simply:

extension String: Differentiable {}

Calculate the diffs by creating StagedChangeset from two collections of elements conforming to Differentiable:

let source = [
    User(id: 0, name: "Vincent"),
    User(id: 1, name: "Jules")
]
let target = [
    User(id: 1, name: "Jules"),
    User(id: 0, name: "Vincent"),
    User(id: 2, name: "Butch")
]

let changeset = StagedChangeset(source: source, target: target)

If you want to include multiple types conforming to Differentiable in the collection, use AnyDifferentiable:

let source = [
    AnyDifferentiable("A"),
    AnyDifferentiable(User(id: 0, name: "Vincent"))
]

In the case of sectioned collection, the section itself must have a unique identifier and be able to compare whether there is an update.
So each section must conforming to DifferentiableSection protocol, but in most cases you can use ArraySection that general type conforming to it.
ArraySection requires a model conforming to Differentiable for diffing from other sections:

enum Model: Differentiable {
    case a, b, c
}

let source: [ArraySection<Model, String>] = [
    ArraySection(model: .a, elements: ["A", "B"]),
    ArraySection(model: .b, elements: ["C"])
]
let target: [ArraySection<Model, String>] = [
    ArraySection(model: .c, elements: ["D", "E"]),
    ArraySection(model: .a, elements: ["A"]),
    ArraySection(model: .b, elements: ["B", "C"])
]

let changeset = StagedChangeset(source: source, target: target)

You can perform diffing batch updates of UITableView and UICollectionView using the created StagedChangeset.

⚠️ Don't forget to synchronously update the data referenced by the data-source, with the data passed in the setData closure. The diffs are applied in stages, and failing to do so is bound to create a crash:

tableView.reload(using: changeset, with: .fade) { data in
    dataSource.data = data
}

Batch updates using too large amount of diffs may adversely affect to performance.
Returning true with interrupt closure then falls back to reloadData:

collectionView.reload(using: changeset, interrupt: { $0.changeCount > 100 }) { data in
    dataSource.data = data
}

[See More Usage]


Comparison with Other Frameworks

Made a fair comparison as much as possible in performance and features with other popular and awesome frameworks.
This does NOT determine superiority or inferiority of the frameworks.
I know that each framework has different benefits.
The frameworks and its version that compared is below.

Performance Comparison

Benchmark project is here.
Performance was mesured by code compiled using Xcode11.1 and Swift 5.1 with -O optimization and run on iPhone11 Pro simulator.
Use Foundation.UUID as an element of collections.

- From 5,000 elements to 1,000 deleted, 1,000 inserted and 200 shuffled

Time(sec)
DifferenceKit 0.0019
RxDataSources 0.0074
IGListKit 0.0346
FlexibleDiff 0.0161
DeepDiff 0.0373
Differ 1.0581
Dwifft 0.4732
Swift.CollectionDifference 0.0620

- From 100,000 elements to 10,000 deleted, 10,000 inserted and 2,000 shuffled

Time(sec)
DifferenceKit 0.0348
RxDataSources 0.1024
IGListKit 0.7002
FlexibleDiff 0.2189
DeepDiff 0.5537
Differ 153.8007
Dwifft 187.1341
Swift.CollectionDifference 5.0281

Features Comparison

- Algorithm

Base algorithm Order
DifferenceKit Heckel O(N)
RxDataSources Heckel O(N)
FlexibleDiff Heckel O(N)
IGListKit Heckel O(N)
DeepDiff Heckel O(N)
Differ Myers O(ND)
Dwifft Myers O(ND)
Swift.CollectionDifference Myers O(ND)

* Heckel algorithm
* Myers algorithm

- Supported Collection

Linear Sectioned Duplicate element/section
DifferenceKit βœ… βœ… βœ…
RxDataSources ❌ βœ… ❌
FlexibleDiff βœ… βœ… βœ…
IGListKit βœ… ❌ βœ…
DeepDiff βœ… ❌ βœ…
Differ βœ… βœ… βœ…
Dwifft βœ… βœ… βœ…
Swift.CollectionDifference βœ… ❌ βœ…

* Linear means 1-dimensional collection
* Sectioned means 2-dimensional collection

- Supported Element Diff

Delete Insert Move Reload Move across sections
DifferenceKit βœ… βœ… βœ… βœ… βœ…
RxDataSources βœ… βœ… βœ… βœ… βœ…
FlexibleDiff βœ… βœ… βœ… βœ… ❌
IGListKit βœ… βœ… βœ… βœ… ❌
DeepDiff βœ… βœ… βœ… βœ… ❌
Differ βœ… βœ… βœ… ❌ ❌
Dwifft βœ… βœ… ❌ ❌ ❌
Swift.CollectionDifference βœ… βœ… βœ… ❌ ❌

- Supported Section Diff

Delete Insert Move Reload
DifferenceKit βœ… βœ… βœ… βœ…
RxDataSources βœ… βœ… βœ… ❌
FlexibleDiff βœ… βœ… βœ… βœ…
IGListKit ❌ ❌ ❌ ❌
DeepDiff ❌ ❌ ❌ ❌
Differ βœ… βœ… βœ… ❌
Dwifft βœ… βœ… ❌ ❌
Swift.CollectionDifference ❌ ❌ ❌ ❌

Requirements

  • Swift 4.2+
  • iOS 9.0+
  • tvOS 9.0+
  • OS X 10.9+
  • watchOS 2.0+ (only algorithm)

Installation

CocoaPods

To use only algorithm without extensions for UI, add the following to your Podfile:

pod 'DifferenceKit/Core'

iOS / tvOS

To use DifferenceKit with UIKit extension, add the following to your Podfile:

pod 'DifferenceKit'

or

pod 'DifferenceKit/UIKitExtension'

macOS

To use DifferenceKit with AppKit extension, add the following to your Podfile:

pod 'DifferenceKit/AppKitExtension'

watchOS

There is no UI extension for watchOS.
To use only algorithm without extensions for UI, add the following to your Podfile:

pod 'DifferenceKit/Core'

Carthage

Add the following to your Cartfile:

github "ra1028/DifferenceKit"

Swift Package Manager for Apple platforms

Select Xcode menu File > Swift Packages > Add Package Dependency and enter repository URL with GUI.

Repository: https://github.com/ra1028/DifferenceKit

Swift Package Manager

Add the following to the dependencies of your Package.swift:

.package(url: "https://github.com/ra1028/DifferenceKit.git", from: "version")

Contribution

Pull requests, bug reports and feature requests are welcome πŸš€
Please see the CONTRIBUTING file for learn how to contribute to DifferenceKit.


Credit

Bibliography

DifferenceKit was developed with reference to the following excellent materials and framework.

OSS using DifferenceKit

The list of the awesome OSS which uses this library. They also help to understanding how to use DifferenceKit.

Other diffing libraries

I respect and ️ ❀️ all libraries involved in diffing.


License

DifferenceKit is released under the Apache 2.0 License.

Comments
  • Diff calculation sometimes misses `insert` operation

    Diff calculation sometimes misses `insert` operation

    Sometimes I have a crash when one item is deleted and another one is inserted in the same section. Their diff identifiers are not equal. It looks that issue occurs when a table view is updated the second time or later (not the first).

    The data structure looks in the following way. before = [s1 :[1, 2], s2: [3]] current = [s1 :[1, 2], s2: [4]]

    So library calculates only delete of 3 in the section s2, but not insert of 4.

    I don't have a good sample yet. But I'll do as soon as I catch it during debug.

    opened by larryonoff 17
  • Duplicate items with some edge cases in AppKitExtension

    Duplicate items with some edge cases in AppKitExtension

    In some edge cases, the AppKit extension (and possibly UIKit extension) will produce duplicate items:

    https://user-images.githubusercontent.com/121539/153323882-db1cb3dc-8d8f-4dce-8ea3-4105b3a2e4fc.mov

    Example:

    StagedChangeset(source: ["1","3"], target: ["3","2","1"])
    

    This will produce the following changeset:

    β–Ώ [
        Changeset(
            data: [
                3,
                2,
                1
            ],
            elementInserted: [
                [element: 1, section: 0]
            ],
            elementMoved: [
                (source: [element: 1, section: 0], target: [element: 0, section: 0])
            ]
        )
    ]
    

    The current implementation, as found here: https://github.com/ra1028/DifferenceKit/blob/62745d7780deef4a023a792a1f8f763ec7bf9705/Sources/Extensions/AppKitExtension.swift#L50-L91

    will execute the following actions:

    removeRows [[element: 1, section: 0]]
    InsertRows [[element: 1, section: 0]]
    Move [element: 1, section: 0] [element: 0, section: 0]
    viewFor:row 0 πŸ˜‚
    

    (The code that produced those logs can be found here: https://github.com/paxos/DifferenceKit/pull/1)

    The problem: AppKit will ask the datasource for the element at position 0, while it should ask for the element at position 1. It turns out that, if you add a new element and then move it (before calling endUpdates()), AppKit will request the element from the new position.

    Possible solution

    A possible solution could be to change the order of things, for example:

    view.beginUpdates()
    handleDeletes…
    handleInserts…
    view.endUpdates() <-- this will cause AppKit to reach out to the datasource with the correct index
    view.beginUpdates()
    handleMoves…
    view.endUpdates
    

    https://user-images.githubusercontent.com/121539/153324303-c2032ce6-87af-466b-a628-a25720cd6bfe.mov

    Checklist

    opened by paxos 13
  • Implement changes for Swift 4.2 and 5.0 with Xcode 10.2

    Implement changes for Swift 4.2 and 5.0 with Xcode 10.2

    This resolves #54. I also added the Swift 5 changes that are needed as well; those are gated by #if swift(5.0). Other changes should be backwards compatible with 4.2.x versions of Swift with Xcode 10.

    opened by alanzeino 10
  • -[__NSDictionaryM setObject:forKey:]: key cannot be nil'

    -[__NSDictionaryM setObject:forKey:]: key cannot be nil'

    I am integrating differencekit with asyncdisplaykit's collectionNode in my application. I followed the instructions as mentioned in the doc and examples. But getting weird error I couldn't figure out where exactly it is happening. Normal reloadData() works perfectly fine. But using

    //self is a ASCollectionNode
    self.view.reload(using: changeset) { (data) in
                        self.products = newValue
                    }
    

    Making the application to crash.

    *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSDictionaryM setObject:forKey:]: key cannot be nil'
    *** First throw call stack:
    (
    	0   CoreFoundation                      0x0000000102e9b1e6 __exceptionPreprocess + 294
    	1   libobjc.A.dylib                     0x0000000101feb031 objc_exception_throw + 48
    	2   CoreFoundation                      0x0000000102edb0bc _CFThrowFormattedException + 194
    	3   CoreFoundation                      0x0000000102dae72a -[__NSDictionaryM setObject:forKey:] + 1002
    	4   UIKit                               0x00000001044faf07 -[UICollectionView _setVisibleView:forLayoutAttributes:] + 171
    	5   UIKit                               0x0000000104513dcb __71-[UICollectionView _updateWithItems:tentativelyForReordering:animator:]_block_invoke.1997 + 744
    	6   UIKit                               0x0000000103aef537 +[UIView(UIViewAnimationWithBlocks) _setupAnimationWithDuration:delay:view:options:factory:animations:start:animationStateGenerator:completion:] + 560
    	7   UIKit                               0x0000000103aefa0f +[UIView(UIViewAnimationWithBlocks) animateWithDuration:delay:options:animations:completion:] + 99
    	8   UIKit                               0x00000001045130f5 -[UICollectionView _updateWithItems:tentativelyForReordering:animator:] + 6349
    	9   UIKit                               0x000000010450ccc3 -[UICollectionView _endItemAnimationsWithInvalidationContext:tentativelyForReordering:animator:] + 17420
    	10  UIKit                               0x000000010451500e -[UICollectionView _endUpdatesWithInvalidationContext:tentativelyForReordering:animator:] + 71
    	11  UIKit                               0x0000000104515357 -[UICollectionView _performBatchUpdates:completion:invalidationContext:tentativelyForReordering:animator:] + 439
    	12  UIKit                               0x000000010451517d -[UICollectionView _performBatchUpdates:completion:invalidationContext:tentativelyForReordering:] + 91
    	13  UIKit                               0x00000001045150ff -[UICollectionView _performBatchUpdates:completion:invalidationContext:] + 74
    	14  UIKit                               0x0000000104515054 -[UICollectionView performBatchUpdates:completion:] + 53
    	15  AsyncDisplayKit                     0x0000000100cd3e3b -[ASCollectionView _superPerformBatchUpdates:completion:] + 571
    	16  AsyncDisplayKit                     0x0000000100ce9552 __64-[ASCollectionView rangeController:updateWithChangeSet:updates:]_block_invoke + 882
    	17  AsyncDisplayKit                     0x0000000100ce91ab _ZL30ASPerformBlockWithoutAnimationbU13block_pointerFvvE + 123
    	18  AsyncDisplayKit                     0x0000000100ce8e83 -[ASCollectionView rangeController:updateWithChangeSet:updates:] + 3859
    	19  AsyncDisplayKit                     0x0000000100df694d -[ASRangeController dataController:updateWithChangeSet:updates:] + 717
    	20  AsyncDisplayKit                     0x0000000100d02bf6 __40-[ASDataController updateWithChangeSet:]_block_invoke_2.224 + 230
    	21  AsyncDisplayKit                     0x0000000100dcb0b4 __30-[ASMainSerialQueue runBlocks]_block_invoke + 308
    	22  libdispatch.dylib                   0x00000001075c27ab _dispatch_call_block_and_release + 12
    	23  libdispatch.dylib                   0x00000001075c37ec _dispatch_client_callout + 8
    	24  libdispatch.dylib                   0x00000001075ce8cf _dispatch_main_queue_callback_4CF + 628
    	25  CoreFoundation                      0x0000000102e5dc99 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
    	26  CoreFoundation                      0x0000000102e21ea6 __CFRunLoopRun + 2342
    	27  CoreFoundation                      0x0000000102e2130b CFRunLoopRunSpecific + 635
    	28  GraphicsServices                    0x000000010ae53a73 GSEventRunModal + 62
    	29  UIKit                               0x0000000103a2d057 UIApplicationMain + 159
    	30  Sodimac                             0x0000000100705027 main + 55
    	31  libdyld.dylib                       0x0000000107640955 start + 1
    	32  ???                                 0x0000000000000001 0x0 + 1
    )
    libc++abi.dylib: terminating with uncaught exception of type NSException
    

    I don't know what I am missing. Any help would be much appreciated. Thanks in advance.

    opened by annamalairaj 10
  • Unnecessary Moves

    Unnecessary Moves

    Checklist

    Example Test Scenario

        func testMovedIssue() {
            let section = 1
            
            let source1 = [1, 2, 3]
            let target1 = [3, 2, 1]
    
            // Test1: This test passes, but it should fail.
            XCTAssertExactDifferences(
                source: source1,
                target: target1,
                section: section,
                expected: [
                    Changeset(
                        data: target1,
                        elementMoved: [
                            (source: ElementPath(element: 2, section: section), target: ElementPath(element: 0, section: section)),
                            (source: ElementPath(element: 1, section: section), target: ElementPath(element: 1, section: section)),
                        ]
                    )
                ]
            )
            
            // Test2: This test fails, but it should pass.
            XCTAssertExactDifferences(
                source: source1,
                target: target1,
                section: section,
                expected: [
                    Changeset(
                        data: target1,
                        elementMoved: [
                            (source: ElementPath(element: 2, section: section), target: ElementPath(element: 0, section: section)),
                        ]
                    )
                ]
            )
        }
    

    Expected Behavior

    Test1 should fail, Test2 should pass.

    Moves contains (2 -> 0), (1 -> 1).

    Current Behavior

    Test1 passes, Test2 fails.

    Moves should contain (2 -> 0).

    Steps to Reproduce

    Add the test I wrote above and run the unit tests.

    Detailed Description (Include Screenshots)

    Moving the item from the same index to the same index should always be unnecessary. In general optimizing to make moves correct and minimal is preferred.

    Environments

    • Library version: Master

    • Swift version: 4.2

    • iOS version: 12

    • Xcode version: 10.1

    • Devices/Simulators: DifferenceKit, "My Mac", CMD+U

    • CocoaPods/Carthage version: N/A

    opened by kylerobson 8
  • Fix pod compilation in Xcode 10.2

    Fix pod compilation in Xcode 10.2

    I created a blank swift 4.2 project with Xcode 10.2 & cocoapods 1.6.0 and I specified the following Podfile:

    platform :ios, '11.0'
    
    target 'Test' do
      use_frameworks!
    
      pod 'DifferenceKit', '~> 1.0'
    
    end
    
    

    The project does not compile.

    The DifferenceKit project compiles correctly on Xcode 10.2. So it seems to be only related to cocoapods.

    opened by gaetanzanella 8
  • `NSOutlineView` support

    `NSOutlineView` support

    While NSOutlineView is a subclass of NSTableView, the existing NSTableView extension is insufficient if you're using an NSOutlineView, because it has its own insertion/deletion/reload API.

    opened by davedelong 8
  • Multiple sections with different types

    Multiple sections with different types

    I want my collection handle multiple sections with different Items.

    Section1: Int Section2: String

    how is it possible? you have showcased one section with multiple items with AnyDifferentiable, but what about multiple sections with multiple types.

    opened by haashem 8
  • Compare with Changeset?

    Compare with Changeset?

    Thanks for releasing DifferenceKit, it looks really cool.

    I’m the author of Changeset, another fairly popular lightweight diffing framework. When you have the time, maybe I can persuade you to include it in your comparison?

    Thanks in advance.

    opened by osteslag 7
  • Fix batch moving of rows in NSTableView

    Fix batch moving of rows in NSTableView

    Fixes #139.

    A while ago I was working on a simple one-level animatable NSOutlineView (similar to the Finder's sidebar) powered by DifferenceKit, and I remember I stumbled upon issues trying to get the row move animation to work reliably for arbitrary move/delete/insert operations.

    After lots of experimentation and debugging I finally found and fixed the problem which was affecting NSTableView in the same way so I decided now to take the time and share this story with you, mainly because at the time I was working on this, I didn't know there was an AppKit extension in the project here which suffers from the same problems I had.

    As it turns out, NSTableView/NSOutlineView's beginUpdates() and endUpdates() methods do not really do batch updates. It rather seems like they just offer animation optimizations, as described in the method's discussion part of the Apple docs:

    The main reason for doing a batch update of changes to a table view is to avoid having the table animate unnecessarily.

    What that means is, in contrast to NSCollectionViews performBatchUpdates(), it inserts, deletes and moves elements immediately, updating any row index after every operation which is why the current implementation is unable to move elements to the correct positions and under certain scenarios even unable to correctly insert new elements either.

    With that in mind, a potential solution was relatively simple to implement. Provided that move changes are always sorted ascendingly by their target index which is always the case for changesets generated by DifferenceKit (and potentially other diffing frameworks), here's a hopefully complete list of index issues and how to solve them:


    1. Moves

    Problem

    As mentioned, NSTableView doesn't do batch updating so one change will immediately change element indexes, resulting in past moves messing with the source index of future moves.

    Solution

    Whenever an element is moved, its source index must be offset by the total number of previous moves' source indexes that were greater than (visually speaking below) itself.

    In the code, that means having to collect the moved source indexes and checking how many elements with a source index greater than the current move's source index are contained. This number represents the offset to be added to the source index of each move.

    Example

    Source: ["D", "C", "B", "A"] Target: ["A", "B", "C", "D"] Moves: (3, 0), (2, 1), (1, 2)

    NSTableView will incorrectly produce: ["A", "D", "C", "B"] because after it has moved A -> 0, subsequent moves with a source index greater than 0 (which in this example are all elements), will be off by the number of moves made above it. Here's the list of incorrect moves NSTableView does for this changeset:

    1st Move: (3, 0) -> ["A", "D", "C", "B"] - OK 2nd Move: (2, 1) -> ["A", "C", "D", "B"] - source index off by one, should be (3, 1) -> ["A", "B", "D", "C"] 3rd Move: (1, 2) -> ["A", "D", "C", "B"] - source index off by two, should be (3, 2) -> ["A", "B", "C", "D"]


    2. Moves with inserts

    Problem

    As part of DifferenceKit's 3rd stage changeset, not only are elements moved but also new ones inserted which then messes with the target index of the moves.

    Solution

    Applying moves after inserts is the first mistake because not only does a move to an index above an inserted element consequently move down all elements below, creating many additional unwanted moves (from which recovery would not be easily possible I guess) which change the actual target index of a move that is below the inserted element but it also changes the source index of every element to-be-moved that is below an inserted element.

    So to be able to properly move elements, inserts must be deferred until after all moves were made and then processed ascendingly by the element's index to avoid scrambling with the indexes even further, which however, seems to be always the case for DifferenceKit changesets, so no extra sorting needed here.

    Now that elements are inserted after the moves, the second issue must be taken care of which is that moves provided by DifferenceKit's diffing algorithm already account for the inserted indexes but since the items haven't been inserted yet and table views can't have "holes" in it, every target index of a move that is below an inserted element will be offset by the number of inserted elements above it.

    To fix this, a list of all insert indexes can be created in advance which is then used during a move to determine how many elements are going to be inserted above the move's target index. This number represents the offset and must be subtracted from the target index of a move to allow for performing all move operations according to the changeset.

    Example

    For this example, it is already assumed that inserts are made after moves because otherwise, the correct order cannot be restored, not even by simple index adjustments.

    Source: ["D", "C", "B"] Target: ["A", "B", "C", "D"] Moves: (2, 1), (1, 2) Inserts: ("A", 0)

    NSTableView will incorrectly produce ["A", "D", "C", "B"] because due to the insert of A -> 0 every move's target index greater than 0 (in our case every element) will be off by one, in addition to the first move causing an offset by one for the second move, due to the mentioned first problem when moving items. Again, here's the list of incorrect moves NSTableView does for this changeset:

    1st move: (2, 1) -> ["D", "B", "C"] - source index off by one, should be (3, 1) -> ["B", "D", "C"] 2nd move: (1, 2) -> ["D", "C", "B"] - source & target index off by one, should be (2, 1) -> ["B", "C", "D"] Insert: ("A", 0) -> ["A", "D", "C", "B"] - OK (will then be ["A", "B", "C", "D"] for the corrected moves)

    Note that by ensuring inserts come after moves, inserted elements will already be at the correct position but without applying both offsets to every move, the order will be incorrect.


    Screenshot

    Here's a screenshot for comparison with the fix applied on the right. The source is a scrambled alphanumeric character set that is missing the letters B, C, F, K, U, W, numbers 1, 4, 7 and includes the emojis πŸ˜€, 😁, πŸ˜‚. πŸ˜ƒ, πŸ˜„, πŸ˜…, πŸ˜†, πŸ˜‡: ["J", "😁", "E", "T", "6", "πŸ˜†", "πŸ˜‚", "πŸ˜ƒ", "M", "I", "2", "πŸ˜‡", "πŸ˜…", "O", "G", "H", "0", "πŸ˜„", "5", "V", "Z", "D", "R", "9", "8", "3", "Q", "S", "L", "Y", "A", "X", "P", "πŸ˜€", "N"]

    The target is the alphanumeric character set: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]

    Demo-Comparison

    Some notes

    By using Swift's IndexSet I was able to reduce the performance overhead a bit so that in the end, this extra logic should be tolerable, considering that the biggest portion of computation time is probably being consumed by the AppKit anyway, but I haven't specifically tested that yet.

    During my tests, I've frequently shuffled the emoji dataset in the sample project while also trying out datasets with random inserts, moves and deletions and I could not find any mistakes in the resulting table view. Real tests comparing the data set with the actual NSTableView cells however, would be more appropriate, of course. Note that while trying to break the table view's animations by quickly shuffling many times in a row, I was able to partially scramble the NSCollectionView's order, the table view's animations though seemed very robust to me so it looks like it's just NSCollectionView's performBatchUpdates not being able to handle too many move/insert/delete operations in a row very well.

    Before I forget it, awesome project by the way, really appreciate it and thanks for reading this long story, of course! πŸ€“


    Table


    TL;DR

    NSTableView doesn't support batch updating so every change (insert, delete, move) may alter its row indexes, making it difficult to apply moves generated from a StagedChangeset unless indexes are corrected accordingly and it is ensured that inserts are always performed after any moves.

    opened by tobiasjordan 6
  • Completion Block for UICollectionView and UITableView reload

    Completion Block for UICollectionView and UITableView reload

    Checklist

    • [x] All tests are passed.
    • [ ] Added tests.
    • [x] Documented the code using Xcode markup.
    • [x] Searched existing pull requests for ensure not duplicated.
    • [x] Updated Example

    Description

    As discussed in https://github.com/ra1028/DiffableDataSources/pull/10 a completion block is needed in DiffableDataSource to mirror Apple's APIs as closely as possible. Moreover, having a completion block when Differences are applied might be very handy for client to have.

    Related Issue

    https://github.com/ra1028/DiffableDataSources/pull/10

    Motivation and Context

    Provides better usage of the API with having a Completion block.

    Impact on Existing Code

    APIs will not break as the completion block is Optional and defaulted to nil

    opened by serjooo 6
  • `Differentiable` requres `Equatable` AND `Hashable`, but docs say otherwise

    `Differentiable` requres `Equatable` AND `Hashable`, but docs say otherwise

    Checklist

    Expected Behavior

    The docs (README) specifies that:

    There are default implementations of Differentiable for the types that conforming to Equatable [or] Hashable

    Current Behavior

    Differentiable is defined as such:

    public typealias Differentiable = ContentIdentifiable & ContentEquatable
    

    Detailed Description (Include Screenshots)

    This discrepancy cost me a few days of refactoring, due to having many nested types that aren't all Equatable or Hashable. I conformed them to Equatable, and theoretically they should be Differentiable, but they don't seem to be. Any advice or workarounds appreciated here, but, this seems like a difference between the docs + code.

    Environment

    • Library version: 1.1.5

    • Swift version: 5.7

    • iOS version: 16.0

    • Xcode version: 14.0

    • Devices/Simulators: Sim

    • CocoaPods/Carthage version: 1.11.3

    opened by peterkos 0
  • The algorithm cannot get the best diff results?

    The algorithm cannot get the best diff results?

    If I have two arrays like below:

    A: [a, b, c]
    B: [b, c, a]
    

    the diff result will be:

    [
    (source:1, target:0),
    (source:2,target:1)
    ]
    

    In fact, we just need 1 move:

    (source:0, target 2)
    

    Is this algorithm not able to get best diff results?

    opened by starFelix 1
  • tvOS UICollectionView.reload crash

    tvOS UICollectionView.reload crash

    Checklist

    Expected Behavior

    DifferenceKit should be crash-free

    Current Behavior

    Our application is getting crash reports related to Difference Kit UICollectionView.reload<A>(using:interrupt:setData:) (UIKitExtension.swift:0)

    Detailed Description (Include Screenshots)

    Our developers are unable to reproduce locally but we are seeing increasing crash rates in our analytics. We realize we have limited information to diagnose but any advice or suggestions would be appreciated.

    Environments

    • Library version: 1.3.0

    • Swift version: 5

    • iOS version: tvOS 15.6

    • Xcode version: 13.2.1

    opened by dannyhuggins 0
  • Bump tzinfo from 1.2.5 to 1.2.10 in /Benchmark

    Bump tzinfo from 1.2.5 to 1.2.10 in /Benchmark

    Bumps tzinfo from 1.2.5 to 1.2.10.

    Release notes

    Sourced from tzinfo's releases.

    v1.2.10

    TZInfo v1.2.10 on RubyGems.org

    v1.2.9

    • Fixed an incorrect InvalidTimezoneIdentifier exception raised when loading a zoneinfo file that includes rules specifying an additional transition to the final defined offset (for example, Africa/Casablanca in version 2018e of the Time Zone Database). #123.

    TZInfo v1.2.9 on RubyGems.org

    v1.2.8

    • Added support for handling "slim" format zoneinfo files that are produced by default by zic version 2020b and later. The POSIX-style TZ string is now used calculate DST transition times after the final defined transition in the file. The 64-bit section is now always used regardless of whether Time has support for 64-bit times. #120.
    • Rubinius is no longer supported.

    TZInfo v1.2.8 on RubyGems.org

    v1.2.7

    • Fixed 'wrong number of arguments' errors when running on JRuby 9.0. #114.
    • Fixed warnings when running on Ruby 2.8. #112.

    TZInfo v1.2.7 on RubyGems.org

    v1.2.6

    • Timezone#strftime('%s', time) will now return the correct number of seconds since the epoch. #91.
    • Removed the unused TZInfo::RubyDataSource::REQUIRE_PATH constant.
    • Fixed "SecurityError: Insecure operation - require" exceptions when loading data with recent Ruby releases in safe mode.
    • Fixed warnings when running on Ruby 2.7. #106 and #111.

    TZInfo v1.2.6 on RubyGems.org

    Changelog

    Sourced from tzinfo's changelog.

    Version 1.2.10 - 19-Jul-2022

    Version 1.2.9 - 16-Dec-2020

    • Fixed an incorrect InvalidTimezoneIdentifier exception raised when loading a zoneinfo file that includes rules specifying an additional transition to the final defined offset (for example, Africa/Casablanca in version 2018e of the Time Zone Database). #123.

    Version 1.2.8 - 8-Nov-2020

    • Added support for handling "slim" format zoneinfo files that are produced by default by zic version 2020b and later. The POSIX-style TZ string is now used calculate DST transition times after the final defined transition in the file. The 64-bit section is now always used regardless of whether Time has support for 64-bit times. #120.
    • Rubinius is no longer supported.

    Version 1.2.7 - 2-Apr-2020

    • Fixed 'wrong number of arguments' errors when running on JRuby 9.0. #114.
    • Fixed warnings when running on Ruby 2.8. #112.

    Version 1.2.6 - 24-Dec-2019

    • Timezone#strftime('%s', time) will now return the correct number of seconds since the epoch. #91.
    • Removed the unused TZInfo::RubyDataSource::REQUIRE_PATH constant.
    • Fixed "SecurityError: Insecure operation - require" exceptions when loading data with recent Ruby releases in safe mode.
    • Fixed warnings when running on Ruby 2.7. #106 and #111.
    Commits
    • 0814dcd Fix the release date.
    • fd05e2a Preparing v1.2.10.
    • b98c32e Merge branch 'fix-directory-traversal-1.2' into 1.2
    • ac3ee68 Remove unnecessary escaping of + within regex character classes.
    • 9d49bf9 Fix relative path loading tests.
    • 394c381 Remove private_constant for consistency and compatibility.
    • 5e9f990 Exclude Arch Linux's SECURITY file from the time zone index.
    • 17fc9e1 Workaround for 'Permission denied - NUL' errors with JRuby on Windows.
    • 6bd7a51 Update copyright years.
    • 9905ca9 Fix directory traversal in Timezone.get when using Ruby data source
    • Additional commits viewable in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    • @dependabot use these labels will set the current labels as the default for future PRs for this repo and language
    • @dependabot use these reviewers will set the current reviewers as the default for future PRs for this repo and language
    • @dependabot use these assignees will set the current assignees as the default for future PRs for this repo and language
    • @dependabot use this milestone will set the current milestone as the default for future PRs for this repo and language

    You can disable automated security fix PRs for this repo from the Security Alerts page.

    dependencies 
    opened by dependabot[bot] 0
  • Bump tzinfo from 1.2.5 to 1.2.10

    Bump tzinfo from 1.2.5 to 1.2.10

    Bumps tzinfo from 1.2.5 to 1.2.10.

    Release notes

    Sourced from tzinfo's releases.

    v1.2.10

    TZInfo v1.2.10 on RubyGems.org

    v1.2.9

    • Fixed an incorrect InvalidTimezoneIdentifier exception raised when loading a zoneinfo file that includes rules specifying an additional transition to the final defined offset (for example, Africa/Casablanca in version 2018e of the Time Zone Database). #123.

    TZInfo v1.2.9 on RubyGems.org

    v1.2.8

    • Added support for handling "slim" format zoneinfo files that are produced by default by zic version 2020b and later. The POSIX-style TZ string is now used calculate DST transition times after the final defined transition in the file. The 64-bit section is now always used regardless of whether Time has support for 64-bit times. #120.
    • Rubinius is no longer supported.

    TZInfo v1.2.8 on RubyGems.org

    v1.2.7

    • Fixed 'wrong number of arguments' errors when running on JRuby 9.0. #114.
    • Fixed warnings when running on Ruby 2.8. #112.

    TZInfo v1.2.7 on RubyGems.org

    v1.2.6

    • Timezone#strftime('%s', time) will now return the correct number of seconds since the epoch. #91.
    • Removed the unused TZInfo::RubyDataSource::REQUIRE_PATH constant.
    • Fixed "SecurityError: Insecure operation - require" exceptions when loading data with recent Ruby releases in safe mode.
    • Fixed warnings when running on Ruby 2.7. #106 and #111.

    TZInfo v1.2.6 on RubyGems.org

    Changelog

    Sourced from tzinfo's changelog.

    Version 1.2.10 - 19-Jul-2022

    Version 1.2.9 - 16-Dec-2020

    • Fixed an incorrect InvalidTimezoneIdentifier exception raised when loading a zoneinfo file that includes rules specifying an additional transition to the final defined offset (for example, Africa/Casablanca in version 2018e of the Time Zone Database). #123.

    Version 1.2.8 - 8-Nov-2020

    • Added support for handling "slim" format zoneinfo files that are produced by default by zic version 2020b and later. The POSIX-style TZ string is now used calculate DST transition times after the final defined transition in the file. The 64-bit section is now always used regardless of whether Time has support for 64-bit times. #120.
    • Rubinius is no longer supported.

    Version 1.2.7 - 2-Apr-2020

    • Fixed 'wrong number of arguments' errors when running on JRuby 9.0. #114.
    • Fixed warnings when running on Ruby 2.8. #112.

    Version 1.2.6 - 24-Dec-2019

    • Timezone#strftime('%s', time) will now return the correct number of seconds since the epoch. #91.
    • Removed the unused TZInfo::RubyDataSource::REQUIRE_PATH constant.
    • Fixed "SecurityError: Insecure operation - require" exceptions when loading data with recent Ruby releases in safe mode.
    • Fixed warnings when running on Ruby 2.7. #106 and #111.
    Commits
    • 0814dcd Fix the release date.
    • fd05e2a Preparing v1.2.10.
    • b98c32e Merge branch 'fix-directory-traversal-1.2' into 1.2
    • ac3ee68 Remove unnecessary escaping of + within regex character classes.
    • 9d49bf9 Fix relative path loading tests.
    • 394c381 Remove private_constant for consistency and compatibility.
    • 5e9f990 Exclude Arch Linux's SECURITY file from the time zone index.
    • 17fc9e1 Workaround for 'Permission denied - NUL' errors with JRuby on Windows.
    • 6bd7a51 Update copyright years.
    • 9905ca9 Fix directory traversal in Timezone.get when using Ruby data source
    • Additional commits viewable in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    • @dependabot use these labels will set the current labels as the default for future PRs for this repo and language
    • @dependabot use these reviewers will set the current reviewers as the default for future PRs for this repo and language
    • @dependabot use these assignees will set the current assignees as the default for future PRs for this repo and language
    • @dependabot use this milestone will set the current milestone as the default for future PRs for this repo and language

    You can disable automated security fix PRs for this repo from the Security Alerts page.

    dependencies 
    opened by dependabot[bot] 0
  • Animation option for row reloading is not a valid option in AppKitExtension

    Animation option for row reloading is not a valid option in AppKitExtension

    Checklist

    Current Behavior

    When performing a NSTableView.reload a reloadRowsAnimation can be specified but this is not a valid option for a reloadData operation

    Expected Behavior

    That parameter should be removed.

    opened by martindufort 0
Releases(1.3.0)
  • 1.3.0(Jun 24, 2022)

    What's Changed

    • Enable view-based NSTableViews to reload entire rows by @ibsh in https://github.com/ra1028/DifferenceKit/pull/138
    • Fix batch moving of rows in NSTableView by @tobiasjordan in https://github.com/ra1028/DifferenceKit/pull/144
    • chore: Version 1.3.0 by @ra1028 in https://github.com/ra1028/DifferenceKit/pull/146

    New Contributors

    • @ibsh made their first contribution in https://github.com/ra1028/DifferenceKit/pull/138
    • @tobiasjordan made their first contribution in https://github.com/ra1028/DifferenceKit/pull/144

    Full Changelog: https://github.com/ra1028/DifferenceKit/compare/1.2.0...1.3.0

    Source code(tar.gz)
    Source code(zip)
  • 1.2.0(Jun 8, 2021)

    Improvements

    • Add conditional ContentEquatable conformance to Array (#101 by @nkristek)
    • ARM64 architecture for Apple Silicon support (#124 by @Dahlgren)
    Source code(tar.gz)
    Source code(zip)
  • 1.1.5(Jan 14, 2020)

    This version DifferenceKit supports both Swift 4.2 and Swift 5+.

    FIX

    1. Fix update bug in AppKit extension (by @ra1028 #91, reported by @martindufort #90)
    Source code(tar.gz)
    Source code(zip)
  • 1.1.4(Dec 9, 2019)

    This version DifferenceKit supports both Swift 4.2 and Swift 5+.

    Enhancement

    1. Add ContentIdentifiable and use protocol composition. (by @Marcocanc #83)
    2. Enabling library evolution mode by setting BUILD_LIBRARY_FOR_DISTRIBUTION to YES. (by @ra1028 #88)
    Source code(tar.gz)
    Source code(zip)
  • 1.1.3(Jun 17, 2019)

    This version DifferenceKit supports both Swift 4.2 and Swift 5.

    Enhancement

    1. Add 'Extensions' directory for support Cocoa platforms in Package.swift. (by @hallee #73)
    2. Add Package.swift for Swift 5. (by @ra1028 #75)
    Source code(tar.gz)
    Source code(zip)
  • 1.1.2(May 7, 2019)

    This version DifferenceKit supports both Swift 4.2 and Swift 5.

    Enhancement

    1. Minor performance improvements and Internal refactoring (by @ra1028 #67)
    2. Add Swift 5 to supported Swift versions in podspec (by @ra1028 #67)
    Source code(tar.gz)
    Source code(zip)
  • 1.1.1(Mar 28, 2019)

  • 1.1.0(Mar 27, 2019)

    This version DifferenceKit supports both Swift 4.2 and Swift 5.

    Xcode 10.2 Support

    1. Swift 5.0 and Xcode 10.2 support (by @alanzeino #55, @Kaspik #55, @ra1028 #58)

    License

    1. The kind of license is now changed to Apache 2.0 License from MIT License. (by @ra1028 #53)
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0(Feb 8, 2019)

    First major version 1.0.0 πŸŽ‰

    Enhancement

    • Add Swift Package Manager support (by @insidegui https://github.com/ra1028/DifferenceKit/pull/50)
    • Prevent to redundant wrapping with AnyDifferentiable (by @ra1028 https://github.com/ra1028/DifferenceKit/pull/52)
    Source code(tar.gz)
    Source code(zip)
  • 0.8.0(Nov 10, 2018)

  • 0.7.2(Oct 9, 2018)

    Fix

    • https://github.com/ra1028/DifferenceKit/pull/40 The element updates are included in stage 1 changeset to fix the animation glitch occurs when element updated with other differences. The animation glitch when the difference contains section updates was fixed in https://github.com/ra1028/DifferenceKit/pull/10 , but another glitch occurred when element update was included. Although this is a workaround, the glitch of the latter is more likely to occur, so I fixed.
    Source code(tar.gz)
    Source code(zip)
  • 0.7.1(Oct 4, 2018)

  • 0.7.0(Oct 2, 2018)

    Improvements

    • Performance improved by 1.5x with Swift4.2 πŸŽ‰
    • Add computed properties sectionChangeCount, elementChangeCount, hasSectionChanges andhasElementChanges in Changeset.
    • Add ContentEquatable.

    Breaking change

    • End support for Swift 4.1 or lower.
    Source code(tar.gz)
    Source code(zip)
  • 0.6.0(Sep 19, 2018)

    Features

    • Add support for Swift4.2 (by @Sameesunkaria https://github.com/ra1028/DifferenceKit/pull/30)
    • macOS is now supported with UI extension (by @OskarGroth https://github.com/ra1028/DifferenceKit/pull/29)
    • Add subspec AppKitExtension for macOS UI

    Breaking changes

    • The subspec name UIExtensions is now renamed to UIKitExtension
    Source code(tar.gz)
    Source code(zip)
  • 0.5.3(Aug 30, 2018)

    Fix

    • Fix unexpected crashs by updating the data held by the data-source inside performBatchUpdates (https://github.com/ra1028/DifferenceKit/issues/17 reported by @Usipov).
    Source code(tar.gz)
    Source code(zip)
  • 0.5.2(Aug 27, 2018)

  • 0.5.1(Aug 26, 2018)

  • 0.5.0(Aug 21, 2018)

    Breaking changes

    • Section is renamed to ArraySection
    • DifferentiableSection is inherited from Differentiable
    • DifferentiableSection no longer requires a model now

    Improvement

    • Got a bit better performance
    Source code(tar.gz)
    Source code(zip)
  • 0.4.1(Aug 20, 2018)

  • 0.4.0(Aug 12, 2018)

    Breaking changes

    • Turn back the name of Differentiable.identifier to differenceIdentifier
    • Turn back the name of associated type Differentiable.Identifier to DifferenceIdentifier

    Fix

    • Fixed a crash bug in edge cases of sectioned collection diffing (https://github.com/ra1028/DifferenceKit/issues/5 reported by @Sameesunkaria)
    Source code(tar.gz)
    Source code(zip)
  • 0.3.0(Aug 10, 2018)

    Breaking change

    • Differentiable.differenceIdentifier is now renamed to identifier.
    • Associated type Differentiable.DifferenceIdentifier is now renamed to Identifier.
    • Differentiable.isUpdated(from:) is now meaning reversed, name has become isContentEqual(to:).

    ⚠️ isUpdated(from:) became isContentEqual(to:) and the meaning was reversed. An update is detected by returning false.

    func isUpdated(from source: User) -> Bool {
        return name != source.name
    }
    

    ↓↓↓

    func isContentEqual(to source: User) -> Bool {
        return name == source.name
    }
    
    Source code(tar.gz)
    Source code(zip)
  • 0.2.0(Aug 5, 2018)

    Change

    • Support iOS 9.0, tvOS 9.0 (https://github.com/ra1028/DifferenceKit/pull/1)
    • Support macOS, watchOS (https://github.com/ra1028/DifferenceKit/pull/2)
    Source code(tar.gz)
    Source code(zip)
  • 0.1.0(Aug 3, 2018)

Owner
Ryo Aoyama
β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘ β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–€β–„β–‘β–‘β–‘β–„β–€β–‘β–‘β–‘β–‘β–‘β–‘β–‘ β–‘β–‘β–‘β–‘β–‘β–„β–ˆβ–€β–ˆβ–ˆβ–ˆβ–€β–ˆβ–„β–‘β–‘β–‘β–‘β–‘ β–‘β–‘β–‘β–ˆβ–€β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–€β–ˆβ–‘β–‘β–‘ β–‘β–‘β–‘β–ˆβ–‘β–ˆβ–€β–€β–€β–€β–€β–€β–€β–ˆβ–‘β–ˆβ–‘β–‘β–‘ β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–€β–€β–‘β–‘β–‘β–€β–€β–‘β–‘β–‘β–‘β–‘β–‘β–‘ β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘
Ryo Aoyama
SwiftRegressor - A linear regression tool that’s flexible and easy to use

SwiftRegressor - A linear regression tool that’s flexible and easy to use

null 3 Jul 10, 2022
Safe and fast access to SwiftUI PreviewDevice

SafePreviewDevice Motivation At WWDC 2019, Apple announced SwiftUI a new library for building UI in a simple and fast way. Xcode’s SwiftUI preview let

Antonino Francesco Musolino 11 Jun 28, 2022
πŸ¦€Amazingly incredible extraordinary lightning fast diffing in Swift

DeepDiff ❀️ Support my apps ❀️ Push Hero - pure Swift native macOS application to test push notifications PastePal - Pasteboard, note and shortcut man

Khoa 2k Dec 29, 2022
FastLayout - A UIKit or AppKit package for fast UI design

FastLayout FastLayout is a UIKit or AppKit package for fast UI design. Layout Ex

null 1 Feb 19, 2022
A handy collection of Swift method and Tools to build project faster and more efficient.

SwifterKnife is a collection of Swift extension method and some tools that often use in develop project, with them you might build project faster and

李阳 4 Dec 29, 2022
BFKit-Swift is a collection of useful classes, structs and extensions to develop Apps faster.

Features β€’ Classes and Extensions Compatibility β€’ Requirements β€’ Communication β€’ Contributing β€’ Installing and Usage β€’ Documentation β€’ Changelog β€’ Exa

Fabrizio Brancati 992 Dec 2, 2022
A Swift package for rapid development using a collection of micro utility extensions for Standard Library, Foundation, and other native frameworks.

ZamzamKit ZamzamKit is a Swift package for rapid development using a collection of micro utility extensions for Standard Library, Foundation, and othe

Zamzam Inc. 261 Dec 15, 2022
SharkUtils is a collection of Swift extensions, handy methods and syntactical sugar that we use within our iOS projects at Gymshark.

SharkUtils is a collection of Swift extensions, handy methods and syntactical sugar that we use within our iOS projects at Gymshark.

Gymshark 1 Jul 6, 2021
Collection of native Swift extensions to boost your development. Support tvOS and watchOS.

SparrowKit Collection of native Swift extensions to boost your development. Support iOS, tvOS and watchOS. If you like the project, don't forget to pu

Ivan Vorobei 119 Dec 20, 2022
A collection of useful result builders for Swift and Foundation value types

Swift Builders A collection of useful result builders for Swift and Foundation value types. Motivation Arrays, dictionaries, and other collection-base

David Roman 3 Oct 14, 2022
BFKit is a collection of useful classes and categories to develop Apps faster.

Swift Version β€’ What does it do β€’ Language support β€’ Requirements β€’ Communication β€’ Contributing β€’ Installing and Usage β€’ Documentation β€’ Changelog β€’

Fabrizio Brancati 806 Dec 2, 2022
A collection of common tools and commands used throughout the development process, customized for Kipple projects.

KippleTools A collection of common tools and commands used throughout the development process, customized for Kipple projects. ⚠️ The code in this lib

Kipple 10 Sep 2, 2022
A handy collection of more than 500 native Swift extensions to boost your productivity.

SwifterSwift is a collection of over 500 native Swift extensions, with handy methods, syntactic sugar, and performance improvements for wide range of

SwifterSwift 12k Jan 7, 2023
A Swift collection of unique, ordered objects

Introduction OrderedSet is essentially the Swift equivalent of Foundation's NSOrderedSet/NSMutableOrderedSet. It was created so Swift would have a uni

Weebly 248 Sep 14, 2022
Array diffs as collection view wants it - now in Swift ✨

Doppelganger-Swift Inspired by Doppelganger written in Swift Features Removes confusion from users when data changes Animates moving, inserting and de

Szymon MaΕ›lanka 9 Jul 9, 2019
A Collection of useful Swift property wrappers to make coding easier

Swift Property Wrappers A Collection of useful Swift property wrappers to make c

Gordan GlavaΕ‘ 2 Jan 28, 2022
Collection of Swift-extensions to boost development process.

SwiftBoost Collection of Swift-extensions to boost development process. Community Installation Ready to use on iOS 13+, tvOS 13+, watchOS 6.0+. Swift

Sparrow Code 119 Dec 20, 2022
Swift package containing collection protocols.

swift-collection-protocols A package containing collection protocols, for the Swift programming language. Overview No overview available. Availability

Alexandre H. Saad 0 Jul 28, 2022
A collection of Swift Package Manager plugins.

KipplePlugins A collection of Swift Package Manager plugins. ⚠️ The code in this library has been made public as-is for the purposes of education, dis

Kipple 10 Oct 10, 2022