🦀Amazingly incredible extraordinary lightning fast diffing in Swift

Overview

DeepDiff

❤️ Support my apps ❤️

❤️ ❤️ 😇 😍 🤘 ❤️ ❤️

CI Status Version Carthage Compatible License Platform Swift

DeepDiff tells the difference between 2 collections and the changes as edit steps. It also supports Texture, see Texture example

Usage

Basic

The result of diff is an array of changes, which is [Change]. A Change can be

  • .insert: The item was inserted at an index
  • .delete: The item was deleted from an index
  • .replace: The item at this index was replaced by another item
  • .move: The same item has moved from this index to another index

By default, there is no .move. But since .move is just .delete followed by .insert of the same item, it can be reduced by specifying reduceMove to true.

Here are some examples

let old = Array("abc")
let new = Array("bcd")
let changes = diff(old: old, new: new)

// Delete "a" at index 0
// Insert "d" at index 2
let old = Array("abcd")
let new = Array("adbc")
let changes = diff(old: old, new: new)

// Move "d" from index 3 to index 1
let old = [
  User(id: 1, name: "Captain America"),
  User(id: 2, name: "Captain Marvel"),
  User(id: 3, name: "Thor"),
]

let new = [
  User(id: 1, name: "Captain America"),
  User(id: 2, name: "The Binary"),
  User(id: 3, name: "Thor"),
]

let changes = diff(old: old, new: new)

// Replace user "Captain Marvel" with user "The Binary" at index 1

DiffAware protocol

Model must conform to DiffAware protocol for DeepDiff to work. An model needs to be uniquely identified via diffId to tell if there have been any insertions or deletions. In case of same diffId, compareContent is used to check if any properties have changed, this is for replacement changes.

public protocol DiffAware {
  associatedtype DiffId: Hashable

  var diffId: DiffId { get }
  static func compareContent(_ a: Self, _ b: Self) -> Bool
}

Some primitive types like String, Int, Character already conform to DiffAware

Animate UITableView and UICollectionView

Changes to DataSource can be animated by using batch update, as guided in Batch Insertion, Deletion, and Reloading of Rows and Sections

Since Change returned by DeepDiff follows the way batch update works, animating DataSource changes is easy.

For safety, update your data source model inside updateData to ensure synchrony inside performBatchUpdates

let oldItems = items
let newItems = DataSet.generateNewItems()
let changes = diff(old: oldItems, new: newItems)

collectionView.reload(changes: changes, section: 2, updateData: { 
  self.items = newItems
})

Take a look at Demo where changes are made via random number of items, and the items are shuffled.

How does it work

Wagner–Fischer

If you recall from school, there is Levenshtein distance which counts the minimum edit distance to go from one string to another.

Based on that, the first version of DeepDiff implements Wagner–Fischer, which uses dynamic programming to compute the edit steps between 2 strings of characters. DeepDiff generalizes this to make it work for any collection.

Some optimisations made

  • Check empty old or new collection to return early
  • Use diffId to quickly check that 2 items are not equal
  • Follow "We can adapt the algorithm to use less space, O(m) instead of O(mn), since it only requires that the previous row and current row be stored at any one time." to use 2 rows, instead of matrix to reduce memory storage.

The performance greatly depends on the number of items, the changes and the complexity of the equal function.

Wagner–Fischer algorithm has O(mn) complexity, so it should be used for collection with < 100 items.

Heckel

The current version of DeepDiff uses Heckel algorithm as described in A technique for isolating differences between files. It works on 2 observations about line occurrences and counters. The result is a bit lengthy compared to the first version, but it runs in linear time.

Thanks to

More

There are other algorithms that are interesting

Benchmarks

Benchmarking is done on real device iPhone 6, with random items made of UUID strings (36 characters including hyphens), just to make comparisons more difficult.

You can take a look at the code Benchmark. Test is inspired from DiffUtil

Among different frameworks

Here are several popular diffing frameworks to compare

💪 From 2000 items to 2100 items (100 deletions, 200 insertions)

let (old, new) = generate(count: 2000, removeRange: 100..<200, addRange: 1000..<1200)

benchmark(name: "DeepDiff", closure: {
  _ = DeepDiff.diff(old: old, new: new)
})

benchmark(name: "Dwifft", closure: {
  _ = Dwifft.diff(old, new)
})

benchmark(name: "Changeset", closure: {
  _ = Changeset.edits(from: old, to: new)
})

benchmark(name: "Differ", closure: {
  _ = old.diffTraces(to: new)
})

benchmark(name: "ListDiff", closure: {
  _ = ListDiff.List.diffing(oldArray: old, newArray: new)
})

Result

DeepDiff: 0.0450611114501953s
Differ: 0.199673891067505s
Dwifft: 149.603884935379s
Changeset: 77.5895738601685s
ListDiff: 0.105544805526733s

Increasing complexity

Here is how DeepDiff handles large number of items and changes

💪 From 10000 items to 11000 items (1000 deletions, 2000 insertions)

DeepDiff: 0.233131170272827s

💪 From 20000 items to 22000 items (2000 deletions, 4000 insertions)

DeepDiff: 0.453393936157227s

💪 From 50000 items to 55000 items (5000 deletions, 10000 insertions)

DeepDiff: 1.04128122329712s

💪 From 100000 items to 1000000 items

Are you sure?

Installation

CocoaPods

Add the following to your Podfile

pod 'DeepDiff'

Carthage

Add the following to your Cartfile

github "onmyway133/DeepDiff"

Swift Package Manager

Add the following to your Package.swift file

.package(url: "https://github.com/onmyway133/DeepDiff.git", .upToNextMajor(from: "2.3.0"))

DeepDiff can also be installed manually. Just download and drop Sources folders in your project.

Author

Khoa Pham, [email protected]

Contributing

We would love you to contribute to DeepDiff, check the CONTRIBUTING file for more info.

License

DeepDiff is available under the MIT license. See the LICENSE file for more info.

Comments
  • Using HashValue as table key is unreliable

    Using HashValue as table key is unreliable

    I've been testing a variation of your Heckel implementation in a macOS CollectionView project and found an issue. Using hashValue as the key in the table is not reliable. I've made this same mistake myself in early versions of the data structures used in that collection view implementation. My basic solution for now is to replace with [T.Element:TableEntry] and the issue hasn't come up again.

    From the Apple Docs

    A hash value, provided by a type’s hashValue property, is an integer that is the same for any two instances that compare equally. That is, for two instances a and b of the same type, if a == b, then a.hashValue == b.hashValue. The reverse is not true: Two instances with equal hash values are not necessarily equal to each other.

    opened by WCByrne 12
  • Heckel algorithm does not want to do replacements.

    Heckel algorithm does not want to do replacements.

    I'm having trouble getting the default algorithm to do replacements instead of deletes followed by inserts. Even this basic code...

    let old = [
        "New York",
        "Imagine City",
        "London"
    ]
    
    let new = [
        "New York",
        "Oslo",
        "London",
        ]
    
    let changes = diff(old: old, new: new)
    

    ...gives me a delete followed by an insert. Whereas using let changes = diff(old: old, new: new, algorithm: WagnerFischer()) makes everything work correctly.

    Is this an intentional attribute of the Heckel algorithm?

    opened by archagon 10
  • Data source update should be inside performBatchUpdates

    Data source update should be inside performBatchUpdates

    Both UICollectionView+Extensions and UITableView+Extensions need to add closures for updating the data model after performBatchUpdates is called but before updates are applied (Example fix below):

    public func reload<T: Hashable>(
        changes: [Change<T>],
        section: Int = 0,
        updateModel: (() -> Void)? = nil, // <===== This should fix #8 
        completion: ((Bool) -> Void)? = nil) {
        
        let changesWithIndexPath = IndexPathConverter().convert(changes: changes, section: section)
        
        // reloadRows needs to be called outside the batch
        
        performBatchUpdates({
          updateModel?()                  // <===== This should fix #8 
          internalBatchUpdates(changesWithIndexPath: changesWithIndexPath)
        }, completion: { finished in
          completion?(finished)
        })
        
        changesWithIndexPath.replaces.executeIfPresent {
          self.reloadItems(at: $0)
        }
      }
    

    Otherwise, you'll see NSInternalInconsistencyExceptions since the model will already have the same number of items before and after update.

    opened by dsanghan 9
  • Build Library For Distribution (swift compatibility)

    Build Library For Distribution (swift compatibility)

    hi! wondering if you'd be able to set the Build Libraries For Distribution flag to YES in the project's build settings. i use deep diff through Carthage and this would help to ensure support for new swift versions which seem to come with every new xcode release.

    thank you !

    opened by cgmaier 8
  • Need more public API

    Need more public API

    I have try to make extension for ASTableNode & ASCollectionNode (both from ASDK) with DeepDiff. So the ChangeWithIndexPath, IndexPathConverter & Array.executeIfPresent need to be public.

    Could you make it happen?

    opened by nixzhu 8
  • 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of sections.  The number of sections contained in the table view after the update (1) must be equal to the number of sections contained in the table view before the update (0), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted).'

    'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of sections. The number of sections contained in the table view after the update (1) must be equal to the number of sections contained in the table view before the update (0), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted).'

    Hey @onmyway133

    I've faced a crash when using sections in UITableView. I have a regular controller with some array of data

    var data: Array<ParticipantData> = []

    By design I want to use each item of this array as a section and inner array (items) as rows in this section. Here is ParticipantData:

    class ParticipantData {
     let id: Int
     let items: Array<Item>
    
        class func compareContent(_ a: ParticipantData, _ b: ParticipantData) -> Bool {
           // Compare ids and items
            return a.id == b.id && Comparator.compare(a.items, b.items)
        }
    
        public var diffId: Int {
            return self.id
        }
    }
    

    For calculating difference between collections I'm calling following code:

    if (!newData.isEmpty) {
                   // Calculate diff between old and new data
                    let changes = diff(old: self.data, new: newData)
    
                   // deliver results
                  // Neither section: self.data.count nor newData.count works
                    self.tableView.reload(changes: changes, section: self.data.count, replacementAnimation: UITableView.RowAnimation.none, updateData: {
                        self.data = newData
                    })
    } else {
                   // Tried a fix with reloading data when it's empty
                    self.tableView.reloadData()
    }
    

    And code which is responsible for displaying sections and rows:

    extension MainController: UITableViewDataSource, UITableViewDelegate {
        public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
           // Each section has as many rows as it has items 
           return data[section].items.count
        }
    
        public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = UITableViewCell()
            cell.textLabel?.text = "\(indexPath.section): \(indexPath.row)"
            return cell
        }
    
        public func numberOfSections(in tableView: UITableView) -> Int {
            // We have as many sections as items in array
            return data.count
        }
    
        public func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
            return "Title"
        }
    }
    

    As result I have a crash 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of sections. The number of sections contained in the table view after the update (1) must be equal to the number of sections contained in the table view before the update (0), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted).'

    Interesting thing: it's working when there is no numberOfSections method

    opened by sereden 7
  • Example using two String arrays

    Example using two String arrays

    Hey! We're trying to use DeepDiff with UIStackView which does not offer batch updates support as UITableView and UICollectionView. This means that when we get an insert we insert a new arrangedSubview, when we get a delete we delete it, and so on.

    We narrowed down the problem to the following example:

    var old = ["Category3", "Category2", "Category4", "Category5", "Category1"]
    let new = ["Category1", "Category2", "Category3", "Category4", "Category5"]
    
    let algorithm = WagnerFischer<String>()
    let changes = algorithm.diff(old: old, new: new)
    
    changes.forEach { change in
        switch change {
        case .insert(let info):
            old.insert(info.item, at: info.index)
        case .delete(let info):
            old.remove(at: info.index)
        case .move(let info):
            old.remove(at: info.fromIndex)
            old.insert(info.item, at: info.toIndex)
        case .replace(let info):
            old.remove(at: info.index)
            old.insert(info.newItem, at: info.index)
        }
    }
    
    print(old) // ["Category1", "Category2", "Category3", "Category4", "Category1"]
    
    (lldb) po changes
    ▿ replace : Replace<String>
      - oldItem : "Category3"
      - newItem : "Category1"
      - index : 0
    ▿ insert : Insert<String>
      - item : "Category3"
      - index : 2
    ▿ delete : Delete<String>
      - item : "Category1"
      - index : 4
    

    As you can see the output is not correct. We're probably missing something at some point but cannot identify where.

    Let us know. Thanks!

    opened by jakunico 6
  • IndexPath.section is always zero in the results

    IndexPath.section is always zero in the results

    While this issue is related to #1 , what I propose is to make DeepDiff applicable to calculate Diff in any section other than 0. Currently, the section number is hardcoded to be 0.

    What I propose, is to add a section parameter to the API to generate valid IndexPaths, for example:

    let changes = diff(old: oldItems, new: items, section: 4)
    
    opened by richardtop 5
  • lifetime of Heckel?

    lifetime of Heckel?

    hey! I was surprised to see that Heckel didn't have static functions, and instead expects diffs to be carried out on an instance. i noticed that in my usage of the class, i have

        public func update(data: [ListSection], animating: Bool = false, completion: (() -> Void)? = nil) {
            if self.data.isNotEmpty {
                let diff = HeckelDiffingAlgorithm().diff(old: self.data, new: data)
    

    where I create a new instance each time. Is that required? or can I create one instance and reuse it each time my data changes?

    opened by AndrewSB 3
  • How to use diffId when diffing SQL entities?

    How to use diffId when diffing SQL entities?

    Hi, I am trying to use DeepDiff and my models are sql objects that are returned from my backend. To enable the use of deepdiff, I need to be able to return a diffId in the to conform to the protocol DiffAware.

    The problem arises that my sql objects have composite primary keys, meaning that it requires more than just one element to compare each object's uniqueness. It is easy to do checks in the compareContent function, but I am very puzzled to how one would go about declaring the diffId to ensure that these types can return a hash, and that being a string has since all of my attributes are composed of strings.

    An example object is instantiated like Object(place: String, date: String, time: String, reason: String). Where the primary key is composed of the place, date and time values. How would one go about setting the diffId for this object?

    I hope this example makes sense.

    Thank you.

    opened by munibrahman 3
  • DeepDiff too slow when comparing 2 string arrays.

    DeepDiff too slow when comparing 2 string arrays.

    Hi, i am trying to implement a text-base diff viewer, my code looks like the following:

    let originalLines = originalSource.split(separator: "\n", omittingEmptySubsequences: false).map{String($0)}
    
    
    let diffLines = diffSource.split(separator: "\n", omittingEmptySubsequences: false).map{String($0)}
                
                
    let algorithmStart = CACurrentMediaTime()
    let diffchecker = WagnerFischer<String>(reduceMove: false)
    let changes = diffchecker.diff(old: originalLines, new: diffLines)
    DC_LOG_INFO("DiffVC, diff algorithm cost \(CACurrentMediaTime() - algorithmStart)")
    

    my sample files are 2 objc source files with 650+lines, and only 1 line is different , and the log prints out: DiffVC, diff algorithm cost 6.890240641998389

    Any idea of why this is so slow?

    opened by Gocy015 3
  • How to apply the changes to old-array

    How to apply the changes to old-array

    In my situation, I make diffing between two different classes packaged by the same enum. As: enum PackagingEnum: DiffAware { case a(ClassA) case b(ClassB)

    var diffId: Int {
        switch self {
        case a(let classA):
            return classA.hashValue
        case b(let classB)
            return classB.hashValue
        }
    }
    

    } in this case, I want to make diffing [ClassA] to [ClassB] via DeepDiff.

    but when I get the changes by 'diff(old: [Enum-B], new: [Enum-A])'

    I traverse the changes, and invoke 'insert/remove/move' in [Enum-B](the old), I get crashed because of 'Out of Range'

    opened by GloamZz 2
  • Unsupported watchOS version for PackageDescription version

    Unsupported watchOS version for PackageDescription version

    invalidManifestFormat("/var/folders/6q/wgy6jtp12w5gzgm9lzcglpqw0000gn/T/TemporaryFile.rCB6P4.swift:11:19: error: 'v6' is unavailable\n .watchOS(.v6)\n ^~\nPackageDescription.SupportedPlatform:45:27: note: 'v6' was introduced in PackageDescription 5.1\n public static var v6: PackageDescription.SupportedPlatform.WatchOSVersion\n ^", diagnosticFile: Optional(AbsolutePath:"/Users/vagrant/Library/Developer/Xcode/DerivedData/Harper-cxngxloxijxbllfwrovkiaxoqvem/SourcePackages/ManifestLoading/deepdiff.dia")) in https://github.com/onmyway133/DeepDiff

    Introduced in 2.3.2.

    Caused by this change: https://github.com/onmyway133/DeepDiff/commit/86d6b51b47804e380801f4eba45297cb07788b18#diff-f913940c58e8744a2af1c68b909bb6383e49007e6c5a12fb03104a9006ae677eR11

    I'm not getting this on all versions of Xcode. For example, it's working on Xcode 12.5 RC on my machine. But it fails consistently on Bitrise with Xcode 12.4 and 12.5.

    opened by RobinDaugherty 4
  • Should I implement diifId If object is already conforming Hashable?

    Should I implement diifId If object is already conforming Hashable?

    Hello,

    Should I implement diifId If object is already conforming Hashable? I am using RealmSwift and my class conforms Object protocol which also conforms Hashable. My problem is that I have an unique string as primary key and that value is used as hashValue. (I don't know how Realm handles it). Therefore, I believe It generates unique int from that string which is used on hash function. When I add below code, I can't be sure If id is unique every time. Therefore, I don't wanna add it.

        var diffId: Int {
            return identifier.hashValue
        }
    
    opened by emreond 1
  • Suggestion to simplify implementation

    Suggestion to simplify implementation

    We should encourage users to hook into the willSet parameter as the tableview / collection view are finicky and will crash when the underlying data is not atomically set.

    opened by 8secz-johndpope 0
  • Error adding new section to collection view

    Error adding new section to collection view

    Hi, Thanks for the great work done on this. I'm running into an issue adding a new section that did not exist in the first place. Is there any way to handle a diff such that it creates a new section altoegther and then inserts the diff row ?

    Error is below. Invalid update: invalid number of sections. The number of sections contained in the collection view after the update (2) must be equal to the number of sections contained in the collection view before the update (1), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted).

    opened by nikhilcoolstuff 1
Releases(2.3.3)
  • 2.3.1(Nov 12, 2019)

  • 2.3.0(Jun 14, 2019)

  • 2.2.0(May 3, 2019)

  • 2.0.1(Feb 28, 2019)

  • 2.0.0(Feb 26, 2019)

  • 1.4.0(Jan 4, 2019)

  • 1.3.0(Sep 24, 2018)

    • Update project configuration to resolve linking warning in app extension https://github.com/onmyway133/DeepDiff/pull/18, by KhaosT
    • Migrate to swift 4.2 https://github.com/onmyway133/DeepDiff/pull/25, by GrigoryUlanov

    🤘 Closed issues

    • Need more public API https://github.com/onmyway133/DeepDiff/issues/11
    • Use of unresolved identifier 'IndexPathConverter' https://github.com/onmyway133/DeepDiff/issues/17
    • reload changes https://github.com/onmyway133/DeepDiff/issues/7
    • No 'Move' step https://github.com/onmyway133/DeepDiff/issues/15
    • NSFetchedResultsController update UICV https://github.com/onmyway133/DeepDiff/issues/9
    • Heckel algorithm doesn't detect replaces? https://github.com/onmyway133/DeepDiff/issues/16
    • Using HashValue as table key is unreliable https://github.com/onmyway133/DeepDiff/issues/13
    • Can we reduce the speed of shuffle? https://github.com/onmyway133/DeepDiff/issues/22
    • Make completion handler optional similar to other Cocoa API https://github.com/onmyway133/DeepDiff/issues/20
    Source code(tar.gz)
    Source code(zip)
  • 1.2.0(Apr 12, 2018)

    Merged pull requests

    • add iOS 8 support https://github.com/onmyway133/DeepDiff/pull/6, by zlib
    • Texture example is added. https://github.com/onmyway133/DeepDiff/pull/10, by gungorbasa
    • Reload with custom animations https://github.com/onmyway133/DeepDiff/pull/12, by nixzhu

    Closed issues

    • IndexPath.section is always zero in the results https://github.com/onmyway133/DeepDiff/issues/4
    • "append" method ? https://github.com/onmyway133/DeepDiff/issues/3
    • reduceMove changed? https://github.com/onmyway133/DeepDiff/issues/14
    Source code(tar.gz)
    Source code(zip)
  • 1.1.2(Feb 3, 2018)

  • 1.1.1(Jan 22, 2018)

  • 1.1.0(Jan 3, 2018)

  • 1.0.0(Jan 3, 2018)

Owner
Khoa
Check my apps https://onmyway133.com/apps
Khoa
Diff - Simple diffing library in pure Swift

Diff Simple diffing library in pure Swift. Installing You can use Carthage or Swift Package Manager to install Diff. Usage Start by importing the pack

Sam Soffes 120 Sep 9, 2022
💻 A fast and flexible O(n) difference algorithm framework for Swift collection.

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

Ryo Aoyama 3.3k Jan 4, 2023
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
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
BCSwiftTor - Opinionated pure Swift controller for Tor, including full support for Swift 5.5 and Swift Concurrency

BCSwiftTor Opinionated pure Swift controller for Tor, including full support for

Blockchain Commons, LLC — A “not-for-profit” benefit corporation 4 Oct 6, 2022
Swift Markdown is a Swift package for parsing, building, editing, and analyzing Markdown documents.

Swift Markdown is a Swift package for parsing, building, editing, and analyzing Markdown documents.

Apple 2k Dec 28, 2022
Swift-DocC is a documentation compiler for Swift frameworks and packages aimed at making it easy to write and publish great developer documentation.

Swift-DocC is a documentation compiler for Swift frameworks and packages aimed at making it easy to write and publish great developer docum

Apple 833 Jan 3, 2023
Cross-Platform, Protocol-Oriented Programming base library to complement the Swift Standard Library. (Pure Swift, Supports Linux)

SwiftFoundation Cross-Platform, Protocol-Oriented Programming base library to complement the Swift Standard Library. Goals Provide a cross-platform in

null 620 Oct 11, 2022
Swift - ✏️Swift 공부 저장소✏️

Swift 스위프트의 기초 1. Swift의 기본 2. 변수와 상수 [3. 데이터 타입 기본] [4. 데이터 타입 고급] 5. 연산자 6. 흐름 제어 7. 함수 8. 옵셔널 객체지향 프로그래밍과 스위프트 9. 구조체와 클래스 10. 프로퍼티와 메서드 11. 인스턴스 생

Jiwon 0 Mar 9, 2022
Swift-ndi - Swift wrapper around NewTek's NDI SDK

swift-ndi Swift wrapper around NewTek's NDI SDK. Make sure you extracted latest

Alessio Nossa 12 Dec 29, 2022
__.swift is a port of Underscore.js to Swift.

__.swift Now, __.swift is version 0.2.0! With the chain of methods, __.swift became more flexible and extensible. Documentation: http://lotz84.github.

Tatsuya Hirose 86 Jun 29, 2022
SNTabBarDemo-Swift - Cool TabBar With Swift

SNTabBarDemo-Swift Cool TabBar How To Use // MARK: - setup private func setu

iAnchor 3 Sep 29, 2022
Swift-when - Expression switch support in Swift

Swift When - supporting switch expressions in Swift! What is it? Basically, it a

Gordan Glavaš 7 Nov 24, 2022
Swift-compute-runtime - Swift runtime for Fastly Compute@Edge

swift-compute-runtime Swift runtime for Fastly Compute@Edge Getting Started Crea

Andrew Barba 57 Dec 24, 2022
Swift-HorizontalPickerView - Customizable horizontal picker view component written in Swift for UIKit/iOS

Horizontal Picker View Customizable horizontal picker view component written in

Afraz Siddiqui 8 Aug 1, 2022
swift-highlight a pure-Swift data structure library designed for server applications that need to store a lot of styled text

swift-highlight is a pure-Swift data structure library designed for server applications that need to store a lot of styled text. The Highlight module is memory-efficient and uses slab allocations and small-string optimizations to pack large amounts of styled text into a small amount of memory, while still supporting efficient traversal through the Sequence protocol.

kelvin 4 Aug 14, 2022
Sovran-Swift: Small, efficient, easy. State Management for Swift

Sovran-Swift: Small, efficient, easy. State Management for Swift

Segment 5 Jan 3, 2023
Approximate is a Swift package that provides implementations of floating point comparisons for the Swift ecosystem

Approximate Approximate floating point equality comparisons for the Swift Programming Language. Introduction Approximate is a Swift package that provi

Christopher Blanchard 1 Jun 1, 2022
A Swift app, named 'iPose', for iPhone's pose measurement based on Swift.

iPhone's pose measurement based on Swift. This is a Swift app, named 'iPose', for iPhone's pose measurement based on Swift. This is a side project to

Ghasem Abdi 3 Jul 26, 2022