• Stars
    star
    432
  • Rank 100,650 (Top 2 %)
  • Language
    Swift
  • License
    MIT License
  • Created over 5 years ago
  • Updated over 3 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A declarative type-safe framework for building fast and flexible lists with UITableViews & UICollectionViews

SwiftDate

Owl offers a data-driven declarative approach for building fast & flexible list in iOS.

It supports both UICollectionView & UITableView; UIStackView is on the way!

Features Highlights
๐Ÿ•บ No more delegates & datasource. Just a fully type-safe declarative content approach
๐Ÿงฉ Better architecture to reuse components e decuple data from UI
๐ŸŒˆ Animate content changes automatically, no reloadData/performBatchUpdates
๐Ÿš€ Blazing fast diff algorithm based upon DifferenceKit
๐Ÿงฌ It uses standard UIKit components at its core. No magic!
๐Ÿ’Ž (COMING SOON) Support for scrollable declarative/fully customizable stack view
๐Ÿฆ Fully made in Swift from Swift โฅ lovers

Owl was created and maintaned by Daniele Margutti - My home site www.danielemargutti.com.

Version License Platform CI Status Carthage compatible Twitter Follow

Requirements

  • Xcode 9.0+
  • iOS 10.0+
  • Swift 5+

Installation

The preferred installation method is with CocoaPods. Add the following to your Podfile:

pod 'OwlKit'

Also you can install Owl using Carthage. Add this to your Cartfile:

github "malcommac/Owl"

Owl is also compatible with SPM (Swift Package Manager) for Swift 5.x+

What you will get

This is how to achieve a fully functional Contacts list with Owl. It's just a silly example but you can create complex layout with heterogeneous models easily!

director = TableDirector(table: table)

// An adapter encapsulate the logic to render a model (Contact) with a specific ui (ContactCell).
let contactAdapter = TableCellAdapter<Contact,ContactCell> { dr in

	// Each adapter exposes all the standard methods of a list, events
	// and properties. ctx (context) received from event is a type-safe
	// object both for model and cell!
	dr.events.dequeue = { ctx in
		// element is type-safe, it's the Contact instance!
		// cell is type-safe, it's the ContactCell instance
		ctx.cell?.item = ctx.element
	}
	dr.events.didSelect = { ctx in
		let vc = ContactDetailVC(people: ctx.element)
		navigationController.pushViewController(vc, animated: true)
	}
	dr.events.shouldHighlight = { ctx in
		return ctx.element.isBlacklisted == false
	}
}
// Since now our table can show Contact istances using ContactCell
// All events are received only in its adapter.
director?.registerCellAdapter(contactAdapter) 

/// Manage your content in a declarative way!
let friendsSection = TableSection(elements: [john,mark,anita])
director?.add(section: friendsSection)
director?.reload()

License

  • Owl is released under the Apache 2.0 License.
  • DifferenceKit is released under the Apache 2.0 License.
  • Owl Icon is made by Freepik from www.flaticon.com is licensed by CC 3.0 BY

Full License File.

Documentation

1.0 - Introduction: Director & Adapters

All the Owl's SDK is based upon two concepts: the director and the adapter.

Director

The director is the class which manage the content of a list, keep in sync data with UI and offers all the methods and properties to manage it. When you need to add, move or remove a section or a cell, change the header or a footer you find all the methods and properties in this class. A director instance can be associated with only one list (table or collection); once a director is assigned to a list it become the datasource and delegate of the object.

The following directors are available:

  • TableDirector used to manage UITableView instances
  • CollectionDirector and FlowCollectionDirector used to manage UICollectionView with custom or UICollectionViewFlowLayout layout.

Adapters

Once you have created a new director for a list it's time to declare what kind of models your list can accept. Each model is assigned to one UI element (UITableViewCell subclass for tables, UICollectionViewCell subclass for collections).

The scope of the adapter is to declare a pair of Model & UI a director can manage.

The entire framework is based to this concept: a model can be rendered by a single UI elements and its up to Owl to pick the right UI for a particular model instance.

An adapter is also the centrail point to receive events where a particular instance of a model is involved in. For example: when an user tap on a row for a model instance of type A, you will receive the event (along with the relevant info: index path, involved model instance etc.) inside the adapter which manage that model.

You will register as much adapters as models you have.

2.0 - Getting Started

The following code shows how to create a director to manage an UITableView (a much similar approach is used for UICollectionView).

public class MyController: UIViewController {
	@IBOutlet public var table: UITableView!
	
	private var director: TableDirector?
	
	func viewDidLoad() {
		super.viewDidLoad()
		director = TableDirector(table: table)
// ...

It's now time to declare what kind of content should manage our director. For shake of simplicity we'll declare just an adapter but you can declare how much adapters you need (you can also create your own director with adapters inside and reuse them as you need. This is a neat approach to reuse and decupling data).

func viewDidLoad() {
	// ...
	let contactAdpt = TableCellAdapter<Contact, ContactCell>()
	
	// You can attach events for adapter configuration.
	// The following example show how to set the row height and dequeue
	
	contactAdpt.events.rowHeight = { ctx in
		return 60.0 // explicit row height
	}
	
	contactAdpt.events.dequeue = { ctx in
		// this is the suggested behaviour; your cell should expose a
		// property of the type of the model it can be render and you will
		// assign it on dequeue. It's type safe too!!
		ctx.cell?.contact = ctx.element
	}
	director?.registerCellAdapter(contactAdpt)

This is minimum setup to render objects of type Contact using cell of type ContactCell using our director. You can configure and attach tons of other properties and events (using the adapter's .events property); you will found a more detailed description of each property below but all UITableView/UICollectionView's events and properties are supported.

Now it's time to add some content to our table. As we said Owl uses a declarative approach to content: this mean you set the content of a list by using imperative functions like add,remove,move both for sections and elements.

// You can add/remove/move elements at anytime in a declarative way
let contacts = [
	Contact(first: "John", last: "Doe"),
	Contact(first: "Adam", last: "Best"),
	...
]
// ...
director?.add(elements: contacts)
director?.remove(section: 1)

There are a plenty list of methods to manage both sections and elements into section:

director?.firstSection?.removeAt(2) // remove an element
director?.remove(section: 2) // remove section
director?.section("mySection").move(swappingAt: 0, with:1) // swap elements
director?.add(elements: [...], at: 5) // add new elements
// and more...

Once you have changed your data models you can call reload() function to sync it with UI.

The following code create (automatically) a new TableSection with the contacts elements inside. If you need you can create a new section manually:

// Create a new section explicitly. Each section has an unique id you can assign
// explicitly or leave Owl create an UUID for you. It's used for diff features.
let newSection = TableSection(id: "mySectionId", elements: contacts, header: "\(contacts) Contacts", footer: nil)
director?.add(section: newSection)

In order to sync the UI just call the director?.reload(), et voilร ! Just few lines of code to create a fully functional list!

At anytime you are able to add new sections, move or replace items just by using the methods inside the TableSection/CollectionSection or TableDirector/CollectionDirector instances then calling reload() to refresh the content.

Refresh is made without animation, but Owl is also able to refresh the content of your data by picking the best animation based on a blazing fast diff algorithm which compare the data before/after your changes. More details in the next chapter.

The following code demostrate how to add swipe to delete functionality to an adapter:

catalogAdapter.events.canEditRow = { ctx in
  return true
}
        
catalogAdapter.events.deleteConfirmTitle = { ctx in
  return "Delete"
}
        
catalogAdapter.events.commitEdit = { [weak self] ctx, style in
  guard let indexPath = ctx.indexPath else { return }
  
  // By using the session to reload you will also receive events like didEndEditingCell
  self?.tableDirector?.reload(afterUpdate: { dir in
    dir.sections[indexPath.section].remove(at: indexPath.row)
    return .none
  }, completion: nil)
}

โ†‘ Back To Top

3.0 - How-To

3.1 - Create Model

Models can be struct or classes; the only requirement is the conformance to ElementRepresentable protocol. This protocol is used by the diff algorithm in order to evaluate the difference between changes applied to models and pick the best animation to show it.

This means you don't need to make any performBatches update neither to call reloadRows/removeRows/addRows methods manually... all these boring and crash-proned stuff are made automatically by Owl.

The following example show our Contact model declaration:

public class Contact: ElementRepresentable {
    let firstName: String
    let lastName: String

	public var differenceIdentifier: String {
		return "\(self.firstName)_\(self.lastName)"
	}

	public func isContentEqual(to other: Differentiable) -> Bool {
		guard let other = other as? Contact else { return false }
		return other == self
	}

	init(first: String, last: String) {
		self.firstName = first
		self.lastName = last
	}
}

Protocol conformance is made by adding:

  • differenceIdentifier property: An model needs to be uniquely identified to tell if there have been any insertions or deletions (it's the perfect place for a uuid property)
  • isContentEqual(to:) function: is used to check if any properties have changed, this is for replacement changes. If your model data change between reloads Owl updates the cell's UI accordingly.

โ†‘ Back To Top

3.2 - Create the Cell

The second step is to create an UI representation of your model. Typically is a subclass of UITableViewCell or UICollectionViewCell.

Reuse Identifier

Cells must have as reuseIdentifier value the same name of the class itself (so ContactCell has also ContactCell as identifier; you can also configure it if you need but it's a good practice).

Best Practices

You don't need to conform any special protocol but, in order to keep your code clean, our suggestion is to create a public property which accepts the model instance and set it on adapter's dequeue event.

public class ContactCell: UITableViewCell {

	// Your outlets
    @IBOutlet public var ...

	// Define a property you set on adapter's dequeue event
	public var contact: Contact? {
		didSet {
           // setup your UI according with instance data
		}
	}
}

In your adapter:

contactAdpt.events.dequeue = { ctx in
	ctx.cell?.contact = ctx.element
}

ctx is an object which includes all the necessary informations of an event, including type-safe instance of the model.

โ†‘ Back To Top

3.3 - Manage Self-Sized Cells

Self-sized cells are easy to be configured, both for tables and collection views.

Owl support easy cell sizing using autolayout. You can set the size of the cell by adapter or collection/table based. For autolayout driven cell sizing set the rowHeight (for TableDirector) or itemSize (for CollectionDirector/FlowCollectionDirector) to the autoLayout value, then provide an estimated value.

Accepted values are:

  • default: you must provide the height (table) or size (collection) of the cell
  • auto(estimated: CGFloat): uses autolayout to evaluate the height of the cell; for Collection Views you can also provide your own calculation by overriding preferredLayoutAttributesFitting() function in cell instance.
  • explicit(CGFloat): provide a fixed height for all cell types (faster if you plan to have all cell sized same)

โ†‘ Back To Top

3.4 - Load Cells/View from Storyboard/Xib/Class

This is the behaviour used to load/dequeue cells and header/footer both for tables and collection views is:

Entity Default Reusable Identifier Default Loading Method
Cells (UITableViewCell,UICollectionViewCell) Name of the class Load the UI with the reusable identifier from the parent table's storyboard cell prototypes list
Header/Footer View (UITableViewHeaderFooterView, UICollectionReusableView) Name of the class Attempt to load the view as the root element of the xib with the same name of the class (ie. "GroupHeaderView.xib") contained in the same bundle of the class itself.

You can however this behaviour by setting the following attributes before the adapter (TableAdapter or TableHeaderFooterAdapter for table's cells and header/footer, CollectionAdapter/CollectionHeaderFooterAdapter for collections's cell and header/footer):

  • reusableViewIdentifier: set a new reusable identifier to use.
  • reusableViewLoadSource: change the source where the view is dequeued (for cells the default value is .fromStoryboard, for header/footer is .fromXib(name:nil, bundle: nil)). Allowed values are:

By default cells or header/footer will use the name of the class itself as reusableIdentifier (for example, your cell ContactCell must have the identifier set to ContactCell). This is an useful convention to avoid strange identifiers.

However you can still override this behaviour by setting cellReuseIdentifier (in TableAdapter or CollectionAdapter for cells) and viewReuseIdentifier (in TableHeaderFooterAdapter/CollectionHeaderFooterAdapter for header/footer).

Another interesting property is reusableViewLoadSource; this specify where Owl must search the UI of the cell/view you are about to load. Allowed values are:

  • fromStoryboard: load from storyboard inside the table/collection's prototypes list (default value for cells).
  • fromXib(name: String?, bundle: Bundle?): load from a specific xib file in a bundle (if name is nil it uses the same filename of the cell class, ie ContactCell.xib; if bundle is nil it uses the same bundle of the class.
  • fromClass: loading from class.

The following example load a cell from an external xib with the same name of the class (ContactCell.xib) and contained in the same bundle of the cell class itself. The same approach is valid for header/footer.

let contactAdpt = TableCellAdapter<Contact, ContactCell>()
// instead of load cell from storyboard it will be loaded by
// reading the root view inside the xib with the same name of the class
contactAdpt.reusableViewLoadSource = .fromXib(name:nil, bundle:nil)
// optionally you can also set a custom id
contactAdpt.reusableViewIdentifier = "CustomContactCellID"
// configure...
director?.registerCellAdapter(contactAdpt)

โ†‘ Back To Top

3.5 - Custom Section's Header/Footer

Sections (both TableSection and CollectionSection) can be configured to have both simple (String) or complex (custom views) header/footer. Custom Header/Footer are configured in the same way you have made for cells, by using the concept of adapter.

To create a simple string-based header/footer just pass its value into the init of section object:

let newTableSection = TableSection(id: "sectionId", elements: contacts, header: "\(contacts.count) Contacts", footer: nil)

The following code create a section with a string header which contains the number of contacts inside that section.

To create a view-based header/footer you must register an adapter using:

  • TableHeaderFooterAdapter<View> class for table (where View is a subclass of UITableViewHeaderFooterView )
  • CollectionHeaderFooterAdapter<View> for collections (where View is a subclass of UICollectionReusableView)

In this case adapter keeps only the reference of the header/footer view class type (no explicit models are involved).

The following example create a view-based header for a collection and configure it with some custom data:

// In our case our custom view is a class EmojiHeaderView subclass of UICollectionReusableView 
let emojiHeader = CollectionHeaderFooterAdapter<EmojiHeaderView> { ctx in
	ctx.events.dequeue = { ctx in // register for view dequeue events to setup some data
		ctx.view?.titleLabel.text = ctx.section?.identifier ?? "-"
	}
}
// Register adapter for header/footer
director?.registerHeaderFooterAdapter(headerAdapter)

Once you have registered your header/footer you can assign it to the init of any section:

let newSection = CollectionSection(id: "Section \(idx)" ,elements: elements, header: emojiHeader, footer: nil)

in the ctx parameter you will receive the section which generates the event.

โ†‘ Back To Top

3.6 - Reload data with automatic animations

Owl supports automatic animated reload of the data between changes. With this feature you don't need to think about calling (even in the correct order) reloadRows/deleteRows/removeRows methods when changing your data source: just perform changes to your model in a callback and, at the end, Owl will generate the corrrect sequence of the animations.

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

The following example:

director?.reload(afterUpdate: { dir in
   dir.firstSection()!.elements.shuffled() // shuffle some items
   dir.firstSection()!.remove(at: 4) // remove item at index 4
   // ... do any other stuff with sections or elements in section
}, completion: nil)

At the end of the call Owl will compare data models before/after and produce a changeset of animations to execute,

Diffing algorithm is based upon the DifferenceKit framework, an O(n) difference algorithm optimized for performance in Swift. It supports all operations for animated UI batch-updates including section reloads.

Compared to IGListKit it allows:

Supported collection

Linear Sectioned Duplicate Element/Section
Owl โœ… โœ… โœ…
RxDataSources โŒ โœ… โŒ
IGListKit โœ… โŒ โœ…

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

Supported element differences

Delete Insert Move Reload Move across sections
Owl โœ… โœ… โœ… โœ… โœ…
RxDataSources โœ… โœ… โœ… โœ… โœ…
IGListKit โœ… โœ… โœ… โœ… โŒ

Supported section differences

Delete Insert Move Reload
Owl โœ… โœ… โœ… โœ…
RxDataSources โœ… โœ… โœ… โŒ
IGListKit โŒ โŒ โŒ โŒ

Moreover it way faster than IGListKit and RxDataSources counterparts!

โ†‘ Back To Top

3.7 - Working with Headers & Footers

Headers and Footers in Owl are managed in the same way already used for Cells, using Adapter; however due to its nature (instead of cells they are not strictly correlated to a specific model class) you will create an adapter which links just the view used to render it (subclass of UITableViewHeaderFooterView for UITableView and subclass of UICollectionReusableView for UICollectionView).

A. Creating Header/Footer for UITableView

Using header/footer in your UITableView via storyboard is not easy; the most affordable way is to create a xib file with the same name of your UITableViewHeaderFooterView subclass.

Suppose you want to make an header/footer class called GroupHeaderView for your Contacts table. You will need to create:

GroupHeaderView.xib file which contains the UI of the class (you can still customize how the view is loaded by overriding the ReusableCellViewProtocol protocol in your GroupHeaderView.swift implementation. By default it expect a separate xib file with UI but you can also load it directly from class or provide a different filename). This xib file contains a single GroupHeaderView view subclass.

GroupHeaderView .swift file with your class implementation. In a standard configuration you don't need to make anything special; just subclass UITableViewHeaderFooterView:

import UIKit

// Nothing special is required to support header/footer class in Owl.
// This is just a simple class.

public class GroupHeaderView: UITableViewHeaderFooterView {
	@IBOutlet public var headerTitleLabel: UILabel?
	@IBOutlet public var headerSubtitleLabel: UILabel?
	...
}

The next step is to register your header/footer adapter:

let groupHeaderAdapter = TableHeaderFooterAdapter<GroupHeaderView> { config in

  // Configure content of the header/footer
  config.events.dequeue = { ctx in
    ctx.view?.headerTitleLabel?.text = "..."
    ctx.view?.headerSubtitleLabel?.text = "Section #\(ctx.section)"
  }
   
  // Configure height of the header/footer
  config.events.height = { _ in
    return 30
  }
}

At this point you can assign this adapter to any TableSection instance just by using:

let newSection = TableSection(elements: [...], headerView: groupHeaderAdapter, footerView: nil)
director?.add(section: section)
director?.reload()

That's all! You can reuse groupHeaderAdapter in more than a section without problem.

B. Creating Header/Footer for UICollectionView

Creating header/footer for collection views is pretty similar to the approach already used for table; the only big difference is you can use header/footer specified in collectionview (as like for cells) when you are using storyboards.

First of all you need to make your class of the header/footer. The following example create a custom header which is a EmojiHeaderView.swift:

import UIKit

public class EmojiHeaderView: UICollectionReusableView {
    @IBOutlet public var titleLabel: UILabel!
    // ...
}

As for table's header/footer view you don't need to make anything special for your subclass.

The next step is to enable header/footer for your collection view and set the newly generated view as subclass of your view class.

You are now ready to create the adapter to hold your header/footer:

let headerAdapter = CollectionHeaderFooterAdapter<EmojiHeaderView> { cfg in
  // dequeue
  cfg.events.dequeue = { ctx in
    ctx.view?.titleLabel.text = ctx.section?.identifier ?? "-"
  }
}

director?.registerHeaderFooterAdapter(headerAdapter)

You can now assign your header/footer adapter to one or more section instances:

let section = CollectionSection(id: "Section \(idx)", elements: rawSection)
section.headerView = headerAdapter
section.headerSize = CGSize(width: self.collection.frame.size.width, height: 30)

More Repositories

1

SwiftDate

๐Ÿ” Toolkit to parse, validate, manipulate, compare and display dates, time & timezones in Swift.
Swift
7,603
star
2

SwiftLocation

โš“๏ธ Async/Await CLLocationManager Wrapper for Apple Platforms
Swift
3,386
star
3

SwiftRichString

๐Ÿ‘ฉโ€๐ŸŽจ Elegant Attributed String composition in Swift sauce
Swift
3,097
star
4

Hydra

โšก๏ธ Lightweight full-featured Promises, Async & Await Library in Swift
Swift
1,973
star
5

Repeat

๐Ÿ•ฆ Modern Timer in Swift, Debouncer and Throttler (alternative to NSTimer) made with GCD
Swift
1,466
star
6

UIWindowTransitions

๐Ÿƒโ€โ™‚๏ธ Animated transitions for UIWindow's rootViewController property
Swift
472
star
7

ImageSizeFetcher

Finds the type/size of an image given its URL by fetching as little data as needed
Swift
440
star
8

ScrollStackController

๐Ÿงฉ Easy scrollable layouts in UIKit - an UIStackView which scroll and uses root views of child view controllers.
Swift
435
star
9

Flow

Declarative approach to populate and manage UITableViews (see https://github.com/malcommac/FlowKit)
Swift
423
star
10

SwiftSimplify

๐Ÿฅท High-performance polyline simplification library - port of simplify.js
Swift
299
star
11

DMLazyScrollView

Lazy Loading UIScrollView for iOS (with support for infinite scrolling)
Objective-C
293
star
12

DMPagerViewController

DMPagerViewController is page navigation controller like the one used in Twitter or Tinder
Objective-C
278
star
13

FlowKit

A declarative type-safe framework for building fast and flexible list with Tables & Collection
Swift
211
star
14

DMCircularScrollView

Infinite/Circular Scrolling Implementation for UIScrollView
Objective-C
205
star
15

ScrollingStackContainer

Efficient Scrolling UIStackView in Swift - DEPRECATED (use ScrollStackController)
Swift
198
star
16

SwiftScanner

String Scanner in pure Swift (supports unicode)
Swift
171
star
17

SwiftMsgPack

๐Ÿ’ฌ Fast & Lightweight MsgPack Serializer & Deserializer for Swift
Swift
154
star
18

DMSlidingCell

Swipe To Reveal UITableViewCell Implementation as like in Twitter
Objective-C
152
star
19

DMSplitView

New NSSplitView class with multiple subviews resize behaviors and animated transitions
Objective-C
116
star
20

DMScrollingTicker

Advanced horizontal scrolling ticker for iOS
Objective-C
110
star
21

DMDynamicWaterfall

UICollectionView Waterfall Layout with UIKit Dynamics
Objective-C
109
star
22

DMTabBar

XCode 4.x like inspector segmented control
Objective-C
108
star
23

HermesNetwork

Swift
72
star
24

DMInspectorPalette

Animated NSScrollView with collapsible sections like in XCode Inspector
Objective-C
61
star
25

SwiftUnistroke

โœ๏ธ $1 Unistroke Gesture Recognizer in Swift
Swift
53
star
26

UAParserSwift

๐Ÿ—บ User-Agent Parser based upon ua-parser.js
Swift
40
star
27

DMPageControl

An high customizable alternative to UIPageControl
39
star
28

DMTwitterOAuth

Twitter OAuth Library (Callback URL Login)
Objective-C
37
star
29

DMScrollViewStack

DMScrollViewStack is a UIScrollView subclass that efficiently handles a vertical stack of multiple scrollviews
Objective-C
33
star
30

DMLocationManager

Location Manager for iOS made simple
Objective-C
27
star
31

DMPathBar

XCode's Yosemite like path bar of OS X
Objective-C
25
star
32

Swift-Coding-Guidelines

A repository to collect best practices when programming with Swift
21
star
33

xcbetarunner

Run & Debug projects [easily] with stable Xcode on beta iOSย devices
Swift
17
star
34

GitLabSwift

๐Ÿ›ต Async/Await GitLab REST APIs v4 for Swift
Swift
17
star
35

DMCacheBox

Fast advanced caching system for Objective-C (Cocoa/iOS compatible)
Objective-C
14
star
36

RealEventsBus

๐ŸšŽ Simple type-safe event bus implementation in swift
Swift
13
star
37

DMMultiDelegatesProxy

Multiple delegate architecture made using NSProxy
Objective-C
10
star
38

Glider-Old

โœˆ๏ธ Fast & powerful logger for Swift
Swift
9
star
39

NSSplitView-Animatable

NSSplitView Category to perform simple animations with dividers
Objective-C
9
star
40

SlidingSheet

Configurable Bottom Sheet for UIKit - like AirBnb and Apple Maps
Swift
8
star
41

CircularScroller

Efficient and Lightweight endless UIScrollView implementation in Swift
Swift
7
star
42

DMAPNServer

APN - Apple Push Notification Server in PHP
PHP
6
star
43

DMCoreDataUtils

Utilities methods for Apple's Core Data Storage
Objective-C
6
star
44

sentry-cocoa-sdk-xcframeworks

A mirror for https://github.com/getsentry/sentry-cocoa to add support for binary (xcframework) distribution with swift package manager.
Shell
4
star
45

CAKeyFrameAnimation-Evaluators

Easily specify CAKeyFrameAnimation curves using C functions or ObjC Class
Objective-C
3
star
46

ZoomableListController

Apple's iOS Weather App List Imitation written in Swift
Swift
2
star
47

MeetingBot

Your next call on Mac status bar
Swift
2
star
48

Konsole

In-app Swift tools for great iOS developers
1
star
49

DMGrid

A simple 2D Grid class made in Objective-C
Objective-C
1
star
50

Envy

Type safe environment variables from shell processes and .env files
1
star