KeyPathKit is a library that provides the standard functions to manipulate data along with a call-syntax that relies on typed keypaths to make the call sites as short and clean as possible.

Related tags

Database swift data sql
Overview

KeyPathKit

Build Status platforms pod Carthage compatible Swift Package Manager compatible

Context

Swift 4 has introduced a new type called KeyPath, with allows to access the properties of an object with a very nice syntax. For instance:

let string = "Foo"
let keyPathForCount = \String.count

let count = string[keyPath: keyPathForCount] // count == 3

The great part is that the syntax can be very concise, because it supports type inference and property chaining.

Purpose of KeyPathKit

Consequently, I thought it would be nice to leverage this new concept in order to build an API that allows to perform data manipulation in a very declarative fashion.

SQL is a great language for such manipulations, so I took inspiration from it and implemented most of its standard operators in Swift 4 using KeyPath.

But what really stands KeyPathKit appart from the competition is its clever syntax that allows to express queries in a very seamless fashion. For instance :

contacts.filter(where: \.lastName == "Webb" && \.age < 40)

Installation

CocoaPods

Add the following to your Podfile:

pod "KeyPathKit"

Carthage

Add the following to your Cartfile:

github "vincent-pradeilles/KeyPathKit"

Swift Package Manager

Create a file Package.swift:

// swift-tools-version:4.0

import PackageDescription

let package = Package(
    name: "YourProject",
    dependencies: [
        .package(url: "https://github.com/vincent-pradeilles/KeyPathKit.git", "1.0.0" ..< "2.0.0")
    ],
    targets: [
        .target(name: "YourProject", dependencies: ["KeyPathKit"])
    ]
)

Operators

Operator details

For the purpose of demonstrating the usage of the operators, the following mock data is defined:

struct Person {
    let firstName: String
    let lastName: String
    let age: Int
    let hasDriverLicense: Bool
    let isAmerican: Bool
}

let contacts = [
    Person(firstName: "Charlie", lastName: "Webb", age: 10, hasDriverLicense: false, isAmerican: true),
    Person(firstName: "Alex", lastName: "Elexson", age: 22, hasDriverLicense: false, isAmerican: true),
    Person(firstName: "Charles", lastName: "Webb", age: 45, hasDriverLicense: true, isAmerican: true),
    Person(firstName: "Alex", lastName: "Zunino", age: 34, hasDriverLicense: true, isAmerican: true),
    Person(firstName: "Alex", lastName: "Alexson", age: 8, hasDriverLicense: false, isAmerican: true),
    Person(firstName: "John", lastName: "Webb", age: 28, hasDriverLicense: true, isAmerican: true),
    Person(firstName: "Webb", lastName: "Elexson", age: 30, hasDriverLicense: true, isAmerican: true)
]

and

Performs a boolean AND operation on a property of type Bool.

contacts.and(\.hasDriverLicense)
contacts.and(\.isAmerican)
false
true

average

Calculates the average of a numerical property.

contacts.average(of: \.age).rounded()
25

between

Filters out elements whose value for the property is not within the range.

contacts.between(\.age, range: 20...30)
// or
contacts.filter(where: 20...30 ~= \.age)
[Person(firstName: "Alex", lastName: "Elexson", age: 22, hasDriverLicense: false, isAmerican: true),
 Person(firstName: "John", lastName: "Webb", age: 28, hasDriverLicense: true, isAmerican: true), 
 Person(firstName: "Webb", lastName: "Elexson", age: 30, hasDriverLicense: true, isAmerican: true)]

contains

Returns whether the sequence contains one element for which the specified boolean property or predicate is true.

contacts.contains(where: \.hasDriverLicense)
contacts.contains(where: \.lastName.count > 10)
true
false

distinct

Returns all the distinct values for the property.

contacts.distinct(\.lastName)
["Webb", "Elexson", "Zunino", "Alexson"]

drop

Returns a subsequence by skipping elements while a property of type Bool or a predicate evaluates to true, and returning the remaining elements.

contacts.drop(while: \.age < 40)
[Person(firstName: "Charles", lastName: "Webb", age: 45, hasDriverLicense: true, isAmerican: true), 
 Person(firstName: "Alex", lastName: "Zunino", age: 34, hasDriverLicense: true, isAmerican: true), 
 Person(firstName: "Alex", lastName: "Alexson", age: 8, hasDriverLicense: false, isAmerican: true), 
 Person(firstName: "John", lastName: "Webb", age: 28, hasDriverLicense: true, isAmerican: true), 
 Person(firstName: "Webb", lastName: "Elexson", age: 30, hasDriverLicense: true, isAmerican: true)]

filter

Filters out elements whose value is false for one (or several) boolean property.

contacts.filter(where: \.hasDriverLicense)
[Person(firstName: "Charles", lastName: "Webb", age: 45, hasDriverLicense: true, isAmerican: true), 
 Person(firstName: "Alex", lastName: "Zunino", age: 34, hasDriverLicense: true, isAmerican: true), 
 Person(firstName: "John", lastName: "Webb", age: 28, hasDriverLicense: true, isAmerican: true), 
 Person(firstName: "Webb", lastName: "Elexson", age: 30, hasDriverLicense: true, isAmerican: true)]

Filter also works with predicates:

contacts.filter(where: \.firstName == "Webb")
[Person(firstName: "Charlie", lastName: "Webb", age: 10, hasDriverLicense: false, isAmerican: true),
 Person(firstName: "Charles", lastName: "Webb", age: 45, hasDriverLicense: true, isAmerican: true),
 Person(firstName: "John", lastName: "Webb", age: 28, hasDriverLicense: true, isAmerican: true)]

filterIn

Filters out elements whose value for an Equatable property is not in a given Sequence.

contacts.filter(where: \.firstName, in: ["Alex", "John"])
[Person(firstName: "Alex", lastName: "Elexson", age: 22, hasDriverLicense: false, isAmerican: true), 
 Person(firstName: "Alex", lastName: "Zunino", age: 34, hasDriverLicense: true, isAmerican: true), 
 Person(firstName: "Alex", lastName: "Alexson", age: 8, hasDriverLicense: false, isAmerican: true), 
 Person(firstName: "John", lastName: "Webb", age: 28, hasDriverLicense: true, isAmerican: true)]

filterLess

Filters out elements whose value is greater than a constant for a Comparable property.

contacts.filter(where: \.age, lessThan: 30)
// or
contacts.filter(where: \.age < 30)
[Person(firstName: "Charlie", lastName: "Webb", age: 10, hasDriverLicense: false, isAmerican: true), 
 Person(firstName: "Alex", lastName: "Elexson", age: 22, hasDriverLicense: false, isAmerican: true), 
 Person(firstName: "Alex", lastName: "Alexson", age: 8, hasDriverLicense: false, isAmerican: true), 
 Person(firstName: "John", lastName: "Webb", age: 28, hasDriverLicense: true, isAmerican: true)]
contacts.filter(where: \.age, lessOrEqual: 30)
// or
contacts.filter(where: \.age <= 30)
[Person(firstName: "Charlie", lastName: "Webb", age: 10, hasDriverLicense: false, isAmerican: true), 
 Person(firstName: "Alex", lastName: "Elexson", age: 22, hasDriverLicense: false, isAmerican: true), 
 Person(firstName: "Alex", lastName: "Alexson", age: 8, hasDriverLicense: false, isAmerican: true), 
 Person(firstName: "John", lastName: "Webb", age: 28, hasDriverLicense: true, isAmerican: true), 
 Person(firstName: "Webb", lastName: "Elexson", age: 30, hasDriverLicense: true, isAmerican: true)]

filterLike

Filters out elements whose value for a string property does not match a regular expression.

contacts.filter(where: \.lastName, like: "^[A-Za-z]*son$")
[Person(firstName: "Alex", lastName: "Elexson", age: 22, hasDriverLicense: false, isAmerican: true), 
 Person(firstName: "Alex", lastName: "Alexson", age: 8, hasDriverLicense: false, isAmerican: true), 
 Person(firstName: "Webb", lastName: "Elexson", age: 30, hasDriverLicense: true, isAmerican: true)]

filterMore

Filters out elements whose value is lesser than a constant for a Comparable property.

contacts.filter(where: \.age, moreThan: 30)
// or
contacts.filter(where: \.age > 30)
[Person(firstName: "Charles", lastName: "Webb", age: 45, hasDriverLicense: true, isAmerican: true), 
 Person(firstName: "Alex", lastName: "Zunino", age: 34, hasDriverLicense: true, isAmerican: true)]
contacts.filter(where: \.age, moreOrEqual: 30)
// or
contacts.filter(where: \.age >= 30)
[Person(firstName: "Charles", lastName: "Webb", age: 45, hasDriverLicense: true, isAmerican: true), 
 Person(firstName: "Alex", lastName: "Zunino", age: 34, hasDriverLicense: true, isAmerican: true), 
 Person(firstName: "Webb", lastName: "Elexson", age: 30, hasDriverLicense: true, isAmerican: true)]

first

Returns the first element matching a predicate.

contacts.first(where: \.lastName == "Webb")
Optional(Person(firstName: "Charlie", lastName: "Webb", age: 10, hasDriverLicense: false, isAmerican: true))

groupBy

Groups values by equality on the property.

contacts.groupBy(\.lastName)
["Alexson": [Person(firstName: "Alex", lastName: "Alexson", age: 8, hasDriverLicense: false, isAmerican: true)], 
 "Webb": [Person(firstName: "Charlie", lastName: "Webb", age: 10, hasDriverLicense: false, isAmerican: true), Person(firstName: "Charles", lastName: "Webb", age: 45, hasDriverLicense: true, isAmerican: true), Person(firstName: "John", lastName: "Webb", age: 28, hasDriverLicense: true, isAmerican: true)], 
 "Elexson": [Person(firstName: "Alex", lastName: "Elexson", age: 22, hasDriverLicense: false, isAmerican: true), Person(firstName: "Webb", lastName: "Elexson", age: 30, hasDriverLicense: true, isAmerican: true)], 
 "Zunino": [Person(firstName: "Alex", lastName: "Zunino", age: 34, hasDriverLicense: true, isAmerican: true)]]

join

Joins values of two sequences in tuples by the equality on their respective property.

contacts.join(\.firstName, with: contacts, on: \.lastName)
// or
contacts.join(with: contacts, where: \.firstName == \.lastName)
[(Person(firstName: "Webb", lastName: "Elexson", age: 30, hasDriverLicense: true, isAmerican: true), Person(firstName: "Charlie", lastName: "Webb", age: 10, hasDriverLicense: false, isAmerican: true)), 
 (Person(firstName: "Webb", lastName: "Elexson", age: 30, hasDriverLicense: true, isAmerican: true), Person(firstName: "Charles", lastName: "Webb", age: 45, hasDriverLicense: true, isAmerican: true)), 
 (Person(firstName: "Webb", lastName: "Elexson", age: 30, hasDriverLicense: true, isAmerican: true), Person(firstName: "John", lastName: "Webb", age: 28, hasDriverLicense: true, isAmerican: true))]

Joining on more than one attribute is also supported:

contacts.join(with: contacts, .where(\.firstName, equals: \.lastName), .where(\.hasDriverLicense, equals: \.isAmerican))
// or
contacts.join(with: contacts, where: \.firstName == \.lastName, \.hasDriverLicense == \.isAmerican)

map

Maps elements to their values of the property.

contacts.map(\.lastName)
["Webb", "Elexson", "Webb", "Zunino", "Alexson", "Webb", "Elexson"]

mapTo

Maps a sequence of properties to a function. This is, for instance, useful to extract a subset of properties into a structured type.

struct ContactCellModel {
    let firstName: String
    let lastName: String
}

contacts.map(\.lastName, \.firstName, to: ContactCellModel.init)
[ContactCellModel(firstName: "Webb", lastName: "Charlie"), 
 ContactCellModel(firstName: "Elexson", lastName: "Alex"), 
 ContactCellModel(firstName: "Webb", lastName: "Charles"), 
 ContactCellModel(firstName: "Zunino", lastName: "Alex"), 
 ContactCellModel(firstName: "Alexson", lastName: "Alex"), 
 ContactCellModel(firstName: "Webb", lastName: "John"), 
 ContactCellModel(firstName: "Elexson", lastName: "Webb")]

max

Returns the element with the greatest value for a Comparable property.

contacts.max(by: \.age)
contacts.max(\.age)
Optional(Person(firstName: "Charles", lastName: "Webb", age: 45, hasDriverLicense: true, isAmerican: true))
Optional(45)

min

Returns the element with the minimum value for a Comparable property.

contacts.min(by: \.age)
contacts.min(\.age)
Optional(Person(firstName: "Alex", lastName: "Alexson", age: 8, hasDriverLicense: false, isAmerican: true))
Optional(8)

or

Performs a boolean OR operation on an property of type Bool.

contacts.or(\.hasDriverLicense)
true

patternMatching

Allows the use of predicates inside a switch statement:

switch person {
case \.firstName == "Charlie":
    print("I'm Charlie!")
    fallthrough
case \.age < 18:
    print("I'm not an adult...")
    fallthrough
default:
    break
}

prefix

Returns a subsequence containing the initial, consecutive elements for whose a property of type Bool or a predicate evaluates to true.

contacts.prefix(while: \.age < 40)
[Person(firstName: "Charlie", lastName: "Webb", age: 10, hasDriverLicense: false, isAmerican: true),
 Person(firstName: "Alex", lastName: "Elexson", age: 22, hasDriverLicense: false, isAmerican: true)]

sum

Calculates the sum of the values for a numerical property.

contacts.sum(of: \.age)
177

sort

Sorts the elements with respect to a Comparable property.

contacts.sorted(by: \.age)
[Person(firstName: "Alex", lastName: "Alexson", age: 8, hasDriverLicense: false, isAmerican: true), 
 Person(firstName: "Charlie", lastName: "Webb", age: 10, hasDriverLicense: false, isAmerican: true), 
 Person(firstName: "Alex", lastName: "Elexson", age: 22, hasDriverLicense: false, isAmerican: true), Person(firstName: "John", lastName: "Webb", age: 28, hasDriverLicense: true, isAmerican: true), 
 Person(firstName: "Webb", lastName: "Elexson", age: 30, hasDriverLicense: true, isAmerican: true), 
 Person(firstName: "Alex", lastName: "Zunino", age: 34, hasDriverLicense: true, isAmerican: true), 
 Person(firstName: "Charles", lastName: "Webb", age: 45, hasDriverLicense: true, isAmerican: true)]

It's also possible to specify the sorting order, to sort on multiple criteria, or to do both.

contacts.sorted(by: .ascending(\.lastName), .descending(\.age))
[Person(firstName: "Alex", lastName: "Alexson", age: 8, hasDriverLicense: false, isAmerican: true), 
 Person(firstName: "Webb", lastName: "Elexson", age: 30, hasDriverLicense: true, isAmerican: true), 
 Person(firstName: "Alex", lastName: "Elexson", age: 22, hasDriverLicense: false, isAmerican: true), 
 Person(firstName: "Charles", lastName: "Webb", age: 45, hasDriverLicense: true, isAmerican: true), 
 Person(firstName: "John", lastName: "Webb", age: 28, hasDriverLicense: true, isAmerican: true), 
 Person(firstName: "Charlie", lastName: "Webb", age: 10, hasDriverLicense: false, isAmerican: true), 
 Person(firstName: "Alex", lastName: "Zunino", age: 34, hasDriverLicense: true, isAmerican: true)]

Author

Thanks

A big thank you to Jérôme Alves (elegantswift.com) for coming up with the right modelization to allow sorting on multiple properties with heterogenous type.

Comments
  • andTests.swift needs updating

    andTests.swift needs updating

    Hi, Your final test case in andTests is:

     func test_infinite_sequence() {
           var i = 0
           let data = AnyIterator { () -> TestData in
               defer { i += 1 }
               return TestData(bool: i % 3 != 0)
           }
           XCTAssertTrue(data.or(\.bool))
       }
    
    

    and should be:

    func test_infinite_sequence() {
          var i = 0
          let data = AnyIterator { () -> TestData in
              defer { i += 1 }
              return TestData(bool: i % 3 != 0)
          }
          XCTAssertFalse(data.and(\.bool))
      }
    

    Love this project!

    opened by Mozahler 4
  • Various enhancements and improvements

    Various enhancements and improvements

    Hello !

    Here's a set of various enhancements and improvements I'd like to see in this library. I recognise that some changes are a little bit opinionated so feel free to discuss/reject some of them if needed.

    Also, some changes are source breaking and I don't know what's your politic about it? Do you prefer we keep both API with a deprecated annotation? Or we can just keep the new version but we will eventually release this version under a new major version?

    • Modernize API by using by: label instead of of: to match more closely with Swift stdlib
    • Use reduce(into:) for better performances Enhance or and and to immediatly return on the first element that doesn't match the expression requirement – it has the benefits to make it work with infinite sequence
    • Add a specialized version of distinct() that works with Hashable elements in order to use a Set – it should make the contains lookup faster
    • Enhance filter to immediatly return on the first element that doesn't match the expression requirement
    • Pull NSPredicate init out of filter closure in order to init it only once
    • Leverage Swift 4.0 new "subscript with default value" Dictionary API (SE-0165)
    • Use stdlib min(by:) and max(by:) methods instead of a custom reduce()
    opened by jegnux 4
  • Errors with Swift 5.0 -- Extraneous argument label 'where:' in call

    Errors with Swift 5.0 -- Extraneous argument label 'where:' in call

    What an awesome library!! Hats off to you 🎩 Some of the tests fail under Swift 5.0 (10 lines in filterLessTests.swift have errors) I'll see if I can figure it out, but meantime here's what I know...

    I'm getting this message: Extraneous argument label 'where:' in call for these lines from class FilterLessTests: XCTestCase

        XCTAssertTrue(data.filter(where: \.int < 2).isEmpty)
        XCTAssertTrue(data.filter(where: \.double < 2.0).isEmpty)
    
        XCTAssertEqual(data.filter(where: \.int < 200), data)
        XCTAssertEqual(data.filter(where: \.int <= 100), data)
        XCTAssertEqual(data.filter(where: \.double < 6e4), data)
        XCTAssertEqual(data.filter(where: \.double <= 5e4), data)
    
        XCTAssertEqual(data.filter(where: \.int < 100), [TestData(int: 3, double: 4.5), TestData(int: -2, double: 5e4)])
        XCTAssertEqual(data.filter(where: \.double < 100.0), [TestData(int: 3, double: 4.5), TestData(int: 100, double: -2300)])
    
        XCTAssertEqual(data.filter(where: \.int < -5), [])
        XCTAssertEqual(data.filter(where: \.double <= -2301), [])
    

    These lines all test ok under Swift 4.2

    opened by Mozahler 3
  • Add `min(_:)` and `max(_:)`

    Add `min(_:)` and `max(_:)`

    Following my previous PR (#10) here are 2 new methods:

    public func min<T: Comparable>(_ attribute: KeyPath<Element, T>) -> T?
    public func max<T: Comparable>(_ attribute: KeyPath<Element, T>) -> T?
    

    They find the min / max value and immediately extract it. Here's an example:

    contacts.max(by: \.age)
    contacts.max(\.age)
    
    Optional(Person(firstName: "Charles", lastName: "Webb", age: 45, hasDriverLicense: true, isAmerican: true))
    Optional(45)
    
    contacts.min(by: \.age)
    contacts.min(\.age)
    
    Optional(Person(firstName: "Alex", lastName: "Alexson", age: 8, hasDriverLicense: false, isAmerican: true))
    Optional(8)
    
    opened by jegnux 3
  • Update for Xcode 10.2 and Swift 5

    Update for Xcode 10.2 and Swift 5

    In swift 5 SubSequence have been removed and replaced by DropWhileSequence or Array of Sequence.Element.

    Unit testing have been also updated for DropWhileSequence compliance. GroupTest have been updated: Sorting doesn't matter in dictionaries.

    • DS_Store has been added to .gitignore file.

    Merci ;)

    opened by PierrePerrin 1
  • The great restructure

    The great restructure

    • project restructure for SwiftPM - Sources folder
    • targets for iOS, macOS, tvOS and watchOS
    • SwiftPM support
    • podspec for cocoapods

    @vincent-pradeilles Will you set up Travis build for this? I could also but I think I need access for this repo then. Also will you release to cocoapods?

    opened by fassko 1
Releases(1.6.0)
  • 1.6.0(Apr 7, 2019)

  • 1.5.0(Jan 6, 2019)

  • 1.4.0(Nov 18, 2018)

    • Modernize API by using by: label instead of of: to match more closely with Swift stdlib
    • Use reduce(into:) for better performances Enhance or and and to immediatly return on the first element that doesn't match the expression requirement – it has the benefits to make it work with infinite sequence
    • Add a specialized version of distinct() that works with Hashable elements in order to use a Set – it should make the contains lookup faster
    • Enhance filter to immediatly return on the first element that doesn't match the expression requirement
    • Pull NSPredicate init out of filter closure in order to init it only once
    • Leverage Swift 4.0 new "subscript with default value" Dictionary API (SE-0165)
    • Use stdlib min(by:) and max(by:) methods instead of a custom reduce()
    Source code(tar.gz)
    Source code(zip)
Owner
Vincent Pradeilles
French iOS software engineer, working in Lyon, France 🇫🇷
Vincent Pradeilles
GraphQLite is a toolkit to work with GraphQL servers easily. It also provides several other features to make life easier during iOS application development.

What is this? GraphQLite is a toolkit to work with GraphQL servers easily. It also provides several other features to make life easier during iOS appl

Related Code 2.8k Jan 9, 2023
Simple, Strongly Typed UserDefaults for iOS, macOS and tvOS

简体中文 DefaultsKit leverages Swift 4's powerful Codable capabilities to provide a Simple and Strongly Typed wrapper on top of UserDefaults. It uses less

Nuno Dias 1.4k Dec 26, 2022
💾 Safe, statically-typed, store-agnostic key-value storage written in Swift!

Storez ?? Safe, statically-typed, store-agnostic key-value storage Highlights Fully Customizable: Customize the persistence store, the KeyType class,

Kitz 67 Aug 7, 2022
ReadWriteLock - Swift Implementation of a standard Read/Write lock.

ReadWriteLock A Swift implementation of a Read/Write lock. I'm really amazed that the Swift Standard Library (nor the Objective-C standard library) ha

Galen Rhodes 1 Mar 1, 2022
Add Strictly Typed NIO Channel Builders for Swift 5.7

⚠️ Requires Swift 5.7 Omnibus is a set of helpers for SwiftNIO that allow you to leverage Swift's generics type system to create NIO Channels. It depe

Orlandos 6 Jun 10, 2022
Typed key-value storage solution to store Codable types in various persistence layers with few lines of code!

?? Stores A typed key-value storage solution to store Codable types in various persistence layers like User Defaults, File System, Core Data, Keychain

Omar Albeik 94 Dec 31, 2022
A library that provides the ability to import/export Realm files from a variety of data container formats.

Realm Converter Realm Converter is an open source software utility framework to make it easier to get data both in and out of Realm. It has been built

Realm 212 Dec 9, 2022
🔥 🔥 🔥Support for ORM operation,Customize the PQL syntax for quick queries,Support dynamic query,Secure thread protection mechanism,Support native operation,Support for XML configuration operations,Support compression, backup, porting MySQL, SQL Server operation,Support transaction operations.

?? ?? ??Support for ORM operation,Customize the PQL syntax for quick queries,Support dynamic query,Secure thread protection mechanism,Support native operation,Support for XML configuration operations,Support compression, backup, porting MySQL, SQL Server operation,Support transaction operations.

null 60 Dec 12, 2022
Safe and easy wrappers for common Firebase Realtime Database functions.

FirebaseHelper FirebaseHelper is a small wrapper over Firebase's realtime database, providing streamlined methods for get, set, delete, and increment

Quan Vo 15 Apr 9, 2022
Prephirences is a Swift library that provides useful protocols and convenience methods to manage application preferences, configurations and app-state. UserDefaults

Prephirences - Preϕrences Prephirences is a Swift library that provides useful protocols and convenience methods to manage application preferences, co

Eric Marchand 557 Nov 22, 2022
Views as functions of their state.

Few.swift React-inspired library for writing AppKit/UIKit UIs which are functions of their state.1 SwiftBox is used for layout. Why UIs are big, messy

Josh Abernathy 1.1k Dec 24, 2022
PJAlertView - This library is to make your own custom alert views to match your apps look and feel

PJAlertView - This library is to make your own custom alert views to match your apps look and feel

prajeet 6 Nov 10, 2017
📕A single value proxy for NSUserDefaults, with clean API.

OneStore A single value proxy for NSUserDefaults, with clean API. With OneStore… Create one proxy(an OneStore object) for each NSUserDefaults value. M

Muukii 27 May 12, 2022
CleanArchitecture - Helping project to learn Clean Architecture using iOS (Swift)

Clean Architecture Helping project to learn Clean Architecture using iOS (Swift)

Alliston 2 Dec 5, 2022
Sample implementation of HDMI Clean out for iPhone or iPad video output.

HDMIPassThroughSample Sample implementation of HDMI Clean out for iPhone or iPad video output. "Lightning - Digital AV Adapter" can be used to output

Hattori Satoshi 4 Nov 1, 2022
🇨🇳 Learn how to make WeChat with SwiftUI. 微信 7.0 🟢

Overview Features Screenshots TODO Requirements License 中文 Overview I will continue to follow the development of technology, the goal is to bring Swif

Gesen 937 Dec 20, 2022
Disk is a powerful and simple file management library built with Apple's iOS Data Storage Guidelines in mind

Disk is a powerful and simple file management library built with Apple's iOS Data Storage Guidelines in mind

Saoud Rizwan 3k Jan 3, 2023
🛶Shallows is a generic abstraction layer over lightweight data storage and persistence.

Shallows Shallows is a generic abstraction layer over lightweight data storage and persistence. It provides a Storage<Key, Value> type, instances of w

Oleg Dreyman 620 Dec 3, 2022
This project has been developed to understand GraphQL and Diffable Data Source. Created on 20.06.2022.

SpaceX Launches First in first. You need to build all packages before building the project. Packages: Extensions API Open Extensions folder under proj

Okan Yücel 3 Sep 29, 2022