From 30b381c7f6637ce701ca0c15f5368e423729be8b Mon Sep 17 00:00:00 2001
From: Vinnie Hesener
Date: Wed, 26 Jul 2017 19:15:30 -0500
Subject: [PATCH] Initial 0.1.2 release (#17)
* Initial commit for beta release
---
LICENSE | 17 +
README.md | 223 ++-
Supporting/jazzy/abstracts/Controls.md | 116 ++
.../jazzy/abstracts/Gesture Recognizers.md | 57 +
Supporting/jazzy/abstracts/KVO.md | 36 +
.../jazzy/abstracts/UICollectionView.md | 52 +
.../abstracts/UIImagePickerController.md | 33 +
Supporting/jazzy/abstracts/UIPickerView.md | 81 ++
Supporting/jazzy/abstracts/UITableView.md | 74 +
Supporting/jazzy/jazzy.yml | 50 +
Xcode/Closures.xcodeproj/project.pbxproj | 510 +++++++
.../contents.xcworkspacedata | 7 +
.../xcshareddata/IDETemplateMacros.plist | 27 +
.../contents.xcworkspacedata | 10 +
.../xcshareddata/WorkspaceSettings.xcsettings | 8 +
Xcode/Closures/Closures.h | 7 +
Xcode/Closures/Info.plist | 24 +
Xcode/Closures/Source/Core.swift | 164 +++
Xcode/Closures/Source/KVO.swift | 97 ++
Xcode/Closures/Source/UICollectionView.swift | 788 ++++++++++
Xcode/Closures/Source/UIControl.swift | 672 +++++++++
.../Closures/Source/UIGestureRecognizer.swift | 502 +++++++
.../Source/UIImagePickerController.swift | 367 +++++
Xcode/Closures/Source/UIPickerView.swift | 555 +++++++
Xcode/Closures/Source/UIScrollView.swift | 331 +++++
Xcode/Closures/Source/UITableView.swift | 1296 +++++++++++++++++
Xcode/ClosuresTests/Info.plist | 22 +
Xcode/ClosuresTests/KVOTests.swift | 53 +
.../ClosuresTests/UICollectionViewTests.swift | 198 +++
Xcode/ClosuresTests/UIControlTests.swift | 138 ++
.../UIGestureRecognizerTests.swift | 72 +
.../UIImagePickerControllerTests.swift | 50 +
Xcode/ClosuresTests/UIPickerViewTests.swift | 50 +
Xcode/ClosuresTests/UITableViewTests.swift | 291 ++++
.../Pages/KVO.xcplaygroundpage/Contents.swift | 44 +
.../Contents.swift | 75 +
.../CollectionViewDemoViewController.xib | 54 +
.../Sources/Setup.swift | 25 +
.../Contents.swift | 107 ++
.../Resources/ControlsDemoViewController.xib | 145 ++
.../Sources/Setup.swift | 58 +
.../Contents.swift | 58 +
.../Resources/GesturesDemoViewController.xib | 55 +
.../Sources/Setup.swift | 16 +
.../Contents.swift | 44 +
.../Contents.swift | 103 ++
.../Resources/PickerDemoViewController.xib | 66 +
.../Sources/Setup.swift | 22 +
.../Contents.swift | 97 ++
.../Resources/TableViewDemoViewController.xib | 48 +
.../Sources/Setup.swift | 10 +
.../Sources/Setup.swift | 41 +
.../contents.xcplayground | 12 +
.../Contents/Resources/Documents/index.html | 8 +-
docs/docsets/Closures.tgz | Bin 112328 -> 112197 bytes
docs/index.html | 8 +-
docs/undocumented.json | 10 +-
57 files changed, 8068 insertions(+), 16 deletions(-)
create mode 100644 LICENSE
create mode 100644 Supporting/jazzy/abstracts/Controls.md
create mode 100644 Supporting/jazzy/abstracts/Gesture Recognizers.md
create mode 100644 Supporting/jazzy/abstracts/KVO.md
create mode 100644 Supporting/jazzy/abstracts/UICollectionView.md
create mode 100644 Supporting/jazzy/abstracts/UIImagePickerController.md
create mode 100644 Supporting/jazzy/abstracts/UIPickerView.md
create mode 100644 Supporting/jazzy/abstracts/UITableView.md
create mode 100644 Supporting/jazzy/jazzy.yml
create mode 100644 Xcode/Closures.xcodeproj/project.pbxproj
create mode 100644 Xcode/Closures.xcodeproj/project.xcworkspace/contents.xcworkspacedata
create mode 100644 Xcode/Closures.xcodeproj/xcshareddata/IDETemplateMacros.plist
create mode 100644 Xcode/Closures.xcworkspace/contents.xcworkspacedata
create mode 100644 Xcode/Closures.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
create mode 100644 Xcode/Closures/Closures.h
create mode 100644 Xcode/Closures/Info.plist
create mode 100644 Xcode/Closures/Source/Core.swift
create mode 100644 Xcode/Closures/Source/KVO.swift
create mode 100644 Xcode/Closures/Source/UICollectionView.swift
create mode 100644 Xcode/Closures/Source/UIControl.swift
create mode 100644 Xcode/Closures/Source/UIGestureRecognizer.swift
create mode 100644 Xcode/Closures/Source/UIImagePickerController.swift
create mode 100644 Xcode/Closures/Source/UIPickerView.swift
create mode 100644 Xcode/Closures/Source/UIScrollView.swift
create mode 100644 Xcode/Closures/Source/UITableView.swift
create mode 100644 Xcode/ClosuresTests/Info.plist
create mode 100644 Xcode/ClosuresTests/KVOTests.swift
create mode 100644 Xcode/ClosuresTests/UICollectionViewTests.swift
create mode 100644 Xcode/ClosuresTests/UIControlTests.swift
create mode 100644 Xcode/ClosuresTests/UIGestureRecognizerTests.swift
create mode 100644 Xcode/ClosuresTests/UIImagePickerControllerTests.swift
create mode 100644 Xcode/ClosuresTests/UIPickerViewTests.swift
create mode 100644 Xcode/ClosuresTests/UITableViewTests.swift
create mode 100644 Xcode/Playground/ClosuresDemo.playground/Pages/KVO.xcplaygroundpage/Contents.swift
create mode 100644 Xcode/Playground/ClosuresDemo.playground/Pages/UICollectionView.xcplaygroundpage/Contents.swift
create mode 100644 Xcode/Playground/ClosuresDemo.playground/Pages/UICollectionView.xcplaygroundpage/Resources/CollectionViewDemoViewController.xib
create mode 100644 Xcode/Playground/ClosuresDemo.playground/Pages/UICollectionView.xcplaygroundpage/Sources/Setup.swift
create mode 100644 Xcode/Playground/ClosuresDemo.playground/Pages/UIControls.xcplaygroundpage/Contents.swift
create mode 100644 Xcode/Playground/ClosuresDemo.playground/Pages/UIControls.xcplaygroundpage/Resources/ControlsDemoViewController.xib
create mode 100644 Xcode/Playground/ClosuresDemo.playground/Pages/UIControls.xcplaygroundpage/Sources/Setup.swift
create mode 100644 Xcode/Playground/ClosuresDemo.playground/Pages/UIGestureRecognizer.xcplaygroundpage/Contents.swift
create mode 100644 Xcode/Playground/ClosuresDemo.playground/Pages/UIGestureRecognizer.xcplaygroundpage/Resources/GesturesDemoViewController.xib
create mode 100644 Xcode/Playground/ClosuresDemo.playground/Pages/UIGestureRecognizer.xcplaygroundpage/Sources/Setup.swift
create mode 100644 Xcode/Playground/ClosuresDemo.playground/Pages/UIImagePickerController.xcplaygroundpage/Contents.swift
create mode 100644 Xcode/Playground/ClosuresDemo.playground/Pages/UIPickerView.xcplaygroundpage/Contents.swift
create mode 100644 Xcode/Playground/ClosuresDemo.playground/Pages/UIPickerView.xcplaygroundpage/Resources/PickerDemoViewController.xib
create mode 100644 Xcode/Playground/ClosuresDemo.playground/Pages/UIPickerView.xcplaygroundpage/Sources/Setup.swift
create mode 100644 Xcode/Playground/ClosuresDemo.playground/Pages/UITableView.xcplaygroundpage/Contents.swift
create mode 100644 Xcode/Playground/ClosuresDemo.playground/Pages/UITableView.xcplaygroundpage/Resources/TableViewDemoViewController.xib
create mode 100644 Xcode/Playground/ClosuresDemo.playground/Pages/UITableView.xcplaygroundpage/Sources/Setup.swift
create mode 100644 Xcode/Playground/ClosuresDemo.playground/Sources/Setup.swift
create mode 100644 Xcode/Playground/ClosuresDemo.playground/contents.xcplayground
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..bc4b2d5
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,17 @@
+The MIT License (MIT)
+Copyright (c) 2017 Vincent Hesener
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+associated documentation files (the "Software"), to deal in the Software without restriction,
+including without limitation the rights to use, copy, modify, merge, publish, distribute,
+sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or
+substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/README.md b/README.md
index 713d5db..c5eceb9 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,222 @@
-# Closures
+![Closures logo](https://raw.githubusercontent.com/vhesener/Closures/assets/assets/logo3.1.png)
+
+[![Language](https://img.shields.io/badge/Swift-4.0-blue.svg?style=plastic&colorB=68B7EB)]()
+[![License](https://img.shields.io/github/license/vhesener/Closures.svg?style=plastic&colorB=68B7EB)]()
+[![Release](https://img.shields.io/github/release/vhesener/Closures.svg?style=plastic&colorB=68B7EB)]()
+
+`Closures`beta is an iOS Framework that adds [closure](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html) handlers to many of the popular UIKit and Foundation classes. Although this framework is a substitute for some Cocoa Touch design patterns, such as [Delegation & Data Sources](https://developer.apple.com/library/content/documentation/General/Conceptual/DevPedia-CocoaCore/Delegation.html) and [Target-Action](https://developer.apple.com/library/content/documentation/General/Conceptual/Devpedia-CocoaApp/TargetAction.html), the authors make no claim regarding which is a *better* way to accomplish the same type of task. Most of the time it is a matter of style, preference, or convenience that will determine if any of these closure extensions are beneficial.
+
+Whether you're a functional purist, dislike a particular API, or simply just want to organize your code a little bit, you might enjoy using this library.
+
+> ***note***
+> `Closures`beta currently only supports projects written in **Swift 4.0**+.
+
+***
+## [Usage Overview](#usage-overview)
+
+### **Convenient Closures**
+
+Some days, you just feel like dealing with [UIControl](https://vhesener.github.io/Closures/Controls.html)'s target-action using a closure instead.
+
+```swift
+button.onTap {
+ // UIButton tapped code
+}
+```
+
+```swift
+mySwitch.onChange { isOn in
+ // UISwitch value changed code
+}
+```
+
+***
+
+Adding a [gesture recognizer](https://vhesener.github.io/Closures/Gesture%20Recognizers.html) can be compacted into one method.
+
+```swift
+view.addPanGesture() { pan in
+ // UIPanGesutreRecognizer recognized code
+}
+```
+
+***
+
+Populating views with an array? I gotchu.
+
+```swift
+tableView.addElements(myArray, cell: MyTableViewCell.self) { element, cell, index in
+ cell.textLabel!.text = "\(element)"
+}
+```
+
+```swift
+collectionView.addFlowElements(myArray, cell: MyCustomCollectionViewCell.self) { element, cell, index in
+ cell.myImageViewProperty.image = element.thumbImage
+}
+```
+
+```swift
+pickerView.addStrings(myStrings) { title, component, row in
+ // UIPickerView item selected code
+}
+```
+***
+### **Daisy Chaining**
+
+Almost all convenience methods allow for the use of [daisy chaining](https://en.wikipedia.org/wiki/Method_chaining). This allows us to have some nice syntax sugar while implementing optional delegate methods in a consise way. Using [UITextField](https://vhesener.github.io/Closures/Extensions/UITextField.html) as an example, we can organize and visualize all of the `UITextFieldDelegate` behavior.
+
+```swift
+textField
+ .didBeginEditing {
+ // UITextField did begin editing code
+ }.shouldClear {
+ true
+ }.shouldChangeCharacters { range, string in
+ // some custom character change code
+ return false
+}
+```
+***
+### **Retain Control**
+
+At no time are you locked into using these convenience methods. For instance, [UITableView](https://vhesener.github.io/Closures/Extensions/UITableView.html) does not need to be populated with an array. You can just as easily provide your own `UITableViewDelegate` and `UITableViewDataSource` handlers.
+
+```swift
+tableView.register(MyTableViewCell.self, forCellReuseIdentifier: "Cell")
+tableView
+ .numberOfRows { _ in
+ myArray.count
+ }.cellForRow { indexPath in
+ let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
+ cell.textLabel!.text = myArray[indexPath.row]
+ return cell
+ }.didSelectRowAt { indexPath in
+ // IndexPath selected code
+}
+```
+
+***
+
+You aren't limited to which delegate/dataSource methods you wish to implement. Similarly, you can act on any
+[UIControl](https://vhesener.github.io/Closures/Extensions/UIControl.html#/s:So9UIControlC8ClosuresE2onABXDSC0A6EventsV_yAB_So7UIEventCSgtc7handlertF) events.
+
+```swift
+anyControl.on(.touchDown) { control, event in
+ // UIControlEvents.touchDown event code
+}
+```
+
+***
+
+These two [UIImagePickerController](https://vhesener.github.io/Closures/Extensions/UIImagePickerController.html) snippets are equivalent. As you can see, there are lots of ways to provide more granular control by mixing and match various convenience methods and closure handlers.
+
+```swift
+UIImagePickerController(source: .camera, allow: .image) { result, picker in
+ myImageView.image = result.editedImage
+}.present(from: self)
+```
+```swift
+let pickerController = UIImagePickerController()
+pickerController.sourceType = .camera
+pickerController.mediaTypes = [kUTTypeImage]
+pickerController.didFinishPickingMedia { [weak self] info in
+ myImageView.image = info[UIImagePickerControllerEditedImage] as? UIImage
+ self?.dismiss(animated: true)
+}.didCancel { [weak self] in
+ self?.dismiss(animated: true)
+}
+self.present(pickerController, animated: true)
+```
+***
+## [Dive Deeper](#dive-deeper)
+
+There are several ways to learn more about the `Closures`beta API, depending on your learning style. Some just like to open up Xcode and use autocomplete to view the various properties/functions. Others prefer a more documented approach. Below are some documentation options.
+
+***
+### **Playground**
+
+To play with the Playground demo, open the `Closures` workspace (Closures.xcworkspace file), build the `Closures`beta framework target, then click on the `Closures Demo` playground. Be sure to show the Assistant Editor and Live View as shown below:
+
+![Playgrounds](https://raw.githubusercontent.com/vhesener/Closures/assets/assets/playground_general.gif)
+
+***
+### **Class Reference Documentation**
+
+The [Reference Documentation](https://vhesener.github.io/Closures) has all of the detailed usage information including all the public methods, parameters, and convenience initializers.
+
+[![Class Reference Documentation](https://raw.githubusercontent.com/vhesener/Closures/assets/assets/reference_large.png)](https://vhesener.github.io/Closures)
+
+***
+## [Installation](#installation)
+
+### **CocoaPods**
+
+If using [CocoaPods](https://cocoapods.org/), add the following to your Podfile:
+
+```ruby
+pod 'Closures'
+```
+
+### **Carthage**
+
+If using [Carthage](https://github.com/Carthage/Carthage), add the following to your Cartfile:
+
+```shell
+github "vhesener/Closures"
+```
+
+### **Manual**
+
+Download or clone the project files found in the [master branch](https://github.com/vhesener/Closures). Drag and drop
+all .swift files located in the 'Closures/Source' subdirectory into your Xcode project. Check the option *Copy items
+if needed*.
+
+***
+## [Background](#background)
+
+Inspired by [BlocksKit](https://github.com/BlocksKit/BlocksKit), there was a need for a more *Swifty* version
+of the same library. The goal of this library was to provide similar usefulness, but with the following
+constraints:
+
+* Use Swift's strong-typed system as much as possible in the API.
+* Not use the [Objective-C runtime](https://github.com/BlocksKit/BlocksKit/search?utf8=%E2%9C%93&q=objc_setAssociatedObject&type=).
+There are many reasons for this, but mostly because
+ * It was arbitrarily challenging.
+ * It was in the spirit of Swift.
+* Create a scalable mechanism to easily add addtional closure wrappers in the future.
+
+It is our goal to become irrelevant via [sherlock](http://www.urbandictionary.com/define.php?term=sherlocked).
+In addition to not having to support this library anymore, it would actually be flattering
+to have been validated by the API folks at Apple.
+
+***
+## [Want more?](#want-more)
+
+If you were hoping to see an API converted using closures and came up empty handed, there's a
+chance all can be right. [Simply vote on a feature](https://github.com/vhesener/Closures/labels/Closure%20API%20Request) by adding a 👍 reaction.
+
+***
+## [License](#license)
+
+Closures is provided under the [MIT License](https://github.com/vhesener/Closures/blob/master/LICENSE).
+
+```text
+The MIT License (MIT)
+Copyright (c) 2017 Vincent Hesener
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+associated documentation files (the "Software"), to deal in the Software without restriction,
+including without limitation the rights to use, copy, modify, merge, publish, distribute,
+sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or
+substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+```
\ No newline at end of file
diff --git a/Supporting/jazzy/abstracts/Controls.md b/Supporting/jazzy/abstracts/Controls.md
new file mode 100644
index 0000000..9034220
--- /dev/null
+++ b/Supporting/jazzy/abstracts/Controls.md
@@ -0,0 +1,116 @@
+ `Closures` framework adds closures to many features of `UIControl` subclasses.
+ Below are some common actions on some common controls.
+
+## UIButton Tap
+
+ A common target-action used is a button tap event. This one is
+ really simple:
+
+```swift
+button.onTap {
+ log("Button tapped")
+}
+```
+## Value Changed Events
+ Most other `UIControl` types are only interesting for their
+ value changes. The following are examples of how to observe
+ value changes on other popular `UIControl`s.
+
+### UISlider
+
+```swift
+slider.onChange { value in
+ log("slider: \(value)")
+}
+```
+
+ * * * *
+### UISegmentedControl
+
+```swift
+segmentedControl.onChange { index in
+ log("segment: \(index)")
+}
+```
+
+ * * * *
+### UIStepper
+
+```swift
+stepper.onChange { value in
+ log("stepper: \(value)")
+}
+```
+
+ * * * *
+### UIPageControl
+
+```swift
+pageControl.onChange { index in
+ log("page: \(index)")
+}
+```
+
+ * * * *
+### UISwitch
+
+```swift
+uiSwitch.onChange { isOn in
+ log("swith is: \(isOn ? "on" : "off")")
+}
+```
+
+ * * * *
+### UIDatePicker
+
+```swift
+datePicker.onChange { date in
+ log(date)
+}
+```
+
+ * * * *
+### UITextField
+
+ In addtion to text changes, `UITextField` has some other convenient wrappers
+ around some commonly needed actions. Below are examples of some events that
+ can you can observe. Notice the use of daisy chaining in order to keep it
+ concise and organized.
+
+```swift
+textfield
+ .onChange { newText in
+ log(newText)
+ }.onEditingBegan {
+ log("Editing began")
+ }.onEditingEnded {
+ log("Editing ended")
+ }.onReturn {
+ log("Return key tapped")
+}
+```
+
+## Delegation
+ `UITextField` also employs delegation to help define its behavior. Below
+ is how you would implement `UITextFieldDelegate` methods using closures.
+
+```swift
+textfield
+ .didBeginEditing {
+ log("Did begin editing delegate")
+ }.shouldClear {
+ log("Text clearing")
+ return true
+ }.shouldChangeCharacters { range, string in
+ return true
+}
+```
+
+ Although these convenience closures are not exhaustive, there is a way to
+ use a closure for any `UIControlEvents`.
+
+```swift
+button.on(.touchDragInside) { sender, event in
+ log("Dragging inside button")
+}
+```
\ No newline at end of file
diff --git a/Supporting/jazzy/abstracts/Gesture Recognizers.md b/Supporting/jazzy/abstracts/Gesture Recognizers.md
new file mode 100644
index 0000000..c5f467e
--- /dev/null
+++ b/Supporting/jazzy/abstracts/Gesture Recognizers.md
@@ -0,0 +1,57 @@
+The `UIGestureRecognizer` initializers and delegation wrappers
+ make it easy to add gesture recognizers to views. It also uses
+ closures instead of target-action and delegation.
+
+## Target-Action Initializers
+
+ The following is how you would add a double tap gesture
+ recognizer to your view using one of the custom initializers.
+ As always, we have a closure handler to respond to the gesture's
+ double tap action.
+
+```swift
+let doubleTapGesture = UITapGestureRecognizer(tapsRequired: 2) { _ in
+ log("double tapped")
+}
+view.addGestureRecognizer(doubleTapGesture)
+```
+
+These convenience initializers, delegate closures, and closure recognizers
+have been added to all of the existing concrete subclasses, including:
+
+* `UITapGestureRecognizer`
+* `UIPinchGestureRecognizer`
+* `UIRotationGestureRecognizer`
+* `UISwipeGestureRecognizer`
+* `UIPanGestureRecognizer`
+* `UIScreenEdgePanGestureRecognizer`
+* `UILongPressGestureRecognizer`
+
+There is also a method for you to configure a custom gesture recognizer
+to use closure handlers for recognition:
+
+```swift
+let myCustomGesture = MyCustomGestureRecognizer()
+configure(gesture: myCustomGesture) { _ in
+ /// a closure that's called when the gesture has ended
+}
+```
+
+## Delegation
+With convenient extension methods on `UIGestureRecognizer` and `UIView`,
+we can daisy chain an entire gesture cycle, including responding
+to `UIGestureRecognizerDelegate` methods.
+
+ ```swift
+view
+ .addPanGesture() { pan in
+ guard pan.state == .ended else { return }
+ log("view panned")
+ }.shouldBegin() {
+ true
+ }.shouldRecognizeSimultaneouslyWith {
+ $0 === doubleTapGesture
+}
+```
+
+
diff --git a/Supporting/jazzy/abstracts/KVO.md b/Supporting/jazzy/abstracts/KVO.md
new file mode 100644
index 0000000..1bd4f76
--- /dev/null
+++ b/Supporting/jazzy/abstracts/KVO.md
@@ -0,0 +1,36 @@
+ The Foundation team at Apple has made great
+ strides to become more closuresque, and Swift
+ 4 has truly proved that with KVO updates.
+
+ The API is almost perfect so there isn't too
+ much to add. There is only one convenience
+ method to improve some of the boiler plate
+ code for a typical use case.
+
+ The existing API requires you to save the observer
+ and store it for the duration of the observation.
+ Not bad, but with a little polish we can set and
+ forget. Although it looks like more effort to
+ provide a conditional remove handler, with the
+ helper var `selfDeinits` on every NSObject,
+ we don't have to hold onto distracting observer
+ variables in our view controllers.
+
+ In this example we'll observe the `text`
+ property of a UITextField.
+
+```swift
+let textField = UITextField()
+class MyClass: NSObject {
+ override init() { super.init()
+ textField.observe(\.text, until: selfDeinits) { obj,change in
+ print("Observed the change to \"\(obj.text!)\"")
+ }
+ }
+}
+
+var myObject: MyClass? = MyClass()
+textField.text = "🐒" // this will be observed
+myObject = nil
+textField.text = "This will NOT be observed"
+```
\ No newline at end of file
diff --git a/Supporting/jazzy/abstracts/UICollectionView.md b/Supporting/jazzy/abstracts/UICollectionView.md
new file mode 100644
index 0000000..b243ba4
--- /dev/null
+++ b/Supporting/jazzy/abstracts/UICollectionView.md
@@ -0,0 +1,52 @@
+## Delegate and DataSource
+
+ `UICollectionView` closures make it easy to implement `UICollectionViewDelegate` and
+ `UICollectionViewDataSource` protocol methods in an organized way. The following
+ is an example of a simple collection view that displays strings in a basic cell.
+
+```swift
+func loadCollectionView() {
+ collectionView.register(MyCustomCollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
+
+ collectionView
+ .numberOfItemsInSection { _ in
+ countries.count
+ }.cellForItemAt { indexPath in
+ let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! MyCustomCollectionViewCell
+ cell.textLabel.text = countries[indexPath.item]
+ return cell
+ }.didSelectItemAt {
+ print("\(countries[$0.item]) selected")
+ }.reloadData()
+}
+```
+
+## Arrays
+ These operations are common. Usually, they involve populating the `UICollectionView`
+ with the values from an array. `Closures` framework gives you a convenient
+ way to pass your array to the collection view, so that it can perform the boilerplate
+ operations for you, especially the ones that are required to make the collection
+ view perform at a basic level.
+
+ * Important:
+ Please remember that Swift `Array`s are value types. This means that they
+ are copied when mutated. When the values or sequence of your array changes, you will
+ need to call `addFlowElements` again, just before you call reloadData().
+
+```swift
+func loadCollectionView(countries: [String]) {
+ collectionView
+ .addFlowElements(countries, cell: MyCustomCollectionViewCell.self) { country, cell, index in
+ cell.textLabel.text = country
+ }.reloadData()
+
+ /**
+ This also allows you to override any default behavior so
+ you aren't overly committed, even though you're initially binding everything
+ to the `Array`.
+ */
+ collectionView.didSelectItemAt {
+ print("\(countries[$0.item]) selected from array")
+ }
+}
+```
diff --git a/Supporting/jazzy/abstracts/UIImagePickerController.md b/Supporting/jazzy/abstracts/UIImagePickerController.md
new file mode 100644
index 0000000..25e33f0
--- /dev/null
+++ b/Supporting/jazzy/abstracts/UIImagePickerController.md
@@ -0,0 +1,33 @@
+ The following is an example of using closures to
+ select media from a `UIImagePickerController`.
+ A simple picker is created which defaults to allowing
+ images to be selected from the photos library. This
+ initializer will call the handler when an image is selected.
+ When preseting with the `present(from:)` method,
+ the `UIImagePickerController` will dismiss itself on user cancel
+ or after the picker has picked its content (after the closure
+ is called).
+
+```swift
+ UIImagePickerController() { result,picker in
+ myImageView.image = result.editedImage
+ }.present(from: self)
+```
+
+ You can also customize the picker if you need more control, including
+ setting your own initial values and delegate callbacks.
+ The following is a verbose example using most of the possible
+ customization points.
+
+```swift
+ UIImagePickerController(
+ source: .camera,
+ allow: [.image, .movie],
+ cameraOverlay: nil,
+ showsCameraControls: false) { result,picker in
+ myImageView.image = result.originalImage
+ }.didCancel { [weak self] in
+ // some custom didCancel implementation
+ self?.dismiss(animated: animation.animate)
+ }.present(from: self)
+```
\ No newline at end of file
diff --git a/Supporting/jazzy/abstracts/UIPickerView.md b/Supporting/jazzy/abstracts/UIPickerView.md
new file mode 100644
index 0000000..7aa46da
--- /dev/null
+++ b/Supporting/jazzy/abstracts/UIPickerView.md
@@ -0,0 +1,81 @@
+## Delegate and DataSource
+
+ `UIPickerView` closures make it easy to implement `UIPickerViewDelegate` and
+ `UIPickerViewDataSource` protocol methods in an organized way. The following
+ is an example of a simple collection view that displays strings in each row.
+
+```swift
+func loadPickerView() {
+ pickerView
+ .numberOfRowsInComponent() { _ in
+ countries.count
+ }.titleForRow() { row, component in
+ countries[row]
+ }.didSelectRow { row, component in
+ log("\(countries[row]) selected")
+ }.reloadAllComponents()
+}
+```
+
+## Arrays
+ These operations are common. Usually, they involve populating the `UIPickerView`
+ with the values from an array. `Closures` framework gives you a convenient
+ way to pass your collection to the table view, so that it can perform the boilerplate
+ operations for you, especially the ones that are required to make the picker
+ view perform at a basic level.
+
+ * Important:
+ Please remember that Swift `Array`s are value types. This means that they
+ are copied when mutaded. When the values or sequence of your array changes, you will
+ need to call one of the `add` methods again, just before you
+ call reloadData().
+
+ ```swift
+func loadPickerView(countries: [String]) {
+ let reversed = Array(countries.reversed())
+ pickerView
+ .addStrings(reversed) { country,component,row in
+ log("\(country) selected from array")
+ }.reloadAllComponents()
+}
+```
+
+* Note:
+ Be sure to note that most of the closure callbacks in these array binding
+ methods switch the order of the parameters of row and component. Most of the
+ `UIPickerView` delegate/datasource method parameters pass `row,component`. The
+ parameters on the `add` methods switch the order and send `component,row`
+
+### Multiple Components
+ And finally, you can just as easily show mutliple components by binding a
+ 2-dimensional array. When using this method, the outer dimension of the array is the
+ component (columns) and the inner dimension are the rows in that component.
+
+```swift
+let anElement = myTwoDArray[component][row]
+```
+
+ In this example, the more verbose row handler is provided just to show the
+ different variations. Adding multiple components has a convenient method to
+ deal with string only arrays also.
+
+```swift
+func loadPickerView(components: [[String]]) {
+ pickerView.addComponents(
+ components,
+ rowTitle: { country,component,row in
+ country},
+ didSelect: { country,component,row in
+ log("\(country) selected from 2D Array")
+ })
+
+ /**
+ This also allows you to override any default behavior so
+ you aren't overly committed, even though you're initially binding everything
+ to the `Array`.
+ */
+ pickerView.widthForComponent { component in
+ component == 0 ? 200 : 100
+ }.reloadAllComponents()
+}
+```
diff --git a/Supporting/jazzy/abstracts/UITableView.md b/Supporting/jazzy/abstracts/UITableView.md
new file mode 100644
index 0000000..bdbf56c
--- /dev/null
+++ b/Supporting/jazzy/abstracts/UITableView.md
@@ -0,0 +1,74 @@
+## Delegate and DataSource
+
+ `UITableView` closures make it easy to implement `UITableViewDelegate` and
+ `UITableViewDataSource` protocol methods in an organized way. The following
+ is an example of a simple table view that displays strings in a basic cell.
+
+```swift
+func loadTableView() {
+ tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
+
+ tableView
+ .numberOfRows { _ in
+ countries.count
+ }.cellForRow { indexPath in
+ let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
+ cell.textLabel!.text = countries[indexPath.row]
+ return cell
+ }.didSelectRowAt {
+ print("\(countries[$0.row]) selected")
+ }.reloadData()
+}
+```
+
+## Arrays
+ These operations are common. Usually, they involve populating the `UITableView`
+ with the values from an array. `Closures` framework gives you a convenient
+ way to pass your array to the table view, so that it can perform the boiler
+ plate operations for you, especially the ones that are required to make the table
+ view perform at a basic level.
+
+ * Important:
+ Please remember that Swift `Array`s are value types. This means that they
+ are copied when mutated. When the values or sequence of your array changes, you will
+ need to call `addElements` again, just before you call reloadData().
+
+```swift
+func loadTableView(countries: [String]) {
+ tableView
+ .addElements(countries, cell: UITableViewCell.self) { country, cell, index in
+ cell.textLabel!.text = country
+ }.reloadData()
+}
+```
+
+### Multiple Sections
+* * * *
+ And finally, you can just as easily have a grouped table view, by binding a
+ 2-dimensional array. Before calling this method, we grouped the countries by their
+ first letter.
+
+ ```swift
+func loadTableView(countries: [[String]]) {
+ tableView.addSections(
+ countries,
+ cell: UITableViewCell.self,
+ headerTitle: { countryArray,index in
+ String(countryArray.first!.first!)},
+ row: { country, cell, index in
+ cell.textLabel!.text = country
+ })
+
+ /**
+ This also allows you to override any default behavior so
+ you aren't overly committed, even though you're initially binding everything
+ to the `Array`.
+ */
+ tableView
+ .estimatedHeightForHeaderInSection { _ in
+ 30
+ }.heightForHeaderInSection { _ in
+ 30
+ }.reloadData()
+}
+```
diff --git a/Supporting/jazzy/jazzy.yml b/Supporting/jazzy/jazzy.yml
new file mode 100644
index 0000000..f9f5a1f
--- /dev/null
+++ b/Supporting/jazzy/jazzy.yml
@@ -0,0 +1,50 @@
+clean: true
+output: "../../docs/"
+min_acl: public
+author: Vinnie Hesener
+copyright: 2017 Vincent Hesener
+author_url: https://git.io/vQ6dA
+github_url: https://github.com/vhesener/Closures
+sdk: iphone
+skip_undocumented: true
+hide_documentation_coverage: true
+use_safe_filenames: true
+readme: "../../README.md"
+module: Closures
+abstract: "../Supporting/jazzy/abstracts/*.md"
+custom_categories:
+ - name: Controls
+ children:
+ - UIButton
+ - UISwitch
+ - UITextField
+ - UIDatePicker
+ - UIPageControl
+ - UISegmentedControl
+ - UIStepper
+ - UISlider
+ - UIControl
+ - name: Scrolling Views
+ children:
+ - UITableView
+ - UICollectionView
+ - UIPickerView
+ - UIScrollView
+ - name: Gesture Recognizers
+ children:
+ - UIGestureRecognizer
+ - UITapGestureRecognizer
+ - UILongPressGestureRecognizer
+ - UIPinchGestureRecognizer
+ - UIPanGestureRecognizer
+ - UISwipeGestureRecognizer
+ - UIRotationGestureRecognizer
+ - UIScreenEdgePanGestureRecognizer
+ - UIView
+ - "configure(gesture:handler:)"
+ - name: Controllers
+ children:
+ - UIImagePickerController
+ - name: KVO
+ children:
+ - _KeyValueCodingAndObserving
diff --git a/Xcode/Closures.xcodeproj/project.pbxproj b/Xcode/Closures.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..722162e
--- /dev/null
+++ b/Xcode/Closures.xcodeproj/project.pbxproj
@@ -0,0 +1,510 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 46;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 3F0F219E1F1171BC007FD187 /* UICollectionViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F0F219D1F1171BC007FD187 /* UICollectionViewTests.swift */; };
+ 3F1F3D221EF8E54300A62BF2 /* UIGestureRecognizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F1F3D211EF8E54300A62BF2 /* UIGestureRecognizerTests.swift */; };
+ 3F55FC3D1EFD6D5E001ED560 /* UIPickerViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F55FC3C1EFD6D5E001ED560 /* UIPickerViewTests.swift */; };
+ 3F59C5711F1C32BF00945666 /* Core.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F59C5681F1C32AA00945666 /* Core.swift */; };
+ 3F59C5721F1C32BF00945666 /* KVO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F59C5691F1C32AA00945666 /* KVO.swift */; };
+ 3F59C5731F1C32BF00945666 /* UICollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F59C56A1F1C32AA00945666 /* UICollectionView.swift */; };
+ 3F59C5741F1C32BF00945666 /* UIControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F59C56B1F1C32AA00945666 /* UIControl.swift */; };
+ 3F59C5751F1C32BF00945666 /* UIGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F59C56C1F1C32AA00945666 /* UIGestureRecognizer.swift */; };
+ 3F59C5761F1C32BF00945666 /* UIImagePickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F59C56D1F1C32AA00945666 /* UIImagePickerController.swift */; };
+ 3F59C5771F1C32BF00945666 /* UIPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F59C56E1F1C32AA00945666 /* UIPickerView.swift */; };
+ 3F59C5781F1C32BF00945666 /* UIScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F59C56F1F1C32AA00945666 /* UIScrollView.swift */; };
+ 3F59C5791F1C32BF00945666 /* UITableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F59C5701F1C32AA00945666 /* UITableView.swift */; };
+ 3F5B9E411F0003C500B8555C /* KVOTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F5B9E401F0003C500B8555C /* KVOTests.swift */; };
+ 3F7620E81D849B5000E17BF5 /* Closures.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F7620DE1D849B5000E17BF5 /* Closures.framework */; };
+ 3F7620ED1D849B5000E17BF5 /* UIControlTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F7620EC1D849B5000E17BF5 /* UIControlTests.swift */; };
+ 3F7620EF1D849B5000E17BF5 /* Closures.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F7620E11D849B5000E17BF5 /* Closures.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 3FB42D941F01A97700740CB0 /* UIImagePickerControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FB42D931F01A97700740CB0 /* UIImagePickerControllerTests.swift */; };
+ 3FB77F311EE4DB5200C60F5C /* UITableViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FB77F301EE4DB5200C60F5C /* UITableViewTests.swift */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ 3F7620E91D849B5000E17BF5 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 3F7620D51D849B5000E17BF5 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 3F7620DD1D849B5000E17BF5;
+ remoteInfo = Closures;
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXFileReference section */
+ 3F0F219D1F1171BC007FD187 /* UICollectionViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UICollectionViewTests.swift; sourceTree = ""; };
+ 3F1F3D211EF8E54300A62BF2 /* UIGestureRecognizerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIGestureRecognizerTests.swift; sourceTree = ""; };
+ 3F55FC3C1EFD6D5E001ED560 /* UIPickerViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIPickerViewTests.swift; sourceTree = ""; };
+ 3F59C5681F1C32AA00945666 /* Core.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Core.swift; sourceTree = ""; };
+ 3F59C5691F1C32AA00945666 /* KVO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KVO.swift; sourceTree = ""; };
+ 3F59C56A1F1C32AA00945666 /* UICollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UICollectionView.swift; sourceTree = ""; };
+ 3F59C56B1F1C32AA00945666 /* UIControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIControl.swift; sourceTree = ""; };
+ 3F59C56C1F1C32AA00945666 /* UIGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIGestureRecognizer.swift; sourceTree = ""; };
+ 3F59C56D1F1C32AA00945666 /* UIImagePickerController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImagePickerController.swift; sourceTree = ""; };
+ 3F59C56E1F1C32AA00945666 /* UIPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIPickerView.swift; sourceTree = ""; };
+ 3F59C56F1F1C32AA00945666 /* UIScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIScrollView.swift; sourceTree = ""; };
+ 3F59C5701F1C32AA00945666 /* UITableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITableView.swift; sourceTree = ""; };
+ 3F5B9E401F0003C500B8555C /* KVOTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KVOTests.swift; sourceTree = ""; };
+ 3F7620DE1D849B5000E17BF5 /* Closures.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Closures.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 3F7620E11D849B5000E17BF5 /* Closures.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Closures.h; sourceTree = ""; };
+ 3F7620E21D849B5000E17BF5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 3F7620E71D849B5000E17BF5 /* ClosuresTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ClosuresTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 3F7620EC1D849B5000E17BF5 /* UIControlTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIControlTests.swift; sourceTree = ""; };
+ 3F7620EE1D849B5000E17BF5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 3FB42D931F01A97700740CB0 /* UIImagePickerControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImagePickerControllerTests.swift; sourceTree = ""; };
+ 3FB77F301EE4DB5200C60F5C /* UITableViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITableViewTests.swift; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 3F7620DA1D849B5000E17BF5 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 3F7620E41D849B5000E17BF5 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 3F7620E81D849B5000E17BF5 /* Closures.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 3F59C5671F1C32AA00945666 /* Source */ = {
+ isa = PBXGroup;
+ children = (
+ 3F59C5681F1C32AA00945666 /* Core.swift */,
+ 3F59C5691F1C32AA00945666 /* KVO.swift */,
+ 3F59C56A1F1C32AA00945666 /* UICollectionView.swift */,
+ 3F59C56B1F1C32AA00945666 /* UIControl.swift */,
+ 3F59C56C1F1C32AA00945666 /* UIGestureRecognizer.swift */,
+ 3F59C56D1F1C32AA00945666 /* UIImagePickerController.swift */,
+ 3F59C56E1F1C32AA00945666 /* UIPickerView.swift */,
+ 3F59C56F1F1C32AA00945666 /* UIScrollView.swift */,
+ 3F59C5701F1C32AA00945666 /* UITableView.swift */,
+ );
+ path = Source;
+ sourceTree = "";
+ };
+ 3F7620D41D849B5000E17BF5 = {
+ isa = PBXGroup;
+ children = (
+ 3F7620E01D849B5000E17BF5 /* Closures */,
+ 3F7620EB1D849B5000E17BF5 /* ClosuresTests */,
+ 3F7620DF1D849B5000E17BF5 /* Products */,
+ );
+ sourceTree = "";
+ };
+ 3F7620DF1D849B5000E17BF5 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 3F7620DE1D849B5000E17BF5 /* Closures.framework */,
+ 3F7620E71D849B5000E17BF5 /* ClosuresTests.xctest */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 3F7620E01D849B5000E17BF5 /* Closures */ = {
+ isa = PBXGroup;
+ children = (
+ 3F59C5671F1C32AA00945666 /* Source */,
+ 3F7620E11D849B5000E17BF5 /* Closures.h */,
+ 3F7620E21D849B5000E17BF5 /* Info.plist */,
+ );
+ path = Closures;
+ sourceTree = "";
+ };
+ 3F7620EB1D849B5000E17BF5 /* ClosuresTests */ = {
+ isa = PBXGroup;
+ children = (
+ 3FB42D931F01A97700740CB0 /* UIImagePickerControllerTests.swift */,
+ 3F55FC3C1EFD6D5E001ED560 /* UIPickerViewTests.swift */,
+ 3F1F3D211EF8E54300A62BF2 /* UIGestureRecognizerTests.swift */,
+ 3FB77F301EE4DB5200C60F5C /* UITableViewTests.swift */,
+ 3F0F219D1F1171BC007FD187 /* UICollectionViewTests.swift */,
+ 3F7620EC1D849B5000E17BF5 /* UIControlTests.swift */,
+ 3F5B9E401F0003C500B8555C /* KVOTests.swift */,
+ 3F7620EE1D849B5000E17BF5 /* Info.plist */,
+ );
+ path = ClosuresTests;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXHeadersBuildPhase section */
+ 3F7620DB1D849B5000E17BF5 /* Headers */ = {
+ isa = PBXHeadersBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 3F7620EF1D849B5000E17BF5 /* Closures.h in Headers */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXHeadersBuildPhase section */
+
+/* Begin PBXNativeTarget section */
+ 3F7620DD1D849B5000E17BF5 /* Closures */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 3F7620F21D849B5000E17BF5 /* Build configuration list for PBXNativeTarget "Closures" */;
+ buildPhases = (
+ 3F7620D91D849B5000E17BF5 /* Sources */,
+ 3F7620DA1D849B5000E17BF5 /* Frameworks */,
+ 3F7620DB1D849B5000E17BF5 /* Headers */,
+ 3F7620DC1D849B5000E17BF5 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = Closures;
+ productName = Closures;
+ productReference = 3F7620DE1D849B5000E17BF5 /* Closures.framework */;
+ productType = "com.apple.product-type.framework";
+ };
+ 3F7620E61D849B5000E17BF5 /* ClosuresTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 3F7620F51D849B5000E17BF5 /* Build configuration list for PBXNativeTarget "ClosuresTests" */;
+ buildPhases = (
+ 3F7620E31D849B5000E17BF5 /* Sources */,
+ 3F7620E41D849B5000E17BF5 /* Frameworks */,
+ 3F7620E51D849B5000E17BF5 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 3F7620EA1D849B5000E17BF5 /* PBXTargetDependency */,
+ );
+ name = ClosuresTests;
+ productName = ClosuresTests;
+ productReference = 3F7620E71D849B5000E17BF5 /* ClosuresTests.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 3F7620D51D849B5000E17BF5 /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastSwiftUpdateCheck = 0800;
+ LastUpgradeCheck = 0900;
+ ORGANIZATIONNAME = "Your Mom";
+ TargetAttributes = {
+ 3F7620DD1D849B5000E17BF5 = {
+ CreatedOnToolsVersion = 8.0;
+ LastSwiftMigration = 0900;
+ ProvisioningStyle = Automatic;
+ };
+ 3F7620E61D849B5000E17BF5 = {
+ CreatedOnToolsVersion = 8.0;
+ LastSwiftMigration = 0900;
+ ProvisioningStyle = Automatic;
+ };
+ };
+ };
+ buildConfigurationList = 3F7620D81D849B5000E17BF5 /* Build configuration list for PBXProject "Closures" */;
+ compatibilityVersion = "Xcode 3.2";
+ developmentRegion = English;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ );
+ mainGroup = 3F7620D41D849B5000E17BF5;
+ productRefGroup = 3F7620DF1D849B5000E17BF5 /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 3F7620DD1D849B5000E17BF5 /* Closures */,
+ 3F7620E61D849B5000E17BF5 /* ClosuresTests */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 3F7620DC1D849B5000E17BF5 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 3F7620E51D849B5000E17BF5 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 3F7620D91D849B5000E17BF5 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 3F59C5731F1C32BF00945666 /* UICollectionView.swift in Sources */,
+ 3F59C5741F1C32BF00945666 /* UIControl.swift in Sources */,
+ 3F59C5721F1C32BF00945666 /* KVO.swift in Sources */,
+ 3F59C5751F1C32BF00945666 /* UIGestureRecognizer.swift in Sources */,
+ 3F59C5711F1C32BF00945666 /* Core.swift in Sources */,
+ 3F59C5791F1C32BF00945666 /* UITableView.swift in Sources */,
+ 3F59C5781F1C32BF00945666 /* UIScrollView.swift in Sources */,
+ 3F59C5761F1C32BF00945666 /* UIImagePickerController.swift in Sources */,
+ 3F59C5771F1C32BF00945666 /* UIPickerView.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 3F7620E31D849B5000E17BF5 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 3F55FC3D1EFD6D5E001ED560 /* UIPickerViewTests.swift in Sources */,
+ 3F0F219E1F1171BC007FD187 /* UICollectionViewTests.swift in Sources */,
+ 3FB42D941F01A97700740CB0 /* UIImagePickerControllerTests.swift in Sources */,
+ 3F1F3D221EF8E54300A62BF2 /* UIGestureRecognizerTests.swift in Sources */,
+ 3F7620ED1D849B5000E17BF5 /* UIControlTests.swift in Sources */,
+ 3FB77F311EE4DB5200C60F5C /* UITableViewTests.swift in Sources */,
+ 3F5B9E411F0003C500B8555C /* KVOTests.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+ 3F7620EA1D849B5000E17BF5 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 3F7620DD1D849B5000E17BF5 /* Closures */;
+ targetProxy = 3F7620E91D849B5000E17BF5 /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
+/* Begin XCBuildConfiguration section */
+ 3F7620F01D849B5000E17BF5 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_SUSPICIOUS_MOVES = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ CURRENT_PROJECT_VERSION = 1;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_TREAT_WARNINGS_AS_ERRORS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 10.0;
+ MTL_ENABLE_DEBUG_INFO = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_TREAT_WARNINGS_AS_ERRORS = YES;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Debug;
+ };
+ 3F7620F11D849B5000E17BF5 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_SUSPICIOUS_MOVES = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ CURRENT_PROJECT_VERSION = 1;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_TREAT_WARNINGS_AS_ERRORS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 10.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = iphoneos;
+ SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
+ SWIFT_TREAT_WARNINGS_AS_ERRORS = YES;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ VERSIONING_SYSTEM = "apple-generic";
+ VERSION_INFO_PREFIX = "";
+ };
+ name = Release;
+ };
+ 3F7620F31D849B5000E17BF5 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_IDENTITY = "";
+ DEFINES_MODULE = YES;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ INFOPLIST_FILE = Closures/Info.plist;
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = com.vhesener.Closures;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_SWIFT3_OBJC_INFERENCE = Off;
+ SWIFT_VERSION = 4.0;
+ };
+ name = Debug;
+ };
+ 3F7620F41D849B5000E17BF5 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_IDENTITY = "";
+ DEFINES_MODULE = YES;
+ DYLIB_COMPATIBILITY_VERSION = 1;
+ DYLIB_CURRENT_VERSION = 1;
+ DYLIB_INSTALL_NAME_BASE = "@rpath";
+ INFOPLIST_FILE = Closures/Info.plist;
+ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
+ IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = com.vhesener.Closures;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SKIP_INSTALL = YES;
+ SWIFT_SWIFT3_OBJC_INFERENCE = Off;
+ SWIFT_VERSION = 4.0;
+ };
+ name = Release;
+ };
+ 3F7620F61D849B5000E17BF5 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
+ INFOPLIST_FILE = ClosuresTests/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = com.vhesener.ClosuresTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_SWIFT3_OBJC_INFERENCE = On;
+ SWIFT_VERSION = 4.0;
+ };
+ name = Debug;
+ };
+ 3F7620F71D849B5000E17BF5 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
+ INFOPLIST_FILE = ClosuresTests/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+ PRODUCT_BUNDLE_IDENTIFIER = com.vhesener.ClosuresTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_SWIFT3_OBJC_INFERENCE = On;
+ SWIFT_VERSION = 4.0;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 3F7620D81D849B5000E17BF5 /* Build configuration list for PBXProject "Closures" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 3F7620F01D849B5000E17BF5 /* Debug */,
+ 3F7620F11D849B5000E17BF5 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 3F7620F21D849B5000E17BF5 /* Build configuration list for PBXNativeTarget "Closures" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 3F7620F31D849B5000E17BF5 /* Debug */,
+ 3F7620F41D849B5000E17BF5 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 3F7620F51D849B5000E17BF5 /* Build configuration list for PBXNativeTarget "ClosuresTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 3F7620F61D849B5000E17BF5 /* Debug */,
+ 3F7620F71D849B5000E17BF5 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 3F7620D51D849B5000E17BF5 /* Project object */;
+}
diff --git a/Xcode/Closures.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Xcode/Closures.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..71487e2
--- /dev/null
+++ b/Xcode/Closures.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/Xcode/Closures.xcodeproj/xcshareddata/IDETemplateMacros.plist b/Xcode/Closures.xcodeproj/xcshareddata/IDETemplateMacros.plist
new file mode 100644
index 0000000..b3e0ffb
--- /dev/null
+++ b/Xcode/Closures.xcodeproj/xcshareddata/IDETemplateMacros.plist
@@ -0,0 +1,27 @@
+
+
+
+
+ FILEHEADER
+
+/**
+ The MIT License (MIT)
+ Copyright (c) ___YEAR___ Vincent Hesener
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+ is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+
\ No newline at end of file
diff --git a/Xcode/Closures.xcworkspace/contents.xcworkspacedata b/Xcode/Closures.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..be4293b
--- /dev/null
+++ b/Xcode/Closures.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
diff --git a/Xcode/Closures.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/Xcode/Closures.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000..3ddf867
--- /dev/null
+++ b/Xcode/Closures.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ BuildSystemType
+ Latest
+
+
diff --git a/Xcode/Closures/Closures.h b/Xcode/Closures/Closures.h
new file mode 100644
index 0000000..91586fb
--- /dev/null
+++ b/Xcode/Closures/Closures.h
@@ -0,0 +1,7 @@
+#import
+
+//! Project version number for Closures.
+FOUNDATION_EXPORT double ClosuresVersionNumber;
+
+//! Project version string for Closures.
+FOUNDATION_EXPORT const unsigned char ClosuresVersionString[];
diff --git a/Xcode/Closures/Info.plist b/Xcode/Closures/Info.plist
new file mode 100644
index 0000000..e6f4652
--- /dev/null
+++ b/Xcode/Closures/Info.plist
@@ -0,0 +1,24 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ FMWK
+ CFBundleShortVersionString
+ 0.1
+ CFBundleVersion
+ $(CURRENT_PROJECT_VERSION)
+ NSPrincipalClass
+
+
+
diff --git a/Xcode/Closures/Source/Core.swift b/Xcode/Closures/Source/Core.swift
new file mode 100644
index 0000000..db24cf8
--- /dev/null
+++ b/Xcode/Closures/Source/Core.swift
@@ -0,0 +1,164 @@
+/**
+ The MIT License (MIT)
+ Copyright (c) 2017 Vincent Hesener
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+ is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+import Foundation
+
+protocol DelegateProtocol: class {
+}
+
+public protocol DelegatorProtocol: class {
+ /**
+ Clears any delegates/datasources that were assigned by the `Closures`
+ framework for this object. This cleans up memory as well as sets the
+ delegate/datasource properties to nil.
+ */
+ func clearClosureDelegates()
+}
+
+class DelegateWrapper: NSObject {
+ weak var delegator: Delegator?
+ let delegate: Delegate
+
+ init(delegator: Delegator, delegate: Delegate) {
+ self.delegate = delegate
+ self.delegator = delegator
+ }
+
+ var tupac: Bool { return delegator == nil }
+
+ public static func wrapper(delegator: Delegator,
+ delegate: @autoclosure () -> Delegate,
+ delegates: inout Set>,
+ bind: (_ delegator: Delegator, _ delegate: Delegate) -> Void) -> DelegateWrapper {
+ var deadRappers = [DelegateWrapper]()
+ defer {
+ delegates.subtract(deadRappers)
+ }
+
+ if let wrapper = delegates.first(where: {
+ // lazy, inaccurate cleanup.
+ if $0.tupac {
+ deadRappers.append($0)
+ }
+ return $0.delegator === delegator
+ }) {
+ return wrapper
+ }
+ let delegate = delegate()
+ let wrapper: DelegateWrapper = DelegateWrapper(delegator: delegator, delegate: delegate)
+ bind(delegator, delegate)
+ delegates.insert(wrapper)
+
+ return wrapper
+ }
+
+ public static func remove(delegator: Delegator, from delegates: inout Set>) {
+ if let wrapper = delegates.first(where: { $0.delegator === delegator }) {
+ delegates.remove(wrapper)
+ }
+ }
+
+ public static func update(_ delegator: Delegator,
+ delegate: @autoclosure () -> Delegate,
+ delegates: inout Set>,
+ bind: (_ delegator: Delegator, _ delegate: Delegate) -> Void,
+ with updateHandler: (_ wrapper: DelegateWrapper) -> Void) {
+ let wrapper = self.wrapper(delegator: delegator, delegate: delegate, delegates: &delegates, bind: bind)
+ updateHandler(wrapper)
+ bind(delegator, wrapper.delegate)
+ }
+}
+
+fileprivate class BundleHook {}
+extension Bundle {
+ static let closures = Bundle(for: BundleHook.self)
+}
+
+extension String {
+ static let namespace = Bundle.closures.bundleIdentifier ?? ""
+}
+
+extension NotificationCenter {
+ static func selfObserve(name: Notification.Name,
+ target: T,
+ closure: @escaping (_ target: T, _ userInfo: [AnyHashable : Any]?) -> Void) where T: AnyObject {
+ NotificationCenter.closures.selfObserve(name: name, target: target, closure: closure)
+ }
+
+ func selfObserve(name: Notification.Name,
+ target: T,
+ closure: @escaping (_ target: T, _ userInfo: [AnyHashable : Any]?) -> Void) where T: AnyObject {
+ var observer: NSObjectProtocol?
+ observer = addObserver(
+ forName: name,
+ object: nil,
+ queue: nil) { [weak target, weak self] in
+ // Cleanup opportunity for this notification name.
+ // Remove if target is nil (target-action on self is fruitless)
+ guard let target = target else {
+ self?.removeObserver(observer!)
+ observer = nil
+ return
+ }
+ // Defensive check that self is posting and the target
+ guard let object = $0.object as? T,
+ object === target else {
+ return
+ }
+ closure(target, $0.userInfo)
+ }
+ }
+
+ @discardableResult
+ static func observeUntil(
+ _ removeCondition: @escaping (_ object: T?) -> Bool,
+ object: T,
+ name: Notification.Name,
+ closure: @escaping (_ object: T, _ userInfo: [AnyHashable : Any]?) -> Void) -> NSObjectProtocol where T: AnyObject {
+ return NotificationCenter.closures.observeUntil(removeCondition, object: object, name: name, closure: closure)
+ }
+
+ @discardableResult
+ func observeUntil(
+ _ removeCondition: @escaping (_ object: T?) -> Bool,
+ object: T,
+ name: Notification.Name,
+ closure: @escaping (_ object: T, _ userInfo: [AnyHashable : Any]?) -> Void) -> NSObjectProtocol where T: AnyObject {
+ var observer: NSObjectProtocol?
+ observer = addObserver(
+ forName: name,
+ object: object,
+ queue: nil) { [weak object, weak self] in
+ // Explicit cleanup condition for this observer.
+ guard !removeCondition(object),
+ let object = object else {
+ self?.removeObserver(observer!)
+ observer = nil
+ return
+ }
+ closure(object, $0.userInfo)
+ }
+ return observer!
+ }
+}
+
+extension NotificationCenter {
+ static let closures = NotificationCenter()
+}
diff --git a/Xcode/Closures/Source/KVO.swift b/Xcode/Closures/Source/KVO.swift
new file mode 100644
index 0000000..cc8d04b
--- /dev/null
+++ b/Xcode/Closures/Source/KVO.swift
@@ -0,0 +1,97 @@
+/**
+ The MIT License (MIT)
+ Copyright (c) 2017 Vincent Hesener
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+ is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+import Foundation
+
+extension NSObject {
+ public var selfDeinits: (_KeyValueCodingAndObserving) -> Bool {
+ return { [weak self] _ in
+ return self == nil
+ }
+ }
+}
+
+extension _KeyValueCodingAndObserving {
+ /**
+ This convenience method puts only a tiny bit of polish on
+ Swift's latest closure-based KVO implementation. Although
+ there aren't many obvious differences between this
+ method and the one in `Foundation`, there are a few helpers, which
+ are describe below.
+
+ First, it passes a remove condition, `until`, which is simpy a closure
+ that gets called to determine whether to remove the observation
+ closure. Returning true will remove the observer, otherwise, the
+ observing will continue. `Foundation`'s method
+ automatically removes observation when the `NSKeyValueObservation`
+ is released, but this requires you to save it somewhere in your
+ view controller as a property, thereby cluttering up your view
+ controller with plumbing-only members.
+
+ Second, this method attempts to slightly improve the clutter. You
+ do not have to save the observer. `@discardableResult` allows you
+ to disregard the returned object entirely.
+
+ Finally, a typical pattern is to remove observers when deinit is
+ called on the object that owns the observed object.
+ For instance, if you are observing a model object property on your
+ view controller, you will probably want to stop observing when the
+ view controller gets released from memory. Because this is a
+ common pattern, there's a convenient var available on all subclasses
+ of NSObject named `selfDeinits`. Simply pass this as a parameter
+ into the `until` paramaeter, and the observation will be removed
+ when `self` is deallocated.
+
+ * * * *
+ #### An example of calling this method:
+ ```swift
+ <#someObject#>.observe(\.<#some.key.path#>, until: selfDeinits) { obj,change in
+ <#do something#>
+ }
+ ```
+
+ * parameter keyPath: The keyPath you wish to observe on this object
+ * parameter options: The observing options
+ * parameter until: The closure called when this handler should stop
+ observing. Return true if you want to forcefully stop observing.
+ * parameter changeHandler: The callback that will be called when
+ the keyPath change has occurred.
+
+ * returns: The observer object needed to remove observation
+ */
+ @discardableResult
+ public func observe(
+ _ keyPath: KeyPath,
+ options: NSKeyValueObservingOptions = [],
+ until removeCondition: @escaping (Self) -> Bool,
+ changeHandler: @escaping (Self, NSKeyValueObservedChange) -> Void)
+ -> NSKeyValueObservation {
+ var observer: NSKeyValueObservation?
+ observer = self.observe(keyPath, options: options) { obj, change in
+ guard !removeCondition(obj) else {
+ observer?.invalidate()
+ observer = nil
+ return
+ }
+ changeHandler(obj, change)
+ }
+ return observer!
+ }
+}
diff --git a/Xcode/Closures/Source/UICollectionView.swift b/Xcode/Closures/Source/UICollectionView.swift
new file mode 100644
index 0000000..a032b48
--- /dev/null
+++ b/Xcode/Closures/Source/UICollectionView.swift
@@ -0,0 +1,788 @@
+/**
+ The MIT License (MIT)
+ Copyright (c) 2017 Vincent Hesener
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+ is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+import UIKit
+
+/// :nodoc:
+private let jzyBug = 0 // Prevent the license header from showing up in Jazzy Docs for UICollectionView
+
+extension UICollectionView {
+ // MARK: Common Array Usage
+ /**
+ This method defaults many of the boilerplate callbacks needed to populate a
+ UICollectionView when using an `Array` as a data source.
+ The defaults that this method takes care of:
+
+ * Registers the cell's class and reuse identifier with a default value
+ * Optionally uses a cellNibName value to create the cell from a nib file
+ from the main bundle
+ * Handles cell dequeueing and provides a reference to the cell
+ in the `item` closure for you to modify in place.
+ * Provides the number of sections
+ * Provides the number of items
+
+ This method simply sets basic default behavior. This means that you can
+ override the default collection view handlers after this method is called.
+ However, remember that order matters. If you call this method after you
+ override the `numberOfSectionsIn` callback, for instance, the closure
+ you passed into `numberOfSectionsIn` will be wiped out by this method
+ and you will have to override that closure handler again.
+
+ * Important:
+ Please remember that Swift `Array`s are value types. This means that they
+ are copied when mutaded. When the values or sequence of your array changes, you will
+ need to call this method again, just before you
+ call reloadData(). If you have a lot of collection view customization in addtion to a lot
+ of updates to your array, it is more appropriate to use the individual
+ closure handlers instead of this method.
+
+ * * * *
+ #### An example of calling this method:
+ ```swift
+ collectionView.addFlowElements(<#myArray#>, cell: <#MyUICollectionViewCellClass#>) { element, cell, index in
+ cell.imageView.image = <#T##someImage(from: element)##UIImage#>
+ }
+ ```
+ * parameter array: An Array to be used for each item.
+ * parameter cell: A type of cell to use when calling `dequeueReusableCell(withReuseIdentifier:for:)`
+ * parameter cellNibName: If non-nil, the cell will be dequeued using a nib with this name from the main bundle
+ * parameter item: A closure that's called when a cell is about to be shown and needs to be configured.
+
+ * returns: itself so you can daisy chain the other datasource calls
+ */
+ @discardableResult
+ public func addFlowElements(
+ _ elements: [Element],
+ cell: Cell.Type,
+ cellNibName: String? = nil,
+ item: @escaping (_ element: Element, _ cell: inout Cell,_ index: Int) -> Void) -> Self
+ where Cell: UICollectionViewCell {
+ return _addSections([elements], cell: cell, cellNibName: cellNibName, item: item)
+ }
+
+ @discardableResult
+ private func _addSections(
+ _ elements: [[Element]],
+ cell: Cell.Type,
+ cellNibName: String? = nil,
+ item: @escaping (_ element: Element, _ cell: inout Cell,_ index: Int) -> Void) -> Self
+ where Cell: UICollectionViewCell {
+
+ DelegateWrapper.remove(delegator: self, from: &CollectionViewDelegate.delegates)
+ delegate = nil
+ dataSource = nil
+ let reuseIdentifier = "\(Element.self).\(cell)"
+
+ if let nibName = cellNibName {
+ register(UINib(nibName: nibName, bundle: nil), forCellWithReuseIdentifier: reuseIdentifier)
+ } else {
+ register(Cell.self, forCellWithReuseIdentifier: reuseIdentifier)
+ }
+
+ return numberOfSectionsIn
+ {
+ return elements.count
+ }.numberOfItemsInSection {
+ return elements[$0].count
+ }.cellForItemAt { [unowned self] in
+ let element = elements[$0.section][$0.item]
+ var cell = self.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: $0) as! Cell
+ item(element, &cell, $0.item)
+ return cell
+ }
+ }
+}
+
+class CollectionViewDelegate: ScrollViewDelegate, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
+ fileprivate static var delegates = Set>()
+
+ fileprivate var shouldHighlightItemAt: ((_ indexPath: IndexPath) -> Bool)?
+ func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool {
+ return shouldHighlightItemAt?(indexPath) ?? true
+ }
+ fileprivate var didHighlightItemAt: ((_ indexPath: IndexPath) -> Void)?
+ func collectionView(_ collectionView: UICollectionView, didHighlightItemAt indexPath: IndexPath) {
+ didHighlightItemAt?(indexPath)
+ }
+ fileprivate var didUnhighlightItemAt: ((_ indexPath: IndexPath) -> Void)?
+ func collectionView(_ collectionView: UICollectionView, didUnhighlightItemAt indexPath: IndexPath) {
+ didUnhighlightItemAt?(indexPath)
+ }
+ fileprivate var shouldSelectItemAt: ((_ indexPath: IndexPath) -> Bool)?
+ func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
+ return shouldSelectItemAt?(indexPath) ?? true
+ }
+ fileprivate var shouldDeselectItemAt: ((_ indexPath: IndexPath) -> Bool)?
+ func collectionView(_ collectionView: UICollectionView, shouldDeselectItemAt indexPath: IndexPath) -> Bool {
+ return shouldDeselectItemAt?(indexPath) ?? true
+ }
+ fileprivate var didSelectItemAt: ((_ indexPath: IndexPath) -> Void)?
+ func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
+ didSelectItemAt?(indexPath)
+ }
+ fileprivate var didDeselectItemAt: ((_ indexPath: IndexPath) -> Void)?
+ func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
+ didDeselectItemAt?(indexPath)
+ }
+ fileprivate var willDisplay: ((_ cell: UICollectionViewCell, _ indexPath: IndexPath) -> Void)?
+ func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
+ willDisplay?(cell,indexPath)
+ }
+ fileprivate var willDisplaySupplementaryView: ((_ elementKind: String, _ indexPath: IndexPath) -> Void)?
+ func collectionView(_ collectionView: UICollectionView, willDisplaySupplementaryView view: UICollectionReusableView, forElementKind elementKind: String, at indexPath: IndexPath) {
+ willDisplaySupplementaryView?(elementKind,indexPath)
+ }
+ fileprivate var didEndDisplaying: ((_ cell: UICollectionViewCell, _ indexPath: IndexPath) -> Void)?
+ func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
+ didEndDisplaying?(cell,indexPath)
+ }
+ fileprivate var didEndDisplayingSupplementaryView: ((_ elementKind: String, _ indexPath: IndexPath) -> Void)?
+ func collectionView(_ collectionView: UICollectionView, didEndDisplayingSupplementaryView view: UICollectionReusableView, forElementOfKind elementKind: String, at indexPath: IndexPath) {
+ didEndDisplayingSupplementaryView?(elementKind,indexPath)
+ }
+ fileprivate var shouldShowMenuForItemAt: ((_ indexPath: IndexPath) -> Bool)?
+ func collectionView(_ collectionView: UICollectionView, shouldShowMenuForItemAt indexPath: IndexPath) -> Bool {
+ return shouldShowMenuForItemAt?(indexPath) ?? false
+ }
+ fileprivate var canPerformAction: ((_ action: Selector, _ indexPath: IndexPath, _ sender: Any?) -> Bool)?
+ func collectionView(_ collectionView: UICollectionView, canPerformAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) -> Bool {
+ return canPerformAction?(action, indexPath, sender) ?? false
+ }
+ fileprivate var performAction: ((_ action: Selector, _ indexPath: IndexPath, _ sender: Any?) -> Void)?
+ func collectionView(_ collectionView: UICollectionView, performAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) {
+ performAction?(action, indexPath, sender)
+ }
+ fileprivate var transitionLayoutForOldLayout: ((_ fromLayout: UICollectionViewLayout, _ toLayout: UICollectionViewLayout) -> UICollectionViewTransitionLayout)?
+ func collectionView(_ collectionView: UICollectionView, transitionLayoutForOldLayout fromLayout: UICollectionViewLayout, newLayout toLayout: UICollectionViewLayout) -> UICollectionViewTransitionLayout {
+ return transitionLayoutForOldLayout?(fromLayout,toLayout) ?? UICollectionViewTransitionLayout(currentLayout: fromLayout, nextLayout: toLayout)
+ }
+ fileprivate var canFocusItemAt: ((_ indexPath: IndexPath) -> Bool)?
+ func collectionView(_ collectionView: UICollectionView, canFocusItemAt indexPath: IndexPath) -> Bool {
+ return canFocusItemAt?(indexPath) ?? false
+ }
+ fileprivate var shouldUpdateFocusIn: ((_ context: UICollectionViewFocusUpdateContext) -> Bool)?
+ func collectionView(_ collectionView: UICollectionView, shouldUpdateFocusIn context: UICollectionViewFocusUpdateContext) -> Bool {
+ return shouldUpdateFocusIn?(context) ?? true
+ }
+ fileprivate var didUpdateFocusIn: ((_ context: UICollectionViewFocusUpdateContext, _ coordinator: UIFocusAnimationCoordinator) -> Void)?
+ func collectionView(_ collectionView: UICollectionView, didUpdateFocusIn context: UICollectionViewFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
+ didUpdateFocusIn?(context,coordinator)
+ }
+ fileprivate var indexPathForPreferredFocusedViewIn: (() -> IndexPath?)?
+ func indexPathForPreferredFocusedView(in collectionView: UICollectionView) -> IndexPath? {
+ return indexPathForPreferredFocusedViewIn?() ?? nil
+ }
+ fileprivate var targetIndexPathForMoveFromItemAt: ((_ originalIndexPath: IndexPath, _ proposedIndexPath: IndexPath) -> IndexPath)?
+ func collectionView(_ collectionView: UICollectionView, targetIndexPathForMoveFromItemAt originalIndexPath: IndexPath, toProposedIndexPath proposedIndexPath: IndexPath) -> IndexPath {
+ return targetIndexPathForMoveFromItemAt?(originalIndexPath,proposedIndexPath) ?? proposedIndexPath
+ }
+ fileprivate var targetContentOffsetForProposedContentOffset: ((_ proposedContentOffset: CGPoint) -> CGPoint)?
+ func collectionView(_ collectionView: UICollectionView, targetContentOffsetForProposedContentOffset proposedContentOffset: CGPoint) -> CGPoint {
+ return targetContentOffsetForProposedContentOffset?(proposedContentOffset) ?? proposedContentOffset
+ }
+ private var _shouldSpringLoadItemAt: Any?
+ @available(iOS 11, *)
+ fileprivate var shouldSpringLoadItemAt: ((_ indexPath: IndexPath, _ context: UISpringLoadedInteractionContext) -> Bool)? {
+ get {
+ return _shouldSpringLoadItemAt as? (_ indexPath: IndexPath, _ context: UISpringLoadedInteractionContext) -> Bool
+ }
+ set {
+ _shouldSpringLoadItemAt = newValue
+ }
+ }
+ @available(iOS 11, *)
+ func collectionView(_ collectionView: UICollectionView, shouldSpringLoadItemAt indexPath: IndexPath, with context: UISpringLoadedInteractionContext) -> Bool {
+ return shouldSpringLoadItemAt?(indexPath, context) ?? true
+ }
+ fileprivate var numberOfItemsInSection: ((_ section: Int) -> Int)?
+ func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
+ return numberOfItemsInSection?(section) ?? 0
+ }
+ fileprivate var cellForItemAt: ((_ indexPath: IndexPath) -> UICollectionViewCell)?
+ func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+ return cellForItemAt?(indexPath) ?? UICollectionViewCell()
+ }
+ fileprivate var numberOfSectionsIn: (() -> Int)?
+ func numberOfSections(in collectionView: UICollectionView) -> Int {
+ return numberOfSectionsIn?() ?? 1
+ }
+ fileprivate var viewForSupplementaryElementOfKind: ((_ kind: String, _ indexPath: IndexPath) -> UICollectionReusableView)?
+ func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
+ return viewForSupplementaryElementOfKind?(kind,indexPath) ?? UICollectionReusableView()
+ }
+ fileprivate var canMoveItemAt: ((_ indexPath: IndexPath) -> Bool)?
+ func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool {
+ return canMoveItemAt?(indexPath) ?? false
+ }
+ fileprivate var moveItemAt: ((_ sourceIndexPath: IndexPath, _ destinationIndexPath: IndexPath) -> Void)?
+ func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
+ moveItemAt?(sourceIndexPath,destinationIndexPath)
+ }
+ fileprivate var indexTitlesFor: (() -> [String])?
+ func indexTitles(for collectionView: UICollectionView) -> [String]? {
+ return indexTitlesFor?() ?? []
+ }
+ fileprivate var indexPathForIndexTitle: ((_ title: String, _ index: Int) -> IndexPath)?
+ func collectionView(_ collectionView: UICollectionView, indexPathForIndexTitle title: String, at index: Int) -> IndexPath {
+ return indexPathForIndexTitle?(title,index) ?? IndexPath()
+ }
+ fileprivate var sizeForItemAt: ((_ indexPath: IndexPath) -> CGSize)?
+ func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
+ return sizeForItemAt?(indexPath) ?? (collectionViewLayout as? UICollectionViewFlowLayout)?.itemSize ?? CGSize(width: 50, height: 50)
+ }
+ fileprivate var insetForSectionAt: ((_ section: Int) -> UIEdgeInsets)?
+ func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
+ return insetForSectionAt?(section) ?? (collectionViewLayout as? UICollectionViewFlowLayout)?.sectionInset ?? .zero
+ }
+ fileprivate var minimumLineSpacingForSectionAt: ((_ section: Int) -> CGFloat)?
+ func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
+ return minimumLineSpacingForSectionAt?(section) ?? (collectionViewLayout as? UICollectionViewFlowLayout)?.minimumLineSpacing ?? 10
+ }
+ fileprivate var minimumInteritemSpacingForSectionAt: ((_ section: Int) -> CGFloat)?
+ func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
+ return minimumInteritemSpacingForSectionAt?(section) ?? (collectionViewLayout as? UICollectionViewFlowLayout)?.minimumInteritemSpacing ?? 10
+ }
+ fileprivate var referenceSizeForHeaderInSection: ((_ section: Int) -> CGSize)?
+ func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
+ return referenceSizeForHeaderInSection?(section) ?? (collectionViewLayout as? UICollectionViewFlowLayout)?.headerReferenceSize ?? .zero
+ }
+ fileprivate var referenceSizeForFooterInSection: ((_ section: Int) -> CGSize)?
+ func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
+ return referenceSizeForFooterInSection?(section) ?? (collectionViewLayout as? UICollectionViewFlowLayout)?.footerReferenceSize ?? .zero
+ }
+
+ override func responds(to aSelector: Selector!) -> Bool {
+ if #available(iOS 11, *) {
+ switch aSelector {
+ case #selector(CollectionViewDelegate.collectionView(_:shouldSpringLoadItemAt:with:)):
+ return _shouldSpringLoadItemAt != nil
+ default:
+ break
+ }
+ }
+
+ switch aSelector {
+ case #selector(CollectionViewDelegate.collectionView(_:shouldHighlightItemAt:)):
+ return shouldHighlightItemAt != nil
+ case #selector(CollectionViewDelegate.collectionView(_:didHighlightItemAt:)):
+ return didHighlightItemAt != nil
+ case #selector(CollectionViewDelegate.collectionView(_:didUnhighlightItemAt:)):
+ return didUnhighlightItemAt != nil
+ case #selector(CollectionViewDelegate.collectionView(_:shouldSelectItemAt:)):
+ return shouldSelectItemAt != nil
+ case #selector(CollectionViewDelegate.collectionView(_:shouldDeselectItemAt:)):
+ return shouldDeselectItemAt != nil
+ case #selector(CollectionViewDelegate.collectionView(_:didSelectItemAt:)):
+ return didSelectItemAt != nil
+ case #selector(CollectionViewDelegate.collectionView(_:didDeselectItemAt:)):
+ return didDeselectItemAt != nil
+ case #selector(CollectionViewDelegate.collectionView(_:willDisplay:forItemAt:)):
+ return willDisplay != nil
+ case #selector(CollectionViewDelegate.collectionView(_:willDisplaySupplementaryView:forElementKind:at:)):
+ return willDisplaySupplementaryView != nil
+ case #selector(CollectionViewDelegate.collectionView(_:didEndDisplaying:forItemAt:)):
+ return didEndDisplaying != nil
+ case #selector(CollectionViewDelegate.collectionView(_:didEndDisplayingSupplementaryView:forElementOfKind:at:)):
+ return didEndDisplayingSupplementaryView != nil
+ case #selector(CollectionViewDelegate.collectionView(_:shouldShowMenuForItemAt:)):
+ return shouldShowMenuForItemAt != nil
+ case #selector(CollectionViewDelegate.collectionView(_:canPerformAction:forItemAt:withSender:)):
+ return canPerformAction != nil
+ case #selector(CollectionViewDelegate.collectionView(_:performAction:forItemAt:withSender:)):
+ return performAction != nil
+ case #selector(CollectionViewDelegate.collectionView(_:transitionLayoutForOldLayout:newLayout:)):
+ return transitionLayoutForOldLayout != nil
+ case #selector(CollectionViewDelegate.collectionView(_:canFocusItemAt:)):
+ return canFocusItemAt != nil
+ case #selector(CollectionViewDelegate.collectionView(_:shouldUpdateFocusIn:)):
+ return shouldUpdateFocusIn != nil
+ case #selector(CollectionViewDelegate.collectionView(_:didUpdateFocusIn:with:)):
+ return didUpdateFocusIn != nil
+ case #selector(CollectionViewDelegate.indexPathForPreferredFocusedView(in:)):
+ return indexPathForPreferredFocusedViewIn != nil
+ case #selector(CollectionViewDelegate.collectionView(_:targetIndexPathForMoveFromItemAt:toProposedIndexPath:)):
+ return targetIndexPathForMoveFromItemAt != nil
+ case #selector(CollectionViewDelegate.collectionView(_:targetContentOffsetForProposedContentOffset:)):
+ return targetContentOffsetForProposedContentOffset != nil
+ case #selector(CollectionViewDelegate.collectionView(_:numberOfItemsInSection:)):
+ return numberOfItemsInSection != nil
+ case #selector(CollectionViewDelegate.collectionView(_:cellForItemAt:)):
+ return cellForItemAt != nil
+ case #selector(CollectionViewDelegate.numberOfSections(in:)):
+ return numberOfSectionsIn != nil
+ case #selector(CollectionViewDelegate.collectionView(_:viewForSupplementaryElementOfKind:at:)):
+ return viewForSupplementaryElementOfKind != nil
+ case #selector(CollectionViewDelegate.collectionView(_:canMoveItemAt:)):
+ return canMoveItemAt != nil
+ case #selector(CollectionViewDelegate.collectionView(_:moveItemAt:to:)):
+ return moveItemAt != nil
+ case #selector(CollectionViewDelegate.indexTitles(for:)):
+ return indexTitlesFor != nil
+ case #selector(CollectionViewDelegate.collectionView(_:indexPathForIndexTitle:at:)):
+ return indexPathForIndexTitle != nil
+ case #selector(CollectionViewDelegate.collectionView(_:layout:sizeForItemAt:)):
+ return sizeForItemAt != nil
+ case #selector(CollectionViewDelegate.collectionView(_:layout:insetForSectionAt:)):
+ return insetForSectionAt != nil
+ case #selector(CollectionViewDelegate.collectionView(_:layout:minimumLineSpacingForSectionAt:)):
+ return minimumLineSpacingForSectionAt != nil
+ case #selector(CollectionViewDelegate.collectionView(_:layout:minimumInteritemSpacingForSectionAt:)):
+ return minimumInteritemSpacingForSectionAt != nil
+ case #selector(CollectionViewDelegate.collectionView(_:layout:referenceSizeForHeaderInSection:)):
+ return referenceSizeForHeaderInSection != nil
+ case #selector(CollectionViewDelegate.collectionView(_:layout:referenceSizeForFooterInSection:)):
+ return referenceSizeForFooterInSection != nil
+ default:
+ return super.responds(to: aSelector)
+ }
+ }
+}
+
+extension UICollectionView {
+ // MARK: Delegate and DataSource Overrides
+ /**
+ Equivalent to implementing UICollectionViewDelegate's collectionView(_:shouldHighlightItemAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func shouldHighlightItemAt(handler: @escaping (_ indexPath: IndexPath) -> Bool) -> Self {
+ return update { $0.shouldHighlightItemAt = handler }
+ }
+ /**
+ Equivalent to implementing UICollectionViewDelegate's collectionView(_:didHighlightItemAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func didHighlightItemAt(handler: @escaping (_ indexPath: IndexPath) -> Void) -> Self {
+ return update { $0.didHighlightItemAt = handler }
+ }
+ /**
+ Equivalent to implementing UICollectionViewDelegate's collectionView(_:didUnhighlightItemAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func didUnhighlightItemAt(handler: @escaping (_ indexPath: IndexPath) -> Void) -> Self {
+ return update { $0.didUnhighlightItemAt = handler }
+ }
+ /**
+ Equivalent to implementing UICollectionViewDelegate's collectionView(_:shouldSelectItemAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func shouldSelectItemAt(handler: @escaping (_ indexPath: IndexPath) -> Bool) -> Self {
+ return update { $0.shouldSelectItemAt = handler }
+ }
+ /**
+ Equivalent to implementing UICollectionViewDelegate's collectionView(_:shouldDeselectItemAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func shouldDeselectItemAt(handler: @escaping (_ indexPath: IndexPath) -> Bool) -> Self {
+ return update { $0.shouldDeselectItemAt = handler }
+ }
+ /**
+ Equivalent to implementing UICollectionViewDelegate's collectionView(_:didSelectItemAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func didSelectItemAt(handler: @escaping (_ indexPath: IndexPath) -> Void) -> Self {
+ return update { $0.didSelectItemAt = handler }
+ }
+ /**
+ Equivalent to implementing UICollectionViewDelegate's collectionView(_:didDeselectItemAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func didDeselectItemAt(handler: @escaping (_ indexPath: IndexPath) -> Void) -> Self {
+ return update { $0.didDeselectItemAt = handler }
+ }
+ /**
+ Equivalent to implementing UICollectionViewDelegate's collectionView(_:willDisplay:forItemAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func willDisplay(handler: @escaping (_ cell: UICollectionViewCell, _ indexPath: IndexPath) -> Void) -> Self {
+ return update { $0.willDisplay = handler }
+ }
+ /**
+ Equivalent to implementing UICollectionViewDelegate's collectionView(_:willDisplaySupplementaryView:forElementKind:at:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func willDisplaySupplementaryView(handler: @escaping (_ elementKind: String, _ indexPath: IndexPath) -> Void) -> Self {
+ return update { $0.willDisplaySupplementaryView = handler }
+ }
+ /**
+ Equivalent to implementing UICollectionViewDelegate's collectionView(_:didEndDisplaying:forItemAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func didEndDisplaying(handler: @escaping (_ cell: UICollectionViewCell, _ indexPath: IndexPath) -> Void) -> Self {
+ return update { $0.didEndDisplaying = handler }
+ }
+ /**
+ Equivalent to implementing UICollectionViewDelegate's collectionView(_:didEndDisplayingSupplementaryView:forElementOfKind:at:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func didEndDisplayingSupplementaryView(handler: @escaping (_ elementKind: String, _ indexPath: IndexPath) -> Void) -> Self {
+ return update { $0.didEndDisplayingSupplementaryView = handler }
+ }
+ /**
+ Equivalent to implementing UICollectionViewDelegate's collectionView(_:shouldShowMenuForItemAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func shouldShowMenuForItemAt(handler: @escaping (_ indexPath: IndexPath) -> Bool) -> Self {
+ return update { $0.shouldShowMenuForItemAt = handler }
+ }
+ /**
+ Equivalent to implementing UICollectionViewDelegate's collectionView(_:canPerformAction:forItemAt:withSender:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func canPerformAction(handler: @escaping (_ action: Selector, _ indexPath: IndexPath, _ sender: Any?) -> Bool) -> Self {
+ return update { $0.canPerformAction = handler }
+ }
+ /**
+ Equivalent to implementing UICollectionViewDelegate's collectionView(_:performAction:forItemAt:withSender:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func performAction(handler: @escaping (_ action: Selector, _ indexPath: IndexPath, _ sender: Any?) -> Void) -> Self {
+ return update { $0.performAction = handler }
+ }
+ /**
+ Equivalent to implementing UICollectionViewDelegate's collectionView(_:transitionLayoutForOldLayout:newLayout:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func transitionLayoutForOldLayout(handler: @escaping (_ fromLayout: UICollectionViewLayout, _ toLayout: UICollectionViewLayout) -> UICollectionViewTransitionLayout) -> Self {
+ return update { $0.transitionLayoutForOldLayout = handler }
+ }
+ /**
+ Equivalent to implementing UICollectionViewDelegate's collectionView(_:canFocusItemAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func canFocusItemAt(handler: @escaping (_ indexPath: IndexPath) -> Bool) -> Self {
+ return update { $0.canFocusItemAt = handler }
+ }
+ /**
+ Equivalent to implementing UICollectionViewDelegate's collectionView(_:shouldUpdateFocusIn:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func shouldUpdateFocusIn(handler: @escaping (_ context: UICollectionViewFocusUpdateContext) -> Bool) -> Self {
+ return update { $0.shouldUpdateFocusIn = handler }
+ }
+ /**
+ Equivalent to implementing UICollectionViewDelegate's collectionView(_:didUpdateFocusIn:with:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func didUpdateFocusIn(handler: @escaping (_ context: UICollectionViewFocusUpdateContext, _ coordinator: UIFocusAnimationCoordinator) -> Void) -> Self {
+ return update { $0.didUpdateFocusIn = handler }
+ }
+ /**
+ Equivalent to implementing UICollectionViewDelegate's indexPathForPreferredFocusedVies(in:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func indexPathForPreferredFocusedViewIn(handler: @escaping () -> IndexPath?) -> Self {
+ return update { $0.indexPathForPreferredFocusedViewIn = handler }
+ }
+ /**
+ Equivalent to implementing UICollectionViewDelegate's collectionView(_:targetIndexPathForMoveFromItemAt:toProposedIndexPath:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func targetIndexPathForMoveFromItemAt(handler: @escaping (_ originalIndexPath: IndexPath, _ proposedIndexPath: IndexPath) -> IndexPath) -> Self {
+ return update { $0.targetIndexPathForMoveFromItemAt = handler }
+ }
+ /**
+ Equivalent to implementing UICollectionViewDelegate's collectionView(_:targetContentOffsetForProposedContentOffset:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func targetContentOffsetForProposedContentOffset(handler: @escaping (_ proposedContentOffset: CGPoint) -> CGPoint) -> Self {
+ return update { $0.targetContentOffsetForProposedContentOffset = handler }
+ }
+ /**
+ Equivalent to implementing UICollectionViewDelegate's collectionView(_:shouldSpringLoadItemAt:with:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @available(iOS 11, *) @discardableResult
+ public func shouldSpringLoadItemAt(handler: @escaping (_ indexPath: IndexPath, _ context: UISpringLoadedInteractionContext) -> Bool) -> Self {
+ return update { $0.shouldSpringLoadItemAt = handler }
+ }
+ /**
+ Equivalent to implementing UICollectionViewDataSource's collectionView(_:numberOfItemsInSection:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent dataSource method
+
+ * returns: itself so you can daisy chain the other dataSource calls
+ */
+ @discardableResult
+ public func numberOfItemsInSection(handler: @escaping (_ section: Int) -> Int) -> Self {
+ return update { $0.numberOfItemsInSection = handler }
+ }
+ /**
+ Equivalent to implementing UICollectionViewDataSource's collectionView(_:cellForItemAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent dataSource method
+
+ * returns: itself so you can daisy chain the other dataSource calls
+ */
+ @discardableResult
+ public func cellForItemAt(handler: @escaping (_ indexPath: IndexPath) -> UICollectionViewCell) -> Self {
+ return update { $0.cellForItemAt = handler }
+ }
+ /**
+ Equivalent to implementing UICollectionViewDataSource's numberOfSections(in:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent dataSource method
+
+ * returns: itself so you can daisy chain the other dataSource calls
+ */
+ @discardableResult
+ public func numberOfSectionsIn(handler: @escaping () -> Int) -> Self {
+ return update { $0.numberOfSectionsIn = handler }
+ }
+ /**
+ Equivalent to implementing UICollectionViewDataSource's collectionView(_:viewForSupplementaryElementOfKind:at:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent dataSource method
+
+ * returns: itself so you can daisy chain the other dataSource calls
+ */
+ @discardableResult
+ public func viewForSupplementaryElementOfKind(handler: @escaping (_ kind: String, _ indexPath: IndexPath) -> UICollectionReusableView) -> Self {
+ return update { $0.viewForSupplementaryElementOfKind = handler }
+ }
+ /**
+ Equivalent to implementing UICollectionViewDataSource's collectionView(_:canMoveItemAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent dataSource method
+
+ * returns: itself so you can daisy chain the other dataSource calls
+ */
+ @discardableResult
+ public func canMoveItemAt(handler: @escaping (_ indexPath: IndexPath) -> Bool) -> Self {
+ return update { $0.canMoveItemAt = handler }
+ }
+ /**
+ Equivalent to implementing UICollectionViewDataSource's collectionView(_:moveItemAt:to:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent dataSource method
+
+ * returns: itself so you can daisy chain the other dataSource calls
+ */
+ @discardableResult
+ public func moveItemAt(handler: @escaping (_ sourceIndexPath: IndexPath, _ destinationIndexPath: IndexPath) -> Void) -> Self {
+ return update { $0.moveItemAt = handler }
+ }
+ /**
+ Equivalent to implementing UICollectionViewDataSource's indexTitles(for:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent dataSource method
+
+ * returns: itself so you can daisy chain the other dataSource calls
+ */
+ @discardableResult
+ public func indexTitlesFor(handler: @escaping () -> [String]) -> Self {
+ return update { $0.indexTitlesFor = handler }
+ }
+ /**
+ Equivalent to implementing UICollectionViewDataSource's collectionView(_:indexPathForIndexTitle:at:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent dataSource method
+
+ * returns: itself so you can daisy chain the other dataSource calls
+ */
+ @discardableResult
+ public func indexPathForIndexTitle(handler: @escaping (_ title: String, _ index: Int) -> IndexPath) -> Self {
+ return update { $0.indexPathForIndexTitle = handler }
+ }
+ /**
+ Equivalent to implementing UICollectionViewDelegateFlowLayout's collectionView(_:layout:sizeForItemAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func sizeForItemAt(handler: @escaping (_ indexPath: IndexPath) -> CGSize) -> Self {
+ return update { $0.sizeForItemAt = handler }
+ }
+ /**
+ Equivalent to implementing UICollectionViewDelegateFlowLayout's collectionV(_:layout:insetForSectionAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func insetForSectionAt(handler: @escaping (_ section: Int) -> UIEdgeInsets) -> Self {
+ return update { $0.insetForSectionAt = handler }
+ }
+ /**
+ Equivalent to implementing UICollectionViewDelegateFlowLayout's collectionView(_:layout:minimumLineSpacingForSectionAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func minimumLineSpacingForSectionAt(handler: @escaping (_ section: Int) -> CGFloat) -> Self {
+ return update { $0.minimumLineSpacingForSectionAt = handler }
+ }
+ /**
+ Equivalent to implementing UICollectionViewDelegateFlowLayout's collectionView(_:layout:minimumInteritemSpacingForSectionAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func minimumInteritemSpacingForSectionAt(handler: @escaping (_ section: Int) -> CGFloat) -> Self {
+ return update { $0.minimumInteritemSpacingForSectionAt = handler }
+ }
+ /**
+ Equivalent to implementing UICollectionViewDelegateFlowLayout's collectionView(_:layout:referenceSizeForHeaderInSection:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func referenceSizeForHeaderInSection(handler: @escaping (_ section: Int) -> CGSize) -> Self {
+ return update { $0.referenceSizeForHeaderInSection = handler }
+ }
+ /**
+ Equivalent to implementing UICollectionViewDelegateFlowLayout's collectionView(_:layout:referenceSizeForFooterInSection:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func referenceSizeForFooterInSection(handler: @escaping (_ section: Int) -> CGSize) -> Self {
+ return update { $0.referenceSizeForFooterInSection = handler }
+ }
+}
+
+extension UICollectionView {
+ @discardableResult
+ @objc override func update(handler: (_ delegate: CollectionViewDelegate) -> Void) -> Self {
+ DelegateWrapper.update(self,
+ delegate: CollectionViewDelegate(),
+ delegates: &CollectionViewDelegate.delegates,
+ bind: UICollectionView.bind) {
+ handler($0.delegate)
+ }
+ return self
+ }
+
+ // MARK: Reset
+ /**
+ Clears any delegate/dataSource closures that were assigned to this
+ `UICollectionView`. This cleans up memory as well as sets the
+ delegate/dataSource properties to nil. This is typically only used for explicit
+ cleanup. You are not required to call this method.
+ */
+ @objc override public func clearClosureDelegates() {
+ DelegateWrapper.remove(delegator: self, from: &CollectionViewDelegate.delegates)
+ UICollectionView.bind(self, nil)
+ }
+
+ fileprivate static func bind(_ delegator: UICollectionView, _ delegate: CollectionViewDelegate?) {
+ delegator.delegate = nil
+ delegator.dataSource = nil
+ delegator.delegate = delegate
+ delegator.dataSource = delegate
+ }
+}
diff --git a/Xcode/Closures/Source/UIControl.swift b/Xcode/Closures/Source/UIControl.swift
new file mode 100644
index 0000000..be598f5
--- /dev/null
+++ b/Xcode/Closures/Source/UIControl.swift
@@ -0,0 +1,672 @@
+/**
+ The MIT License (MIT)
+ Copyright (c) 2017 Vincent Hesener
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+ is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+import UIKit
+
+fileprivate extension UIControl {
+ fileprivate func _onChange(
+ callerHandler: @escaping (_ value: ValueType) -> (Void),
+ valueHandler: @escaping (_ control: ControlType) -> (ValueType)) {
+ on(.valueChanged) { control, _ in
+ guard let castedControl = control as? ControlType else {
+ return
+ }
+ callerHandler(valueHandler(castedControl))
+ }
+ }
+}
+
+extension UIControl {
+ public typealias EventHandler = (_ sender: UIControl, _ forEvent: UIEvent?) -> Void
+ /**
+ Provide a handler that will be called for UIControlEvents option passed in.
+
+ * * * *
+ #### To use this method just call it from a UIControl instance like so:
+ ```swift
+ myButton.on(.touchUpInside) { control, event in
+ <# button tapped code #>
+ }
+ ```
+
+ * warning: You must only pass in one UIControlEvents option to this method. Providing
+ something such as `[.touchUpOutside, .touchUpInside]` is currently not
+ supported yet.
+
+ * parameter events: The UIControlEvents option to listen for
+ * parameter handler: The callback closure you wish to be called for this event
+
+ * returns: itself so you can daisy chain the other event handler calls
+ */
+ @discardableResult
+ public func on(_ events: UIControlEvents, handler: @escaping EventHandler) -> Self {
+ guard let selector = selectorSignature(for: events) else {
+ return self
+ }
+ removeTarget(self, action: selector, for: events)
+ addTarget(self, action: selector, for: events)
+ NotificationCenter.selfObserve(
+ name: notificationName(for: events),
+ target: self) {
+ handler($0, $1?[UIControl.eventKey] as? UIEvent)
+ }
+ return self
+ }
+
+ /**
+ Methods to react to all the UIControlEvents types. Sadly, these cannot be
+ combined into one method because UIKit does not send the UIControlEvents type
+ in the target selector anywhere.
+ */
+ @objc func touchDown(sender: UIControl, event: UIEvent?) {
+ trigger(sender, event, for: .touchDown)
+ }
+ @objc func touchDownRepeat(sender: UIControl, event: UIEvent?) {
+ trigger(sender, event, for: .touchDownRepeat)
+ }
+ @objc func touchDragInside(sender: UIControl, event: UIEvent?) {
+ trigger(sender, event, for: .touchDragInside)
+ }
+ @objc func touchDragOutside(sender: UIControl, event: UIEvent?) {
+ trigger(sender, event, for: .touchDragOutside)
+ }
+ @objc func touchDragEnter(sender: UIControl, event: UIEvent?) {
+ trigger(sender, event, for: .touchDragEnter)
+ }
+ @objc func touchDragExit(sender: UIControl, event: UIEvent?) {
+ trigger(sender, event, for: .touchDragExit)
+ }
+ @objc func touchUpInside(sender: UIControl, event: UIEvent?) {
+ trigger(sender, event, for: .touchUpInside)
+ }
+ @objc func touchUpOutside(sender: UIControl, event: UIEvent?) {
+ trigger(sender, event, for: .touchUpOutside)
+ }
+ @objc func touchCancel(sender: UIControl, event: UIEvent?) {
+ trigger(sender, event, for: .touchCancel)
+ }
+ @objc func valueChanged(sender: UIControl, event: UIEvent?) {
+ trigger(sender, event, for: .valueChanged)
+ }
+ @objc func primaryActionTriggered(sender: UIControl, event: UIEvent?) {
+ trigger(sender, event, for: .primaryActionTriggered)
+ }
+ @objc func editingDidBegin(sender: UIControl, event: UIEvent?) {
+ trigger(sender, event, for: .editingDidBegin)
+ }
+ @objc func editingChanged(sender: UIControl, event: UIEvent?) {
+ trigger(sender, event, for: .editingChanged)
+ }
+ @objc func editingDidEnd(sender: UIControl, event: UIEvent?) {
+ trigger(sender, event, for: .editingDidEnd)
+ }
+ @objc func editingDidEndOnExit(sender: UIControl, event: UIEvent?) {
+ trigger(sender, event, for: .editingDidEndOnExit)
+ }
+ @objc func allTouchEvents(sender: UIControl, event: UIEvent?) {
+ trigger(sender, event, for: .allTouchEvents)
+ }
+ @objc func allEditingEvents(sender: UIControl, event: UIEvent?) {
+ trigger(sender, event, for: .allEditingEvents)
+ }
+ @objc func applicationReserved(sender: UIControl, event: UIEvent?) {
+ trigger(sender, event, for: .applicationReserved)
+ }
+ @objc func systemReserved(sender: UIControl, event: UIEvent?) {
+ trigger(sender, event, for: .systemReserved)
+ }
+ @objc func allEvents(sender: UIControl, event: UIEvent?) {
+ trigger(sender, event, for: .allEvents)
+ }
+
+ /// Used to pass the event through the notification userInfo object
+ private static let eventKey = String.namespace + ".notificcations.keys.event"
+
+ /**
+ Provide a handler that will be called for UIControlEvents option passed in.
+
+ * parameter sender: The UIControl
+ * parameter event: The UIControlEvents option to listen for
+ * parameter type: The UIControlEvents option to listen for
+
+ */
+ private func trigger(_ sender: UIControl, _ event: UIEvent?, for type: UIControlEvents) {
+ NotificationCenter.closures.post(name: notificationName(for: type),
+ object: self,
+ userInfo: [UIControl.eventKey: event as Any])
+ }
+
+ /**
+ Creates a value for Notification that represents the event type
+
+ * parameter events: The UIControleEvents option identifying the
+ type of notification
+
+ * returns: An identifyable Notification.Name
+ */
+ private func notificationName(for events: UIControlEvents) -> Notification.Name {
+ return Notification.Name("\(String.namespace).notifications.names.UIControlTargetAction.\(events)")
+ }
+}
+
+extension UIControl {
+ /**
+ Provides the proper internal selector to call for the type of UIControlEvent
+ passed in.
+
+ * parameter event: The UIControlEvents option to grab for the selector
+
+ * returns: The selector
+ */
+ fileprivate func selectorSignature(for event: UIControlEvents) -> Selector? {
+ switch event {
+ case UIControlEvents.touchDown:
+ return #selector(touchDown(sender:event:))
+ case UIControlEvents.touchDownRepeat:
+ return #selector(touchDownRepeat(sender:event:))
+ case UIControlEvents.touchDragInside:
+ return #selector(touchDragInside(sender:event:))
+ case UIControlEvents.touchDragOutside:
+ return #selector(touchDragOutside(sender:event:))
+ case UIControlEvents.touchDragEnter:
+ return #selector(touchDragEnter(sender:event:))
+ case UIControlEvents.touchDragExit:
+ return #selector(touchDragExit(sender:event:))
+ case UIControlEvents.touchUpInside:
+ return #selector(touchUpInside(sender:event:))
+ case UIControlEvents.touchUpOutside:
+ return #selector(touchUpOutside(sender:event:))
+ case UIControlEvents.touchCancel:
+ return #selector(touchCancel(sender:event:))
+ case UIControlEvents.valueChanged:
+ return #selector(valueChanged(sender:event:))
+ case UIControlEvents.primaryActionTriggered:
+ return #selector(primaryActionTriggered(sender:event:))
+ case UIControlEvents.editingDidBegin:
+ return #selector(editingDidBegin(sender:event:))
+ case UIControlEvents.editingChanged:
+ return #selector(editingChanged(sender:event:))
+ case UIControlEvents.editingDidEnd:
+ return #selector(editingDidEnd(sender:event:))
+ case UIControlEvents.editingDidEndOnExit:
+ return #selector(editingDidEndOnExit(sender:event:))
+ case UIControlEvents.allTouchEvents:
+ return #selector(allTouchEvents(sender:event:))
+ case UIControlEvents.allEditingEvents:
+ return #selector(allEditingEvents(sender:event:))
+ case UIControlEvents.applicationReserved:
+ return #selector(applicationReserved(sender:event:))
+ case UIControlEvents.systemReserved:
+ return #selector(systemReserved(sender:event:))
+ case UIControlEvents.allEvents:
+ return #selector(allEvents(sender:event:))
+ default:
+ return nil
+ }
+ }
+}
+
+extension UIButton {
+ // MARK: Common Events
+ /**
+ A convenience method wrapping a typical use for UIButton's
+ UIControlEvents.touchUpInside event. This allows a parameterless
+ closure to be called when the UIButton is tapped.
+
+ * * * *
+ #### An example of calling this method:
+ ```swift
+ myButton.onTap {
+ <# button tapped code #>
+ }
+ ```
+
+ * parameter handler: The closure to be called on the button tapped event
+
+ * returns: itself so you can daisy chain the other event handler calls
+ */
+ @discardableResult
+ public func onTap(handler: @escaping () -> Void) -> Self {
+ on(.touchUpInside) { _,_ in
+ handler()
+ }
+ return self
+ }
+}
+
+extension UITextField {
+ // MARK: Common Events
+ /**
+ A convenience method wrapping a typical use for UITextField's
+ UIControlEvents.editingChanged event. This will send the new
+ value `text` after it has been modified.
+
+ * * * *
+ #### An example of calling this method:
+ ```swift
+ myTextField.onChange { newText in
+ <#text changed code#>
+ }
+ ```
+ * Note:
+ This callback provides a non-optional String, even though UITextField.text can be `nil`.
+ For that reason, it is technically not exactly the text in the `text` property, but
+ rather a convenience so you don't have to unwrap it. If `nil` is an important distinction
+ for you to have, please use the text field's `text` property directly or use
+ `on(_:handler:)` and obtain the `text` property from the sender parameter.
+
+ * parameter handler: The closure to be called on the text changed event
+
+ * returns: itself so you can daisy chain the other event handler calls
+ */
+ @discardableResult
+ public func onChange(handler: @escaping (_ text: String) -> Void) -> Self {
+ on(.editingChanged) { sender, _ in
+ guard let textField = sender as? UITextField, let text = textField.text else {
+ handler("")
+ return
+ }
+ handler(text)
+ }
+ return self
+ }
+
+ /**
+ A convenience method to respond to UIControlEvents.editingDidEnd for a UITextField.
+ This occurs when the UITextField resigns as first responder.
+
+ * parameter handler: The closure to be called when the focus leaves the text field
+
+ * returns: itself so you can daisy chain the other event handler calls
+ */
+ @discardableResult
+ public func onEditingEnded(handler: @escaping () -> Void) -> Self {
+ on(.editingDidEnd) { _,_ in
+ handler()
+ }
+ return self
+ }
+
+ /**
+ A convenience method to respond to UIControlEvents.editingDidBegin for a UITextField.
+ This occurs when the UITextField becomes the first responder.
+
+ * parameter handler: The closure to be called when the focus enters the text field
+
+ * returns: itself so you can daisy chain the other event handler calls
+ */
+ @discardableResult
+ public func onEditingBegan(handler: @escaping () -> Void) -> Self {
+ on(.editingDidBegin) { _,_ in
+ handler()
+ }
+ return self
+ }
+
+ /**
+ A convenience method to respond to UIControlEvents.editingDidEndOnExit for a UITextField.
+ This occurs when the user taps the return key on the keyboard.
+
+ * parameter handler: The closure to be called when the return key is tapped
+
+ * returns: itself so you can daisy chain the other event handler calls
+ */
+ @discardableResult
+ public func onReturn(handler: @escaping () -> Void) -> Self {
+ on(.editingDidEndOnExit) { _,_ in
+ handler()
+ }
+ return self
+ }
+}
+
+extension UITextField {
+ // MARK: Delegate Overrides
+ /**
+ This method determines if the text field should begin editing. This is equivalent
+ to implementing the textFieldShouldBeginEditing(_:) method in UITextFieldDelegate.
+
+ * parameter handler: The closure that determines whether the text field should begin editing.
+
+ * returns: Returns itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func shouldBeginEditing(handler: @escaping () -> Bool) -> Self {
+ return update { $0.shouldBeginEditing = handler }
+ }
+
+ /**
+ This method determines if the text field did begin editing. This is equivalent
+ to implementing the textFieldDidBeginEditing(_:) method in UITextFieldDelegate.
+
+ * parameter handler: The closure that determines whether the text field did begin editing.
+
+ * returns: Returns itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func didBeginEditing(handler: @escaping () -> Void) -> Self {
+ return update { $0.didBeginEditing = handler }
+ }
+
+ /**
+ This method determines if the text field should end editing. This is equivalent
+ to implementing the textFieldShouldEndEditing(_:) method in UITextFieldDelegate.
+
+ * parameter handler: The closure that determines whether the text field should end editing.
+
+ * returns: Returns itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func shouldEndEditing(handler: @escaping () -> Bool) -> Self {
+ return update { $0.shouldEndEditing = handler }
+ }
+
+ /**
+ This method determines if the text field did end editing. This is equivalent
+ to implementing the textFieldDidEndEditing(_:) method in UITextFieldDelegate.
+
+ * parameter handler: The closure that determines whether the text field did end editing.
+
+ * returns: Returns itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func didEndEditing(handler: @escaping () -> Void) -> Self {
+ return update { $0.didEndEditing = handler }
+ }
+
+ /**
+ This method determines if the text field should change its characters based on user input.
+ This is equivalent to implementing the textField(_:shouldChangeCharactersIn:replacementString:)
+ method in UITextFieldDelegate. This closure passed here will conflict with any closure
+ passed to `shouldChangeString(handler:)` and the last closure passed will be the one that's called.
+
+ * parameter handler: The closure that determines whether the text field should change its characters.
+
+ * returns: Returns itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func shouldChangeCharacters(handler: @escaping (_ range: NSRange, _ replacementString: String) -> Bool) -> Self {
+ return update { $0.shouldChangeCharacters = handler }
+ }
+
+ /**
+ This method determines if the text field should change its text based on user input.
+ This is a convenience method around equivalent to implementing the
+ textField(_:shouldChangeCharactersIn:replacementString:) method in UITextFieldDelegate.
+ This closure passed here will conflict with any closure passed to `shouldChangeCharacters(_:)`
+ and the last closure passed will be the one that's called.
+
+ * parameter handler: The closure that determines whether the text field should change its text `from`
+ the first parameter string `to` the second parameter string.
+
+ * returns: Returns itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func shouldChangeString(handler: @escaping (_ from: String, _ to: String) -> Bool) -> Self {
+ return shouldChangeCharacters() { [weak self] range, string in
+ guard let strongSelf = self,
+ let text = strongSelf.text else {
+ return true
+ }
+ let newString = NSString(string: text).replacingCharacters(in: range, with: string)
+ return handler(text, newString)
+ }
+ }
+
+ /**
+ This method determines if the text field should remove its text. This is equivalent
+ to implementing the textFieldShouldClear(_:) method in UITextFieldDelegate.
+
+ * parameter handler: The closure that determines whether the text field should clear its text.
+
+ * returns: Returns itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func shouldClear(handler: @escaping () -> Bool) -> Self {
+ return update { $0.shouldClear = handler }
+ }
+
+ /**
+ This method determines if the text field should process the return button. This is equivalent
+ to implementing the textFieldShouldReturn(_:) method in UITextFieldDelegate.
+
+ * parameter handler: The closure that determines whether the text field should process the return button.
+
+ * returns: Returns itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func shouldReturn(handler: @escaping () -> Bool) -> Self {
+ return update { $0.shouldReturn = handler }
+ }
+}
+
+extension UITextField: DelegatorProtocol {
+ @discardableResult
+ fileprivate func update(handler: (_ delegate: TextFieldDelegate) -> Void) -> Self {
+ DelegateWrapper.update(self,
+ delegate: TextFieldDelegate(),
+ delegates: &TextFieldDelegate.delegates,
+ bind: UITextField.bind) {
+ handler($0.delegate)
+ }
+ return self
+ }
+
+ // MARK: Reset
+ /**
+ Clears any delegate closures that were assigned to this
+ `UITextField`. This cleans up memory as well as sets the
+ delegate property to nil. This is typically only used for explicit
+ cleanup. You are not required to call this method.
+ */
+ @objc public func clearClosureDelegates() {
+ DelegateWrapper.remove(delegator: self, from: &TextFieldDelegate.delegates)
+ UITextField.bind(self, nil)
+ }
+
+ fileprivate static func bind(_ delegator: UITextField, _ delegate: TextFieldDelegate?) {
+ delegator.delegate = nil
+ delegator.delegate = delegate
+ }
+}
+
+#if DEBUG
+var textFieldDelegates: Set {
+ return TextFieldDelegate.delegates
+}
+#endif
+
+fileprivate final class TextFieldDelegate: NSObject, UITextFieldDelegate, DelegateProtocol {
+ fileprivate static var delegates = Set>()
+
+ override required init() {
+ super.init()
+ }
+
+ fileprivate var shouldBeginEditing: (() -> Bool)?
+ fileprivate func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
+ return shouldBeginEditing?() ?? true
+ }
+
+ fileprivate var didBeginEditing: (() -> Void)?
+ fileprivate func textFieldDidBeginEditing(_ textField: UITextField) {
+ didBeginEditing?()
+ }
+
+ fileprivate var shouldEndEditing: (() -> Bool)?
+ fileprivate func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
+ return shouldEndEditing?() ?? true
+ }
+
+ fileprivate var didEndEditing: (() -> Void)?
+ fileprivate func textFieldDidEndEditing(_ textField: UITextField) {
+ didEndEditing?()
+ }
+
+ fileprivate var shouldChangeCharacters: (( _ range: NSRange, _ replacementString: String) -> Bool)?
+ fileprivate func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
+ return shouldChangeCharacters?(range, string) ?? true
+ }
+
+ fileprivate var shouldClear: (() -> Bool)?
+ fileprivate func textFieldShouldClear(_ textField: UITextField) -> Bool {
+ return shouldClear?() ?? true
+ }
+
+ fileprivate var shouldReturn: (() -> Bool)?
+ fileprivate func textFieldShouldReturn(_ textField: UITextField) -> Bool{
+ return shouldReturn?() ?? true
+ }
+
+ override func responds(to aSelector: Selector!) -> Bool {
+ switch aSelector {
+ case #selector(TextFieldDelegate.textFieldShouldBeginEditing(_:)):
+ return shouldBeginEditing != nil
+ case #selector(TextFieldDelegate.textFieldDidBeginEditing(_:)):
+ return didBeginEditing != nil
+ case #selector(TextFieldDelegate.textFieldShouldEndEditing(_:)):
+ return shouldEndEditing != nil
+ case #selector(TextFieldDelegate.textFieldDidEndEditing(_:)):
+ return didEndEditing != nil
+ case #selector(TextFieldDelegate.textField(_:shouldChangeCharactersIn:replacementString:)):
+ return shouldChangeCharacters != nil
+ case #selector(TextFieldDelegate.textFieldShouldClear(_:)):
+ return shouldClear != nil
+ case #selector(TextFieldDelegate.textFieldShouldReturn(_:)):
+ return shouldReturn != nil
+ default:
+ return super.responds(to: aSelector)
+ }
+ }
+}
+
+extension UISwitch {
+ // MARK: Common Events
+ /**
+ A convenience method wrapping the .onValueChanged event
+ type for UISwitch.
+
+ * parameter handler: The closure to be called on the valueChanged event
+
+ * returns: itself so you can daisy chain the other event handler calls
+ */
+ @discardableResult
+ public func onChange(handler: @escaping (_ value: Bool) -> Void) -> Self {
+ _onChange(callerHandler: handler, valueHandler: { (uiSwitch: UISwitch) -> Bool in
+ return uiSwitch.isOn
+ })
+ return self
+ }
+}
+
+extension UISlider {
+ // MARK: Common Events
+ /**
+ A convenience method wrapping the .onValueChanged event
+ type for UISlider.
+
+ * parameter handler: The closure to be called on the valueChanged event
+
+ * returns: itself so you can daisy chain the other event handler calls
+ */
+ @discardableResult
+ public func onChange(handler: @escaping (_ value: Float) -> Void) -> Self {
+ _onChange(callerHandler: handler, valueHandler: { (slider: UISlider) -> Float in
+ return slider.value
+ })
+ return self
+ }
+}
+
+extension UISegmentedControl {
+ // MARK: Common Events
+ /**
+ A convenience method wrapping the .onValueChanged event
+ type for UISegmentedControl.
+
+ * parameter handler: The closure to be called on the valueChanged event
+
+ * returns: itself so you can daisy chain the other event handler calls
+ */
+ @discardableResult
+ public func onChange(handler: @escaping (_ value: Int) -> Void) -> Self {
+ _onChange(callerHandler: handler, valueHandler: { (segmented: UISegmentedControl) -> Int in
+ return segmented.selectedSegmentIndex
+ })
+ return self
+ }
+}
+
+extension UIStepper {
+ // MARK: Common Events
+ /**
+ A convenience method wrapping the .onValueChanged event
+ type for UIStepper.
+
+ * parameter handler: The closure to be called on the valueChanged event
+
+ * returns: itself so you can daisy chain the other event handler calls
+ */
+ @discardableResult
+ public func onChange(handler: @escaping (_ value: Double) -> Void) -> Self {
+ _onChange(callerHandler: handler, valueHandler: { (stepper: UIStepper) -> Double in
+ return stepper.value
+ })
+ return self
+ }
+}
+
+extension UIPageControl {
+ // MARK: Common Events
+ /**
+ A convenience method wrapping the .onValueChanged event
+ type for UIPageControl.
+
+ * parameter handler: The closure to be called on the valueChanged event
+
+ * returns: itself so you can daisy chain the other event handler calls
+ */
+ @discardableResult
+ public func onChange(handler: @escaping (_ value: Int) -> Void) -> Self {
+ _onChange(callerHandler: handler, valueHandler: { (pager: UIPageControl) -> Int in
+ return pager.currentPage
+ })
+ return self
+ }
+}
+
+extension UIDatePicker {
+ // MARK: Common Events
+ /**
+ A convenience method wrapping the .onValueChanged event
+ type for UIDatePicker.
+
+ * parameter handler: The closure to be called on the valueChanged event
+
+ * returns: itself so you can daisy chain the other event handler calls
+ */
+ @discardableResult
+ public func onChange(handler: @escaping (_ value: Date) -> Void) -> Self {
+ _onChange(callerHandler: handler, valueHandler: { (picker: UIDatePicker) -> Date in
+ return picker.date
+ })
+ return self
+ }
+}
diff --git a/Xcode/Closures/Source/UIGestureRecognizer.swift b/Xcode/Closures/Source/UIGestureRecognizer.swift
new file mode 100644
index 0000000..858f826
--- /dev/null
+++ b/Xcode/Closures/Source/UIGestureRecognizer.swift
@@ -0,0 +1,502 @@
+/**
+ The MIT License (MIT)
+ Copyright (c) 2017 Vincent Hesener
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+ is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+import UIKit
+
+fileprivate extension UIGestureRecognizer {
+ @objc func gestureRecognized() {
+ NotificationCenter.closures.post(name: .gestureRecognized, object: self)
+ }
+}
+
+fileprivate extension Notification.Name {
+ static let gestureRecognized = Notification.Name("UIGestureRecognizer.recognized")
+}
+
+/**
+ This method is a convenience method to add a closure handler to a custom subclass of
+ UIGestureRecognizer. If creating a custom gesture recognizer, you may want to also
+ provide an initializer that takes a completion handler, just as the Closures
+ framework provides for UIKit gesture recognizers.
+
+ * * * *
+ #### An example of an initializer that adds closure support for recognition of custom gesture recognizer:
+
+ ```swift
+ class MyCustomGestureRecognizer: UIGestureRecognizer {
+ convenience init(handler: @escaping (_ gesture: MyCustomGestureRecognizer) -> Void) {
+ self.init()
+ configure(target: self, handler: handler)
+ }
+ }
+ ```
+
+ * parameter gesture: The UIGestureRecognizer that is being configured to use a closure in place of target-action.
+ * parameter handler: The closure that will be called when the gesture is recognized.
+ */
+public func configure(gesture: T, handler: @escaping (_ gesture: T) -> Void)
+ where T: UIGestureRecognizer {
+ gesture.addTarget(gesture, action: #selector(UIGestureRecognizer.gestureRecognized))
+ NotificationCenter.selfObserve(name: .gestureRecognized, target: gesture) { gesture,userInfo in
+ handler(gesture)
+ }
+}
+
+extension UITapGestureRecognizer {
+ /**
+ A convenience initializer for a UITapGestureRecognizer so that it
+ can be configured with a single line of code.
+
+ * parameter tapsRequired: Defaults UITapGestureRecognizer's `numberOfTapsRequired` property value
+ * parameter touchesRequired: Defaults UITapGestureRecognizer's `numberOfTouchesRequired` property value
+ * parameter handler: The closure that is called when the gesture is recognized
+ */
+ public convenience init(tapsRequired: Int = 1,
+ touchesRequired: Int = 1,
+ handler: @escaping (_ gesture: UITapGestureRecognizer) -> Void) {
+ self.init()
+ configure(gesture: self, handler: handler)
+ numberOfTapsRequired = tapsRequired
+ numberOfTouchesRequired = touchesRequired
+ }
+}
+
+extension UIView {
+ // MARK: Add Gesture Convenience
+ /**
+ A convenience method that adds a UITapGestureRecognizer to a view, while also
+ passing default values to its convenience initializer.
+
+ * parameter tapsRequired: Defaults UITapGestureRecognizer's `numberOfTapsRequired` property value
+ * parameter touchesRequired: Defaults UITapGestureRecognizer's `numberOfTouchesRequired` property value
+ * parameter handler: The closure that is called when the gesture is recognized
+
+ * returns: The gesture that was created so that it can be used to daisy chain other
+ customizations
+ */
+ @discardableResult
+ public func addTapGesture(tapsRequired: Int = 1,
+ touchesRequired: Int = 1,
+ handler: @escaping (_ gesture: UITapGestureRecognizer) -> Void) -> UITapGestureRecognizer {
+ let gesture = UITapGestureRecognizer(tapsRequired: tapsRequired,
+ touchesRequired: touchesRequired,
+ handler: handler)
+ addGestureRecognizer(gesture)
+ return gesture
+ }
+}
+
+extension UILongPressGestureRecognizer {
+ /**
+ A convenience initializer for a UILongPressGestureRecognizer so that it
+ can be configured with a single line of code.
+
+ * parameter tapsRequired: Defaults UILongPressGestureRecognizer's `numberOfTapsRequired` property value
+ * parameter touchesRequired: Defaults UILongPressGestureRecognizer's `numberOfTouchesRequired` property value
+ * parameter minDuration: Defaults UILongPressGestureRecognizer's `minimumPressDuration` property value
+ * parameter allowableMovement: Defaults UILongPressGestureRecognizer's `allowableMovement` property value
+ * parameter handler: The closure that is called when the gesture is recognized
+ */
+ public convenience init(tapsRequired: Int = 0,
+ touchesRequired: Int = 1,
+ minDuration: CFTimeInterval = 0.5,
+ allowableMovement: CGFloat = 10,
+ handler: @escaping (_ gesture: UILongPressGestureRecognizer) -> Void) {
+ self.init()
+ configure(gesture: self, handler: handler)
+ numberOfTapsRequired = tapsRequired
+ numberOfTouchesRequired = touchesRequired
+ minimumPressDuration = minDuration
+ self.allowableMovement = allowableMovement
+ }
+}
+
+extension UIView {
+ /**
+ A convenience method that adds a UILongPressGestureRecognizer to a view, while also
+ passing default values to its convenience initializer.
+
+ * parameter tapsRequired: Defaults UILongPressGestureRecognizer's `numberOfTapsRequired` property value
+ * parameter touchesRequired: Defaults UILongPressGestureRecognizer's `numberOfTouchesRequired` property value
+ * parameter minDuration: Defaults UILongPressGestureRecognizer's `minimumPressDuration` property value
+ * parameter allowableMovement: Defaults UILongPressGestureRecognizer's `allowableMovement` property value
+ * parameter handler: The closure that is called when the gesture is recognized
+
+ * returns: The gesture that was created so that it can be used to daisy chain other
+ customizations
+ */
+ @discardableResult
+ public func addLongPressGesture(tapsRequired: Int = 0,
+ touchesRequired: Int = 1,
+ minDuration: CFTimeInterval = 0.5,
+ allowableMovement: CGFloat = 10,
+ handler: @escaping (_ gesture: UILongPressGestureRecognizer) -> Void) -> UILongPressGestureRecognizer {
+ let gesture = UILongPressGestureRecognizer(tapsRequired: tapsRequired,
+ touchesRequired: touchesRequired,
+ minDuration: minDuration,
+ allowableMovement: allowableMovement,
+ handler: handler)
+ addGestureRecognizer(gesture)
+ return gesture
+ }
+}
+
+extension UIPinchGestureRecognizer {
+ /**
+ A convenience initializer for a UIPinchGestureRecognizer so that it
+ can be configured with a single line of code.
+
+ * parameter handler: The closure that is called when the gesture is recognized
+ */
+ public convenience init(handler: @escaping (_ gesture: UIPinchGestureRecognizer) -> Void) {
+ self.init()
+ configure(gesture: self, handler: handler)
+ }
+}
+
+extension UIView {
+ /**
+ A convenience method that adds a UIPinchGestureRecognizer to a view, while also
+ passing default values to its convenience initializer.
+
+ * parameter handler: The closure that is called when the gesture is recognized
+
+ * returns: The gesture that was created so that it can be used to daisy chain other
+ customizations
+ */
+ @discardableResult
+ public func addPinchGesture(handler: @escaping (_ gesture: UIPinchGestureRecognizer) -> Void) -> UIPinchGestureRecognizer {
+ let gesture = UIPinchGestureRecognizer(handler: handler)
+ addGestureRecognizer(gesture)
+ return gesture
+ }
+}
+
+extension UISwipeGestureRecognizer {
+ /**
+ A convenience initializer for a UISwipeGestureRecognizer so that it
+ can be configured with a single line of code.
+
+ * parameter direction: Defaults UISwipeGestureRecognizer's `direction` property value
+ * parameter touchesRequired: Defaults UISwipeGestureRecognizer's `numberOfTouchesRequired` property value
+ * parameter handler: The closure that is called when the gesture is recognized
+ */
+ public convenience init(direction: UISwipeGestureRecognizerDirection = .right,
+ touchesRequired: Int = 1,
+ handler: @escaping (_ gesture: UISwipeGestureRecognizer) -> Void) {
+ self.init()
+ configure(gesture: self, handler: handler)
+ self.direction = direction
+ self.numberOfTouchesRequired = touchesRequired
+ }
+}
+
+extension UIView {
+ /**
+ A convenience method that adds a UISwipeGestureRecognizer to a view, while also
+ passing default values to its convenience initializer.
+
+ * parameter direction: Defaults UISwipeGestureRecognizer's `direction` property value
+ * parameter touchesRequired: Defaults UISwipeGestureRecognizer's `numberOfTouchesRequired` property value
+ * parameter handler: The closure that is called when the gesture is recognized
+
+ * returns: The gesture that was created so that it can be used to daisy chain other
+ customizations
+ */
+ @discardableResult
+ public func addSwipeGesture(direction: UISwipeGestureRecognizerDirection = .right,
+ touchesRequired: Int = 1,
+ handler: @escaping (_ gesture: UISwipeGestureRecognizer) -> Void) -> UISwipeGestureRecognizer {
+ let gesture = UISwipeGestureRecognizer(direction: direction,
+ touchesRequired: touchesRequired,
+ handler: handler)
+ addGestureRecognizer(gesture)
+ return gesture
+ }
+}
+
+extension UIRotationGestureRecognizer {
+ /**
+ A convenience initializer for a UIRotationGestureRecognizer so that it
+ can be configured with a single line of code.
+
+ * parameter handler: The closure that is called when the gesture is recognized
+ */
+ public convenience init(handler: @escaping (_ gesture: UIRotationGestureRecognizer) -> Void) {
+ self.init()
+ configure(gesture: self, handler: handler)
+ }
+}
+
+extension UIView {
+ /**
+ A convenience method that adds a UIRotationGestureRecognizer to a view, while also
+ passing default values to its convenience initializer.
+
+ * parameter handler: The closure that is called when the gesture is recognized
+
+ * returns: The gesture that was created so that it can be used to daisy chain other
+ customizations
+ */
+ @discardableResult
+ public func addRotationGesture(handler: @escaping (_ gesture: UIRotationGestureRecognizer) -> Void) -> UIRotationGestureRecognizer {
+ let gesture = UIRotationGestureRecognizer(handler: handler)
+ addGestureRecognizer(gesture)
+ return gesture
+ }
+}
+
+extension UIPanGestureRecognizer {
+ /**
+ A convenience initializer for a UIPanGestureRecognizer so that it
+ can be configured with a single line of code.
+
+ * parameter minTouches: Defaults UIPanGestureRecognizer's `minimumNumberOfTouches` property value
+ * parameter maxTouches: Defaults UIPanGestureRecognizer's `maximumNumberOfTouches` property value
+ * parameter handler: The closure that is called when the gesture is recognized
+ */
+ public convenience init(minTouches: Int = 1,
+ maxTouches: Int = .max,
+ handler: @escaping (_ gesture: UIPanGestureRecognizer) -> Void) {
+ self.init()
+ configure(gesture: self, handler: handler)
+ minimumNumberOfTouches = minTouches
+ maximumNumberOfTouches = maxTouches
+ }
+}
+
+extension UIView {
+ /**
+ A convenience method that adds a UIPanGestureRecognizer to a view, while also
+ passing default values to its convenience initializer.
+
+ * parameter minTouches: Defaults UIPanGestureRecognizer's `minimumNumberOfTouches` property value
+ * parameter maxTouches: Defaults UIPanGestureRecognizer's `maximumNumberOfTouches` property value
+ * parameter handler: The closure that is called when the gesture is recognized
+
+ * returns: The gesture that was created so that it can be used to daisy chain other
+ customizations
+ */
+ @discardableResult
+ public func addPanGesture(minTouches: Int = 1,
+ maxTouches: Int = .max,
+ handler: @escaping (_ gesture: UIPanGestureRecognizer) -> Void) -> UIPanGestureRecognizer {
+ let gesture = UIPanGestureRecognizer(minTouches: minTouches,
+ maxTouches: maxTouches,
+ handler: handler)
+ addGestureRecognizer(gesture)
+ return gesture
+ }
+}
+
+extension UIScreenEdgePanGestureRecognizer {
+ /**
+ A convenience initializer for a UIScreenEdgePanGestureRecognizer so that it
+ can be configured with a single line of code.
+
+ * parameter edges: Defaults UIScreenEdgePanGestureRecognizer's `edges` property value
+ * parameter handler: The closure that is called when the gesture is recognized
+ */
+ public convenience init(edges: UIRectEdge = .all,
+ handler: @escaping (_ gesture: UIScreenEdgePanGestureRecognizer) -> Void) {
+ self.init()
+ configure(gesture: self, handler: handler)
+ self.edges = edges
+ }
+}
+
+extension UIView {
+ /**
+ A convenience method that adds a UIScreenEdgePanGestureRecognizer to a view, while also
+ passing default values to its convenience initializer.
+
+ * parameter edges: Defaults UIScreenEdgePanGestureRecognizer's `edges` property value
+ * parameter handler: The closure that is called when the gesture is recognized
+
+ * returns: The gesture that was created so that it can be used to daisy chain other
+ customizations
+ */
+ @discardableResult
+ public func addScreenEdgePanGesture(edges: UIRectEdge = .all,
+ handler: @escaping (_ gesture: UIScreenEdgePanGestureRecognizer) -> Void) -> UIScreenEdgePanGestureRecognizer {
+ let gesture = UIScreenEdgePanGestureRecognizer(edges: edges,
+ handler: handler)
+ addGestureRecognizer(gesture)
+ return gesture
+ }
+}
+
+fileprivate final class GestureRecognizerDelegate: NSObject, UIGestureRecognizerDelegate, DelegateProtocol {
+ static var delegates = Set>()
+
+ fileprivate var shouldBegin: (() -> Bool)?
+ fileprivate func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
+ return shouldBegin?() ?? true
+ }
+
+ fileprivate var shouldRecognizeSimultaneouslyWith: ((_ otherGestureRecognizer: UIGestureRecognizer) -> Bool)?
+ fileprivate func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
+ return shouldRecognizeSimultaneouslyWith?(otherGestureRecognizer) ?? false
+ }
+
+ fileprivate var shouldRequireFailureOf: ((_ otherGestureRecognizer: UIGestureRecognizer) -> Bool)?
+ fileprivate func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
+ return shouldRequireFailureOf?(otherGestureRecognizer) ?? false
+ }
+
+ fileprivate var shouldBeRequiredToFailBy: ((_ otherGestureRecognizer: UIGestureRecognizer) -> Bool)?
+ fileprivate func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
+ return shouldBeRequiredToFailBy?(otherGestureRecognizer) ?? false
+ }
+
+ fileprivate var shouldReceiveTouch: ((_ touch: UITouch) -> Bool)?
+ fileprivate func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
+ return shouldReceiveTouch?(touch) ?? true
+ }
+
+ fileprivate var shouldReceivePress: ((_ press: UIPress) -> Bool)?
+ fileprivate func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive press: UIPress) -> Bool {
+ return shouldReceivePress?(press) ?? true
+ }
+
+ override func responds(to aSelector: Selector!) -> Bool {
+ switch aSelector {
+ case #selector(GestureRecognizerDelegate.gestureRecognizerShouldBegin(_:)):
+ return shouldBegin != nil
+ case #selector(GestureRecognizerDelegate.gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:)):
+ return shouldRecognizeSimultaneouslyWith != nil
+ case #selector(GestureRecognizerDelegate.gestureRecognizer(_:shouldRequireFailureOf:)):
+ return shouldRequireFailureOf != nil
+ case #selector(GestureRecognizerDelegate.gestureRecognizer(_:shouldBeRequiredToFailBy:)):
+ return shouldBeRequiredToFailBy != nil
+ case #selector((GestureRecognizerDelegate.gestureRecognizer(_:shouldReceive:)) as (GestureRecognizerDelegate) -> (UIGestureRecognizer, UITouch) -> (Bool)):
+ return shouldReceiveTouch != nil
+ case #selector(GestureRecognizerDelegate.gestureRecognizer(_:shouldReceive:) as (GestureRecognizerDelegate) -> (UIGestureRecognizer, UIPress) -> (Bool)):
+ return shouldReceivePress != nil
+ default:
+ return super.responds(to: aSelector)
+ }
+ }
+}
+
+extension UIGestureRecognizer {
+ // MARK: Delegate Overrides
+ /**
+ Equivalent to implementing UIGestureRecognizerDelegate's gestureRecognizerShouldBegin(:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func shouldBegin(handler: @escaping () -> Bool) -> Self {
+ return update { $0.shouldBegin = handler }
+ }
+
+ /**
+ Equivalent to implementing UIGestureRecognizerDelegate's gestureRecognizer(:shouldRecognizeSimultaneouslyWith:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func shouldRecognizeSimultaneouslyWith(handler: @escaping (_ otherGestureRecognizer: UIGestureRecognizer) -> Bool) -> Self {
+ return update { $0.shouldRecognizeSimultaneouslyWith = handler }
+ }
+
+ /**
+ Equivalent to implementing UIGestureRecognizerDelegate's gestureRecognizer(:shouldRequireFailureOf:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func shouldRequireFailureOf(handler: @escaping (_ otherGestureRecognizer: UIGestureRecognizer) -> Bool) -> Self {
+ return update { $0.shouldRequireFailureOf = handler }
+ }
+
+ /**
+ Equivalent to implementing UIGestureRecognizerDelegate's gestureRecognizer(:shouldBeRequiredToFailBy:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func shouldBeRequiredToFailBy(handler: @escaping (_ otherGestureRecognizer: UIGestureRecognizer) -> Bool) -> Self {
+ return update { $0.shouldBeRequiredToFailBy = handler }
+ }
+
+ /**
+ Equivalent to implementing UIGestureRecognizerDelegate's gestureRecognizer(:shouldReceive:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func shouldReceiveTouch(handler: @escaping (_ touch: UITouch) -> Bool) -> Self {
+ return update { $0.shouldReceiveTouch = handler }
+ }
+
+ /**
+ Equivalent to implementing UIGestureRecognizerDelegate's gestureRecognizer(:shouldReceive:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func shouldReceivePress(handler: @escaping (_ press: UIPress) -> Bool) -> Self {
+ return update { $0.shouldReceivePress = handler }
+ }
+}
+
+extension UIGestureRecognizer: DelegatorProtocol {
+ @discardableResult
+ fileprivate func update(handler: (_ delegate: GestureRecognizerDelegate) -> Void) -> Self {
+ DelegateWrapper.update(self,
+ delegate: GestureRecognizerDelegate(),
+ delegates: &GestureRecognizerDelegate.delegates,
+ bind: UIGestureRecognizer.bind) {
+ handler($0.delegate)
+ }
+ return self
+ }
+
+ // MARK: Reset
+ /**
+ Clears any delegate closures that were assigned to this
+ `UIGestureRecognizer`. This cleans up memory as well as sets the
+ delegate property to nil. This is typically only used for explicit
+ cleanup. You are not required to call this method.
+ */
+ @objc public func clearClosureDelegates() {
+ DelegateWrapper.remove(delegator: self, from: &GestureRecognizerDelegate.delegates)
+ UIGestureRecognizer.bind(self, nil)
+ }
+
+ fileprivate static func bind(_ delegator: UIGestureRecognizer, _ delegate: GestureRecognizerDelegate?) {
+ delegator.delegate = nil
+ delegator.delegate = delegate
+ }
+}
diff --git a/Xcode/Closures/Source/UIImagePickerController.swift b/Xcode/Closures/Source/UIImagePickerController.swift
new file mode 100644
index 0000000..049e1b6
--- /dev/null
+++ b/Xcode/Closures/Source/UIImagePickerController.swift
@@ -0,0 +1,367 @@
+/**
+ The MIT License (MIT)
+ Copyright (c) 2017 Vincent Hesener
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+ is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+import UIKit
+import MobileCoreServices
+import PhotosUI
+
+fileprivate final class ImagePickerControllerDelegate: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate, DelegateProtocol {
+ static var delegates = Set>()
+
+ fileprivate var didFinishPickingMedia: ((_ withInfo: [String: Any]) -> Void)?
+ fileprivate func imagePickerController(_ picker: UIImagePickerController,
+ didFinishPickingMediaWithInfo info: [String: Any]) {
+ didFinishPickingMedia?(info)
+ }
+
+ fileprivate var didCancel: (() -> Void)?
+ fileprivate func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
+ didCancel?()
+ }
+
+ override func responds(to aSelector: Selector!) -> Bool {
+ switch aSelector {
+ case #selector(ImagePickerControllerDelegate.imagePickerController(_:didFinishPickingMediaWithInfo:)):
+ return didFinishPickingMedia != nil
+ case #selector(ImagePickerControllerDelegate.imagePickerControllerDidCancel(_:)):
+ return didCancel != nil
+ default:
+ return super.responds(to: aSelector)
+ }
+ }
+}
+
+extension UIImagePickerController {
+ // MARK: Convenient Presenting
+ /**
+ This is a convenience initializer that allows you to setup your
+ UIImagePickerController with some commonly used default settings.
+ In addition, it provides a default handler for a cancelled picker,
+ but you are free to provide your own `didCancel` implementation
+ using the `didCancel` parameter. The only required parameter is
+ the `didPick` closure, which will be called when the user picks
+ a medium.
+
+ * important: The picker will not be attempted to be dismissed
+ after this closure is called, unless you use the
+ `present(from:animation:)` method.
+
+ * * * *
+ #### An example of initializing with this method:
+ ```swift
+ let picker = UIImagePickerController(
+ source: .camera,
+ allow: [.image, .movie]) { [weak self] result,picker in
+ myImageView.image = result.originalImage
+ self?.dismiss(animated: animation.animate)
+ }
+ }
+ ```
+
+ * parameter source: The `UIImagePickerControllerSourceType` that
+ UIImagePickerController expects to determine where the content
+ should be obtained from. This value will be applied to the
+ picker's `sourceType` property.
+ * parameter allow: A convenience `OptionSet` filter based on `kUTType` Strings.
+ These types will be applied to the picker's mediaTypes property.
+ * parameter cameraOverlay: This is equivalent to `UIImagePickerController`'s
+ `cameraOverlayView` property and will be applied as such.
+ * parameter showsCameraControls: This is equivalent to `UIImagePickerController`'s
+ `showsCameraControls` property and will be applied as such.
+ * parameter didCancel: The closure that is called when the `didCancel` delegate
+ method is called. The default is to attempt to dismiss the picker.
+ * parameter didPick: The closure that is called when the
+ imagePickerController(_:didFinishPickingMediaWithInfo:) is called.
+ The closure conveniently wraps the Dictionary obtained from this
+ delegate call in a `Result` struct. The original Dictionary can
+ be found in the `rawInfo` property.
+ */
+ public convenience init(source: UIImagePickerControllerSourceType = .photoLibrary,
+ allow: UIImagePickerController.MediaFilter = .image,
+ cameraOverlay: UIView? = nil,
+ showsCameraControls: Bool = true,
+ didCancel: @escaping ( _ picker: UIImagePickerController) -> Void = dismissFromPresenting,
+ didPick: @escaping (_ result: UIImagePickerController.Result, _ picker: UIImagePickerController) -> Void) {
+ self.init()
+ sourceType = source
+ /**
+ From UIImagePickerController docs: "You can access this property
+ only when the source type of the image picker is set to camera.
+ Attempting to access this property for other source types results in
+ throwing an invalidArgumentException exception"
+ */
+ if source == .camera {
+ cameraOverlayView = cameraOverlay
+ self.showsCameraControls = showsCameraControls
+ }
+ mediaTypes = UIImagePickerController.MediaFilter.allTypes.flatMap {
+ allow.contains($0) ? $0.mediaType : nil
+ }
+ didFinishPickingMedia { [unowned self] in
+ didPick(UIImagePickerController.Result(rawInfo: $0), self)
+ }
+ }
+
+ public static func dismissFromPresenting(_ picker: UIImagePickerController) {
+ picker.presentingViewController?.dismiss(animated: true)
+ }
+
+ /**
+ A convenience method that will present the UIImagePickerController. It will
+ also do the following as default behavior, which can be overriden at anytime:
+
+ 1. Default the didCancel callback to dismiss the picker.
+ 1. Call the didFinishPickingMedia handler if one was set previously, usually
+ from the convenience initiaizer's `didPick` parameter or by calling
+ the `didFinishPickingMedia(_:)` method.
+ 1. Dismiss the picker after calling the `didFinishPickingMedia` handler.
+
+ * * * *
+ #### An example of calling this method using the convenience initializer:
+ ```swift
+ let picker = UIImagePickerController(
+ source: .photoLibrary,
+ allow: .image) { result,_ in
+ myImageView.image = result.originalImage
+ }.present(from: self)
+ ```
+
+ * parameter viewController: The view controller that is able to present the
+ picker. This view controller is also used to dismiss the picker for the default
+ dismissing behavior.
+ * parameter animation: An animation info tuple that describes whether to
+ animate the presenting and what to do after the animation is complete.
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func present(from viewController: UIViewController, animation: (animate: Bool, onComplete: (() -> Void)?) = (true, nil)) -> Self {
+ didCancel { [weak viewController] in
+ viewController?.dismiss(animated: animation.animate)
+ }
+ update { [weak viewController] delegate in
+ let finishedPickingCopy = delegate.didFinishPickingMedia
+ delegate.didFinishPickingMedia = {
+ finishedPickingCopy?($0)
+ viewController?.dismiss(animated: animation.animate)
+ }
+ }
+ viewController.present(self, animated: animation.animate, completion: animation.onComplete)
+ return self
+ }
+}
+
+extension UIImagePickerController {
+ // MARK: Delegate Overrides
+ /**
+ Equivalent to implementing UIImagePickerControllerDelegate's imagePickerController(_:didFinishPickingMediaWithInfo:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func didFinishPickingMedia(handler: @escaping (_ withInfo: [String: Any]) -> Void) -> Self {
+ return update { $0.didFinishPickingMedia = handler }
+ }
+
+ /**
+ Equivalent to implementing UIImagePickerControllerDelegate's imagePickerControllerDidCancel(_:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func didCancel(handler: @escaping () -> Void) -> Self {
+ return update { $0.didCancel = handler }
+ }
+}
+
+extension UIImagePickerController {
+ // MARK: Helper Types
+ /**
+ A wrapper around `kUTType`. Eventually these will
+ be replaced by Apple with a Swifty enum, but until
+ then, this simply wraps these values in a strongly
+ typed `OptionSet`. You can use these when deciding
+ which media types you want the user to select from
+ the `UIImagePickerController` using the
+ `init(source:allow:cameraOverlay:showsCameraControls:didPick:)`
+ initializer.
+
+ Since it is an `OptionSet`, you can use array literal
+ syntax to describe more than one media type.
+
+ ```swift
+ let picker = UIImagePickerController(allow: [.image, .movie]) {_,_ in
+ // ...
+ }
+ ```
+ */
+ public struct MediaFilter: OptionSet {
+ /// :nodoc:
+ public let rawValue: Int
+ /// :nodoc:
+ public init(rawValue: Int) {
+ self.rawValue = rawValue
+ }
+
+ /**
+ Tells the UIImagePickerController to allow images
+ to be seleced.
+ */
+ public static let image: MediaFilter = 1
+ /**
+ Tells the UIImagePickerController to allow movies
+ to be seleced.
+ */
+ public static let movie: MediaFilter = 2
+ /**
+ Tells the UIImagePickerController to allow all
+ explicitly supported `MediaFilter` types to be seleced.
+ */
+ public static let all: MediaFilter = [.image, .movie]
+
+ fileprivate static let allTypes: [MediaFilter] = [.image, .movie]
+
+ fileprivate var mediaType: String {
+ switch self {
+ case .image:
+ return kUTTypeImage as String
+ case .movie:
+ return kUTTypeMovie as String
+ default:
+ return kUTTypeImage as String
+ }
+ }
+ }
+
+ /**
+ This result object is a only a wrapper around the loosely
+ typed Dictionary returned from `UIImagePickerController`'s
+ `-imagePickerController:didFinishPickingMediaWithInfo:` delegate
+ method. When using the
+ `init(source:allow:cameraOverlay:showsCameraControls:didPick:)`
+ initializer, the `didPick` closure passes this convenience struct.
+ If the original Dictionary is needed, it can be found in the
+ `rawInfo` property.
+ */
+ public struct Result {
+ /**
+ The original Dictionary received from UIPickerController's
+ `-imagePickerController:didFinishPickingMediaWithInfo:` delegate
+ method.
+ */
+ public let rawInfo: [String: Any]
+ /**
+ The type of media picked by the user, converted to a
+ MediaFilter option. This is equivalent to the
+ `UIImagePickerControllerOriginalImage` key value from `rawInfo`.
+ */
+ public let type: MediaFilter
+ /**
+ The original UIImage that the user selected from their
+ source. This is equivalent to the `UIImagePickerControllerOriginalImage`
+ key value from `rawInfo`.
+ */
+ public let originalImage: UIImage?
+ /**
+ The edited image after any croping, resizing, etc has occurred.
+ This is equivalent to the `UIImagePickerControllerEditedImage`
+ key value from `rawInfo`.
+ */
+ public let editedImage: UIImage?
+ /**
+ This is equivalent to the `UIImagePickerControllerCropRect` key
+ value from `rawInfo`.
+ */
+ public let cropRect: CGRect?
+ /**
+ The fileUrl of the movie that was picked. This is equivalent to
+ the `UIImagePickerControllerMediaURL` key value from `rawInfo`.
+ */
+ public let movieUrl: URL?
+ /**
+ This is equivalent to the `UIImagePickerControllerMediaMetadata` key value from `rawInfo`.
+ */
+ public let metaData: NSDictionary?
+
+ init(rawInfo: [String: Any]) {
+ self.rawInfo = rawInfo
+ type = (rawInfo[UIImagePickerControllerMediaType] as! CFString).mediaFilter
+ originalImage = rawInfo[UIImagePickerControllerOriginalImage] as? UIImage
+ editedImage = rawInfo[UIImagePickerControllerEditedImage] as? UIImage
+ cropRect = rawInfo[UIImagePickerControllerCropRect] as? CGRect
+ movieUrl = rawInfo[UIImagePickerControllerMediaURL] as? URL
+ metaData = rawInfo[UIImagePickerControllerMediaMetadata] as? NSDictionary
+ }
+ }
+}
+
+extension UIImagePickerController.MediaFilter: ExpressibleByIntegerLiteral {
+ /// :nodoc:
+ public init(integerLiteral value: Int) {
+ self.init(rawValue: value)
+ }
+}
+
+fileprivate extension CFString {
+ fileprivate var mediaFilter: UIImagePickerController.MediaFilter {
+ switch self {
+ case kUTTypeImage:
+ return .image
+ case kUTTypeMovie:
+ return .movie
+ default:
+ return .image
+ }
+ }
+}
+
+extension UIImagePickerController: DelegatorProtocol {
+ @discardableResult
+ fileprivate func update(handler: (_ delegate: ImagePickerControllerDelegate) -> Void) -> Self {
+ DelegateWrapper.update(self,
+ delegate: ImagePickerControllerDelegate(),
+ delegates: &ImagePickerControllerDelegate.delegates,
+ bind: UIImagePickerController.bind) {
+ handler($0.delegate)
+ }
+ return self
+ }
+
+ // MARK: Reset
+ /**
+ Clears any delegate closures that were assigned to this
+ `UIImagePickerController`. This cleans up memory as well as sets the
+ `delegate` property to nil. This is typically only used for explicit
+ cleanup. You are not required to call this method.
+ */
+ @objc public func clearClosureDelegates() {
+ DelegateWrapper.remove(delegator: self, from: &ImagePickerControllerDelegate.delegates)
+ UIImagePickerController.bind(self, nil)
+ }
+
+ fileprivate static func bind(_ delegator: UIImagePickerController, _ delegate: ImagePickerControllerDelegate?) {
+ delegator.delegate = nil
+ delegator.delegate = delegate
+ }
+}
diff --git a/Xcode/Closures/Source/UIPickerView.swift b/Xcode/Closures/Source/UIPickerView.swift
new file mode 100644
index 0000000..878c2be
--- /dev/null
+++ b/Xcode/Closures/Source/UIPickerView.swift
@@ -0,0 +1,555 @@
+/**
+ The MIT License (MIT)
+ Copyright (c) 2017 Vincent Hesener
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+ is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+import UIKit
+
+fileprivate final class PickerViewDelegate: NSObject, UIPickerViewDelegate, UIPickerViewDataSource, DelegateProtocol {
+ static var delegates = Set>()
+
+ fileprivate var rowHeightForComponent: ((_ component: Int) -> CGFloat)?
+ fileprivate func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat {
+ return rowHeightForComponent?(component) ?? 0
+ }
+
+ fileprivate var widthForComponent: ((_ component: Int) -> CGFloat)?
+ fileprivate func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat {
+ return widthForComponent?(component) ?? 0
+ }
+
+ fileprivate var titleForRow: ((_ row: Int, _ component: Int) -> String?)?
+ fileprivate func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
+ return titleForRow?(row, component)
+ }
+
+ fileprivate var attributedTitleForRow: ((_ row: Int, _ component: Int) -> NSAttributedString?)?
+ fileprivate func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? {
+ return attributedTitleForRow?(row, component)
+ }
+
+ fileprivate var viewForRow: ((_ row: Int, _ component: Int, _ reusingView: UIView?) -> UIView)?
+ fileprivate func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
+ return viewForRow?(row, component, view) ?? UIView()
+ }
+
+ fileprivate var didSelectRow: ((_ row: Int, _ component: Int) -> Void)?
+ fileprivate func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
+ didSelectRow?(row, component)
+ }
+
+ fileprivate var numberOfComponents: (() -> Int)?
+ fileprivate func numberOfComponents(in pickerView: UIPickerView) -> Int {
+ return numberOfComponents?() ?? 1
+ }
+
+ fileprivate var numberOfRowsInComponent: ((_ component: Int) -> Int)?
+ fileprivate func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
+ return numberOfRowsInComponent?(component) ?? 0
+ }
+
+ override func responds(to aSelector: Selector!) -> Bool {
+ switch aSelector {
+ case #selector(PickerViewDelegate.pickerView(_:rowHeightForComponent:)):
+ return rowHeightForComponent != nil
+ case #selector(PickerViewDelegate.pickerView(_:widthForComponent:)):
+ return widthForComponent != nil
+ case #selector(PickerViewDelegate.pickerView(_:titleForRow:forComponent:)):
+ return titleForRow != nil
+ case #selector(PickerViewDelegate.pickerView(_:attributedTitleForRow:forComponent:)):
+ return attributedTitleForRow != nil
+ case #selector(PickerViewDelegate.pickerView(_:viewForRow:forComponent:reusing:)):
+ return viewForRow != nil
+ case #selector(PickerViewDelegate.pickerView(_:didSelectRow:inComponent:)):
+ return didSelectRow != nil
+ case #selector(PickerViewDelegate.numberOfComponents(in:)):
+ return true
+ case #selector(PickerViewDelegate.pickerView(_:numberOfRowsInComponent:)):
+ return true
+ default:
+ return super.responds(to: aSelector)
+ }
+ }
+}
+
+extension UIPickerView {
+ // MARK: Common Array Usage
+ /**
+ This method defaults many of the boilerplate callbacks needed to populate a
+ UIPickerView when using an `Array` as a data source.
+ The defaults that this method takes care of:
+
+ * Provides the number of components
+ * Provides the number of rows
+ * Handles the pickerView(_:didSelectRow:inComponent:) delegate method
+ * Provides the title string for each row
+
+ This method simply sets basic default behavior. This means that you can
+ override the default picker view handlers after this method is called.
+ However, remember that order matters. If you call this method after you
+ override the `numberOfComponents` callback, for instance, the closure
+ you passed into `numberOfComponents` will be wiped out by this method
+ and you will have to override that closure handler again.
+
+ * Important:
+ Please remember that Swift `Array`s are value types. This means that they
+ are copied when mutaded. When the values or sequence of your array changes, you will
+ need to call this method again, just before you call reloadComponent()
+ or reloadAllComponents(). If you have a lot of picker view customization
+ in addtion to a lot of updates to your array, it is more appropriate to use the individual
+ closure handlers instead of this method.
+
+ * * * *
+ #### An example of calling this method:
+ ```swift
+ pickerView.addStrings(<#myArray#>) { aTitle,component,row in
+ print("\(aTitle) was selected")
+ }
+ ```
+ * parameter strings: An `Array` of `String`s to be used for each row in a single component picker view.
+ * parameter didSelect: A closure that is called when the UIPickerView's value has been selected.
+
+ * returns: itself so you can daisy chain the other delegate/datasource calls
+ */
+ @discardableResult
+ public func addStrings(_ strings: [String],
+ didSelect: @escaping (_ element: String, _ component: Int, _ row: Int) -> Void) -> Self {
+ return addComponents([strings], didSelect: didSelect)
+ }
+
+ /**
+ This method defaults many of the boilerplate callbacks needed to populate a
+ UIPickerView when using an `Array` as a data source.
+ The defaults that this method takes care of:
+
+ * Provides the number of components
+ * Provides the number of rows
+ * Handles the pickerView(_:titleForRow:forComponent:) delegate method
+ * Handles the pickerView(_:didSelectRow:inComponent:) delegate method
+
+ This method simply sets basic default behavior. This means that you can
+ override the default picker view handlers after this method is called.
+ However, remember that order matters. If you call this method after you
+ override the `numberOfComponents` callback, for instance, the closure
+ you passed into `numberOfComponents` will be wiped out by this method
+ and you will have to override that closure handler again.
+
+ * Important:
+ Please remember that Swift `Array`s are value types. This means that they
+ are copied when mutaded. When the values or sequence of your array changes, you will
+ need to call this method again, just before you call reloadComponent()
+ or reloadAllComponents(). If you have a lot of picker view customization
+ in addtion to a lot of updates to your array, it is more appropriate to use the individual
+ closure handlers instead of this method.
+
+ * Important:
+ Be sure to note that most of the closure callbacks in these array binding
+ methods switch the order of the parameters of row and component. Most of the
+ UIPickerView delegate/datasource method parameters have row,component. The
+ handlers' parameters on the `add` methods send component,row.
+
+ * * * *
+ #### An example of calling this method:
+ ```swift
+ pickerView.addElements(
+ <#myArray#>,
+ rowTitle: { element, component, row in
+ return <#aTitle(from: element)#>},
+ didSelect: { element, component, row in
+ print("\(element) was selected")
+ )
+ ```
+ * parameter strings: An `Array` of any type to be used for each row in a single component picker view.
+ * parameter rowTitle: A closure that is called when the UIPickerView needs to display a string for it's row.
+ * parameter didSelect: A closure that is called when the UIPickerView's value has been selected.
+
+ * returns: itself so you can daisy chain the other delegate/datasource calls
+ */
+ @discardableResult
+ public func addElements(_ elements: [Element],
+ rowTitle: @escaping (_ element: Element, _ component: Int, _ row: Int) -> String,
+ didSelect: @escaping (_ element: Element, _ component: Int, _ row: Int) -> Void) -> Self {
+ return addComponents([elements], rowTitle: rowTitle, didSelect: didSelect)
+ }
+
+ /**
+ This method defaults many of the boilerplate callbacks needed to populate a
+ UIPickerView when using an `Array` as a data source.
+ The defaults that this method takes care of:
+
+ * Provides the number of components
+ * Provides the number of rows
+ * Handles the pickerView(_:viewForRow:forComponent:) delegate method
+ * Handles the pickerView(_:didSelectRow:inComponent:) delegate method
+
+ This method simply sets basic default behavior. This means that you can
+ override the default picker view handlers after this method is called.
+ However, remember that order matters. If you call this method after you
+ override the `numberOfComponents` callback, for instance, the closure
+ you passed into `numberOfComponents` will be wiped out by this method
+ and you will have to override that closure handler again.
+
+ * Important:
+ Please remember that Swift `Array`s are value types. This means that they
+ are copied when mutaded. When the values or sequence of your array changes, you will
+ need to call this method again, just before you call reloadComponent()
+ or reloadAllComponents(). If you have a lot of picker view customization
+ in addtion to a lot of updates to your array, it is more appropriate to use the individual
+ closure handlers instead of this method.
+
+ * Important:
+ Be sure to note that most of the closure callbacks in these array binding
+ methods switch the order of the parameters of row and component. Most of the
+ UIPickerView delegate/datasource method parameters have row,component. The
+ handlers' parameters on the `add` methods send component,row.
+
+ * * * *
+ #### An example of calling this method:
+ ```swift
+ pickerView.addElements(
+ <#myArray#>,
+ rowView: { element, reuseView, component, row in
+ return <#aView(from: element)#>},
+ didSelect: { element, component, row in
+ print("\(element) was selected")
+ )
+ ```
+ * parameter strings: An `Array` of any type to be used for each row in a single component picker view.
+ * parameter rowView: A closure that is called when the UIPickerView needs to display a view for it's row.
+ * parameter didSelect: A closure that is called when the UIPickerView's value has been selected.
+
+ * returns: itself so you can daisy chain the other delegate/datasource calls
+ */
+ @discardableResult
+ public func addElements(_ elements: [Element],
+ rowView: @escaping (_ element: Element, _ reuseView: UIView?, _ component: Int, _ row: Int) -> UIView,
+ didSelect: @escaping (_ element: Element, _ component: Int, _ row: Int) -> Void) -> Self {
+ return addComponents([elements], rowView: rowView, didSelect: didSelect)
+ }
+
+ /**
+ This method defaults many of the boilerplate callbacks needed to populate a
+ UIPickerView when using a two-dimensional Array as a data source.
+ The defaults that this method takes care of:
+
+ * Provides the number of components
+ * Provides the number of rows
+ * Handles the pickerView(_:didSelectRow:inComponent:) delegate method
+ * Provides the title string for each row
+
+ This method simply sets basic default behavior. This means that you can
+ override the default picker view handlers after this method is called.
+ However, remember that order matters. If you call this method after you
+ override the `numberOfComponents` callback, for instance, the closure
+ you passed into `numberOfComponents` will be wiped out by this method
+ and you will have to override that closure handler again.
+
+ * Important:
+ Please remember that Swift `Array`s are value types. This means that they
+ are copied when mutaded. When the values or sequence of your array changes, you will
+ need to call this method again, just before you call reloadComponent()
+ or reloadAllComponents(). If you have a lot of picker view customization
+ in addtion to a lot of updates to your array, it is more appropriate to use the individual
+ closure handlers instead of this method.
+
+ * * * *
+ #### An example of calling this method:
+ ```swift
+ pickerView.addComponents(<#my2DArray#>) { aTitle,component,row in
+ print("\(aTitle) was selected")
+ }
+ ```
+ * parameter strings: An `Array` of `Array` of `String`s to be used for each row and component in a picker view.
+ The outer dimension of the array is the component (columns) and the inner dimension are the rows in that component.
+ e.g. myTwoDArray[component][row]
+ * parameter didSelect: A closure that is called when the UIPickerView's value has been selected.
+
+ * returns: itself so you can daisy chain the other delegate/datasource calls
+ */
+ @discardableResult
+ public func addComponents(_ strings: [[String]],
+ didSelect: @escaping (_ element: String, _ component: Int, _ row: Int) -> Void) -> Self {
+ return addComponents(strings, rowTitle: {e,_,_ in e}, didSelect: didSelect)
+ }
+
+ /**
+ This method defaults many of the boilerplate callbacks needed to populate a
+ UIPickerView when using a two-dimensional Array as a data source.
+ The defaults that this method takes care of:
+
+ * Provides the number of components
+ * Provides the number of rows
+ * Handles the pickerView(_:titleForRow:forComponent:) delegate method
+ * Handles the pickerView(_:didSelectRow:inComponent:) delegate method
+
+ This method simply sets basic default behavior. This means that you can
+ override the default picker view handlers after this method is called.
+ However, remember that order matters. If you call this method after you
+ override the `numberOfComponents` callback, for instance, the closure
+ you passed into `numberOfComponents` will be wiped out by this method
+ and you will have to override that closure handler again.
+
+ * Important:
+ Please remember that Swift `Array`s are value types. This means that they
+ are copied when mutaded. When the values or sequence of your array changes, you will
+ need to call this method again, just before you call reloadComponent()
+ or reloadAllComponents(). If you have a lot of picker view customization
+ in addtion to a lot of updates to your array, it is more appropriate to use the individual
+ closure handlers instead of this method.
+
+ * Important:
+ Be sure to note that most of the closure callbacks in these array binding
+ methods switch the order of the parameters of row and component. Most of the
+ UIPickerView delegate/datasource method parameters have row,component. The
+ handlers' parameters on the `add` methods send component,row.
+
+ * * * *
+ #### An example of calling this method:
+ ```swift
+ pickerView.addComponents(
+ <#my2DArray#>,
+ rowTitle: { element, component, row in
+ return <#aTitle(from: element, componentIdx: component)#>},
+ didSelect: { element, component, row in
+ print("\(element) was selected")
+ )
+ ```
+ * parameter components: An `Array` of `Array` of any type to be used for each row and component in a picker view.
+ The outer dimension of the array is the component (columns) and the inner dimension are the rows in that component.
+ e.g. myTwoDArray[component][row]
+ * parameter rowTitle: A closure that is called when the UIPickerView needs to display a string for it's row.
+ * parameter didSelect: A closure that is called when the UIPickerView's value has been selected.
+
+ * returns: itself so you can daisy chain the other delegate/datasource calls
+ */
+ @discardableResult
+ public func addComponents(_ components: [[Element]],
+ rowTitle: @escaping (_ element: Element, _ component: Int, _ row: Int) -> String,
+ didSelect: @escaping (_ element: Element, _ component: Int, _ row: Int) -> Void) -> Self {
+ return configureComponents(components, didSelect: didSelect).titleForRow() { rowIdx, componentIdx in
+ rowTitle(components[componentIdx][rowIdx], componentIdx, rowIdx)
+ }
+ }
+
+ /**
+ This method defaults many of the boilerplate callbacks needed to populate a
+ UIPickerView when using a two-dimensional Array as a data source.
+ The defaults that this method takes care of:
+
+ * Provides the number of components
+ * Provides the number of rows
+ * Handles the pickerView(_:viewForRow:forComponent:) delegate method
+ * Handles the pickerView(_:didSelectRow:inComponent:) delegate method
+
+ This method simply sets basic default behavior. This means that you can
+ override the default picker view handlers after this method is called.
+ However, remember that order matters. If you call this method after you
+ override the `numberOfComponents` callback, for instance, the closure
+ you passed into `numberOfComponents` will be wiped out by this method
+ and you will have to override that closure handler again.
+
+ * Important:
+ Please remember that Swift `Array`s are value types. This means that they
+ are copied when mutaded. When the values or sequence of your array changes, you will
+ need to call this method again, just before you call reloadComponent()
+ or reloadAllComponents(). If you have a lot of picker view customization
+ in addtion to a lot of updates to your array, it is more appropriate to use the individual
+ closure handlers instead of this method.
+
+ * Important:
+ Be sure to note that most of the closure callbacks in these array binding
+ methods switch the order of the parameters of row and component. Most of the
+ UIPickerView delegate/datasource method parameters have row,component. The
+ handlers' parameters on the `add` methods send component,row.
+
+ * * * *
+ #### An example of calling this method:
+ ```swift
+ pickerView.addComponents(
+ <#my2DArray#>,
+ rowView: { element, reuseView, component, row in
+ return <#aView(from: element, componentIdx: component)#>},
+ didSelect: { element, component, row in
+ print("\(element) was selected")
+ )
+ ```
+ parameter components: An `Array` of `Array` of any type to be used for each row and component in a picker view.
+ The outer dimension of the array is the component (columns) and the inner dimension are the rows in that component.
+ e.g. myTwoDArray[component][row]
+ * parameter rowView: A closure that is called when the UIPickerView needs to display a view for it's row.
+ * parameter didSelect: A closure that is called when the UIPickerView's value has been selected.
+
+ * returns: itself so you can daisy chain the other delegate/datasource calls
+ */
+ @discardableResult
+ public func addComponents(_ components: [[Element]],
+ rowView: @escaping (_ element: Element, _ reuseView: UIView?, _ component: Int, _ row: Int) -> UIView,
+ didSelect: @escaping (_ element: Element, _ component: Int, _ row: Int) -> Void) -> Self {
+ return configureComponents(components, didSelect: didSelect).viewForRow() { rowIdx, componentIdx, resuseView in
+ rowView(components[componentIdx][rowIdx], resuseView, componentIdx, rowIdx)
+ }
+ }
+
+ private func configureComponents(_ components: [[Element]],
+ didSelect: @escaping (_ element: Element, _ component: Int, _ row: Int) -> Void) -> Self {
+ DelegateWrapper.remove(delegator: self, from: &PickerViewDelegate.delegates)
+ delegate = nil
+ dataSource = nil
+
+ return numberOfComponents
+ {
+ components.count
+ }.numberOfRowsInComponent {
+ components[$0].count
+ }.didSelectRow() { rowIdx,componentIdx in
+ didSelect(components[componentIdx][rowIdx], componentIdx, rowIdx)
+ }
+ }
+}
+
+extension UIPickerView {
+ // MARK: Delegate and DataSource Overrides
+ /**
+ Equivalent to implementing UIPickerViewDelegate's pickerView(_:rowHeightForComponent:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func rowHeightForComponent(handler: @escaping (_ component: Int) -> CGFloat) -> Self {
+ return update { $0.rowHeightForComponent = handler }
+ }
+
+ /**
+ Equivalent to implementing UIPickerViewDelegate's pickerView(_:widthForComponent:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func widthForComponent(handler: @escaping (_ component: Int) -> CGFloat) -> Self {
+ return update { $0.widthForComponent = handler }
+ }
+
+ /**
+ Equivalent to implementing UIPickerViewDelegate's pickerView(_:titleForRow:forComponent:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func titleForRow(handler: @escaping (_ row: Int, _ component: Int) -> String?) -> Self {
+ return update { $0.titleForRow = handler }
+ }
+
+ /**
+ Equivalent to implementing UIPickerViewDelegate's pickerView(_:attributedTitleForRow:forComponent:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func attributedTitleForRow(handler: @escaping (_ row: Int, _ component: Int) -> NSAttributedString?) -> Self {
+ return update { $0.attributedTitleForRow = handler }
+ }
+
+ /**
+ Equivalent to implementing UIPickerViewDelegate's pickerView(_:viewForRow:forComponent:reusing:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func viewForRow(handler: @escaping (_ row: Int, _ component: Int, _ reusingView: UIView?) -> UIView) -> Self {
+ return update { $0.viewForRow = handler }
+ }
+
+ /**
+ Equivalent to implementing UIPickerViewDelegate's pickerView(_:didSelectRow:inComponent:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func didSelectRow(handler: @escaping (_ row: Int, _ component: Int) -> Void) -> Self {
+ return update { $0.didSelectRow = handler }
+ }
+
+ /**
+ Equivalent to implementing UIPickerViewDataSource's numberOfComponents(in:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent dataSource method
+
+ * returns: itself so you can daisy chain the other dataSource calls
+ */
+ @discardableResult
+ public func numberOfComponents(handler: @escaping () -> Int) -> Self {
+ return update { $0.numberOfComponents = handler }
+ }
+
+ /**
+ Equivalent to implementing UIPickerViewDataSource's pickerView(_:numberOfRowsInComponent:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent dataSource method
+
+ * returns: itself so you can daisy chain the other dataSource calls
+ */
+ @discardableResult
+ public func numberOfRowsInComponent(handler: @escaping (_ component: Int) -> Int) -> Self {
+ return update { $0.numberOfRowsInComponent = handler }
+ }
+}
+
+extension UIPickerView: DelegatorProtocol {
+ @discardableResult
+ fileprivate func update(handler: (_ delegate: PickerViewDelegate) -> Void) -> Self {
+ DelegateWrapper.update(self,
+ delegate: PickerViewDelegate(),
+ delegates: &PickerViewDelegate.delegates,
+ bind: UIPickerView.bind) {
+ handler($0.delegate)
+ }
+ return self
+ }
+
+ // MARK: Reset
+ /**
+ Clears any delegate/dataSource closures that were assigned to this
+ `UIPickerView`. This cleans up memory as well as sets the
+ delegate/dataSource properties to nil. This is typically only used for explicit
+ cleanup. You are not required to call this method.
+ */
+ @objc public func clearClosureDelegates() {
+ DelegateWrapper.remove(delegator: self, from: &PickerViewDelegate.delegates)
+ UIPickerView.bind(self, nil)
+ }
+
+ fileprivate static func bind(_ delegator: UIPickerView, _ delegate: PickerViewDelegate?) {
+ delegator.delegate = nil
+ delegator.dataSource = nil
+ delegator.delegate = delegate
+ delegator.dataSource = delegate
+ }
+}
+
diff --git a/Xcode/Closures/Source/UIScrollView.swift b/Xcode/Closures/Source/UIScrollView.swift
new file mode 100644
index 0000000..b8a3ad1
--- /dev/null
+++ b/Xcode/Closures/Source/UIScrollView.swift
@@ -0,0 +1,331 @@
+/**
+ The MIT License (MIT)
+ Copyright (c) 2017 Vincent Hesener
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+ is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+import UIKit
+
+class ScrollViewDelegate: NSObject, UIScrollViewDelegate, DelegateProtocol {
+ fileprivate static var delegates = Set>()
+
+ fileprivate var didScroll: ((_ scrollView: UIScrollView) -> Void)?
+ func scrollViewDidScroll(_ scrollView: UIScrollView) {
+ didScroll?(scrollView)
+ }
+
+ fileprivate var didZoom: ((_ scrollView: UIScrollView) -> Void)?
+ func scrollViewDidZoom(_ scrollView: UIScrollView) {
+ didZoom?(scrollView)
+ }
+
+ fileprivate var willBeginDragging: ((_ scrollView: UIScrollView) -> Void)?
+ func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
+ willBeginDragging?(scrollView)
+ }
+
+ fileprivate var willEndDragging: ((_ scrollView: UIScrollView, _ velocity: CGPoint, _ targetContentOffset: UnsafeMutablePointer) -> Void)?
+ func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) {
+ willEndDragging?(scrollView, velocity, targetContentOffset)
+ }
+
+ fileprivate var didEndDragging: ((_ scrollView: UIScrollView, _ decelerate: Bool) -> Void)?
+ func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
+ didEndDragging?(scrollView, decelerate)
+ }
+
+ fileprivate var willBeginDecelerating: ((_ scrollView: UIScrollView) -> Void)?
+ func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
+ willBeginDecelerating?(scrollView)
+ }
+
+ fileprivate var didEndDecelerating: ((_ scrollView: UIScrollView) -> Void)?
+ func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
+ didEndDecelerating?(scrollView)
+ }
+
+ fileprivate var didEndScrollingAnimation: ((_ scrollView: UIScrollView) -> Void)?
+ func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
+ didEndScrollingAnimation?(scrollView)
+ }
+
+ fileprivate var viewForZoomingIn: ((_ scrollView: UIScrollView) -> UIView?)?
+ func viewForZooming(in scrollView: UIScrollView) -> UIView? {
+ return viewForZoomingIn?(scrollView)
+ }
+
+ fileprivate var willBeginZooming: ((_ scrollView: UIScrollView, _ view: UIView?) -> Void)?
+ func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) {
+ willBeginZooming?(scrollView, view)
+ }
+
+ fileprivate var didEndZooming: ((_ scrollView: UIScrollView, _ view: UIView?, _ scale: CGFloat) -> Void)?
+ func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) {
+ didEndZooming?(scrollView, view, scale)
+ }
+
+ fileprivate var shouldScrollToTop: ((_ scrollView: UIScrollView) -> Bool)?
+ func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool {
+ return shouldScrollToTop?(scrollView) ?? true
+ }
+
+ fileprivate var didScrollToTop: ((_ scrollView: UIScrollView) -> Void)?
+ func scrollViewDidScrollToTop(_ scrollView: UIScrollView) {
+ didScrollToTop?(scrollView)
+ }
+
+ fileprivate var didChangeAdjustedContentInset: ((_ scrollView: UIScrollView) -> Void)?
+ func scrollViewDidChangeAdjustedContentInset(_ scrollView: UIScrollView) {
+ didChangeAdjustedContentInset?(scrollView)
+ }
+
+ override func responds(to aSelector: Selector!) -> Bool {
+ switch aSelector {
+ case #selector(ScrollViewDelegate.scrollViewDidScroll(_:)):
+ return didScroll != nil
+ case #selector(ScrollViewDelegate.scrollViewDidZoom(_:)):
+ return didZoom != nil
+ case #selector(ScrollViewDelegate.scrollViewWillBeginDragging(_:)):
+ return willBeginDragging != nil
+ case #selector(ScrollViewDelegate.scrollViewWillEndDragging(_:withVelocity:targetContentOffset:)):
+ return willEndDragging != nil
+ case #selector(ScrollViewDelegate.scrollViewDidEndDragging(_:willDecelerate:)):
+ return didEndDragging != nil
+ case #selector(ScrollViewDelegate.scrollViewWillBeginDecelerating(_:)):
+ return willBeginDecelerating != nil
+ case #selector(ScrollViewDelegate.scrollViewDidEndDecelerating(_:)):
+ return didEndDecelerating != nil
+ case #selector(ScrollViewDelegate.scrollViewDidEndScrollingAnimation(_:)):
+ return didEndScrollingAnimation != nil
+ case #selector(ScrollViewDelegate.viewForZooming(in:)):
+ return viewForZoomingIn != nil
+ case #selector(ScrollViewDelegate.scrollViewWillBeginZooming(_:with:)):
+ return willBeginZooming != nil
+ case #selector(ScrollViewDelegate.scrollViewDidEndZooming(_:with:atScale:)):
+ return didEndZooming != nil
+ case #selector(ScrollViewDelegate.scrollViewShouldScrollToTop(_:)):
+ return shouldScrollToTop != nil
+ case #selector(ScrollViewDelegate.scrollViewDidScrollToTop(_:)):
+ return didScrollToTop != nil
+ case #selector(ScrollViewDelegate.scrollViewDidChangeAdjustedContentInset(_:)):
+ return didChangeAdjustedContentInset != nil
+ default:
+ return super.responds(to: aSelector)
+ }
+ }
+}
+
+extension UIScrollView {
+ // MARK: Delegate Overrides
+ /**
+ Equivalent to implementing UIScrollView's scrollViewDidScroll(_:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func didScroll(handler: @escaping (_ scrollView: UIScrollView) -> Void) -> Self {
+ return update { $0.didScroll = handler }
+ }
+
+ /**
+ Equivalent to implementing UIScrollView's scrollViewDidZoom(_:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func didZoom(handler: @escaping (_ scrollView: UIScrollView) -> Void) -> Self {
+ return update { $0.didZoom = handler }
+ }
+
+ /**
+ Equivalent to implementing UIScrollView's scrollViewWillBeginDragging(_:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func willBeginDragging(handler: @escaping (_ scrollView: UIScrollView) -> Void) -> Self {
+ return update { $0.willBeginDragging = handler }
+ }
+
+ /**
+ Equivalent to implementing UIScrollView's scrollViewWillEndDragging(_:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func willEndDragging(handler: @escaping (_ scrollView: UIScrollView, _ velocity: CGPoint, _ targetContentOffset: UnsafeMutablePointer) -> Void) -> Self {
+ return update { $0.willEndDragging = handler }
+ }
+
+ /**
+ Equivalent to implementing UIScrollView's scrollViewDidEndDragging(_:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func didEndDragging(handler: @escaping (_ scrollView: UIScrollView, _ decelerate: Bool) -> Void) -> Self {
+ return update { $0.didEndDragging = handler }
+ }
+
+ /**
+ Equivalent to implementing UIScrollView's scrollViewWillBeginDecelerating(_:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func willBeginDecelerating(handler: @escaping (_ scrollView: UIScrollView) -> Void) -> Self {
+ return update { $0.willBeginDecelerating = handler }
+ }
+
+ /**
+ Equivalent to implementing UIScrollView's scrollViewDidEndDecelerating(_:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func didEndDecelerating(handler: @escaping (_ scrollView: UIScrollView) -> Void) -> Self {
+ return update { $0.didEndDecelerating = handler }
+ }
+
+ /**
+ Equivalent to implementing UIScrollView's scrollViewDidEndScrollingAnimation(_:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func didEndScrollingAnimation(handler: @escaping (_ scrollView: UIScrollView) -> Void) -> Self {
+ return update { $0.didEndScrollingAnimation = handler }
+ }
+
+ /**
+ Equivalent to implementing UIScrollView's viewForZooming(_:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func viewForZoomingIn(handler: @escaping (_ scrollView: UIScrollView) -> UIView?) -> Self {
+ return update { $0.viewForZoomingIn = handler }
+ }
+
+ /**
+ Equivalent to implementing UIScrollView's scrollViewWillBeginZooming(_:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func willBeginZooming(handler: @escaping (_ scrollView: UIScrollView, _ view: UIView?) -> Void) -> Self {
+ return update { $0.willBeginZooming = handler }
+ }
+
+ /**
+ Equivalent to implementing UIScrollView's scrollViewDidEndZooming(_:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func didEndZooming(handler: @escaping (_ scrollView: UIScrollView, _ view: UIView?, _ scale: CGFloat) -> Void) -> Self {
+ return update { $0.didEndZooming = handler }
+ }
+
+ /**
+ Equivalent to implementing UIScrollView's scrollViewShouldScrollToTop(_:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func shouldScrollToTop(handler: @escaping (_ scrollView: UIScrollView) -> Bool) -> Self {
+ return update { $0.shouldScrollToTop = handler }
+ }
+
+ /**
+ Equivalent to implementing UIScrollView's scrollViewDidScrollToTop(_:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func didScrollToTop(handler: @escaping (_ scrollView: UIScrollView) -> Void) -> Self {
+ return update { $0.didScrollToTop = handler }
+ }
+
+ /**
+ Equivalent to implementing UIScrollView's scrollViewDidChangeAdjustedContentInset(_:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func didChangeAdjustedContentInset(handler: @escaping (_ scrollView: UIScrollView) -> Void) -> Self {
+ return update { $0.didChangeAdjustedContentInset = handler }
+ }
+}
+
+extension UIScrollView: DelegatorProtocol {
+ @discardableResult
+ @objc func update(handler: (_ delegate: ScrollViewDelegate) -> Void) -> Self {
+ DelegateWrapper.update(self,
+ delegate: ScrollViewDelegate(),
+ delegates: &ScrollViewDelegate.delegates,
+ bind: UIScrollView.bind) {
+ handler($0.delegate)
+ }
+ return self
+ }
+
+ // MARK: Reset
+ /**
+ Clears any delegate closures that were assigned to this
+ `UIScrollView`. This cleans up memory as well as sets the
+ delegate property to nil. This is typically only used for explicit
+ cleanup. You are not required to call this method.
+ */
+ @objc public func clearClosureDelegates() {
+ DelegateWrapper.remove(delegator: self, from: &ScrollViewDelegate.delegates)
+ UIScrollView.bind(self, nil)
+ }
+
+ fileprivate static func bind(_ delegator: UIScrollView, _ delegate: ScrollViewDelegate?) {
+ delegator.delegate = nil
+ delegator.delegate = delegate
+ }
+}
diff --git a/Xcode/Closures/Source/UITableView.swift b/Xcode/Closures/Source/UITableView.swift
new file mode 100644
index 0000000..7e8b95d
--- /dev/null
+++ b/Xcode/Closures/Source/UITableView.swift
@@ -0,0 +1,1296 @@
+/**
+ The MIT License (MIT)
+ Copyright (c) 2017 Vincent Hesener
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+ is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+import UIKit
+
+/// :nodoc:
+private let jzyBug = 0 // Prevent the license header from showing up in Jazzy Docs for UITableView
+
+extension UITableView {
+ // MARK: Common Array Usage
+ /**
+ This method defaults many of the boilerplate callbacks needed to populate a
+ UITableView when using an `Array` as a data source.
+ The defaults that this method takes care of:
+
+ * Registers the cell's class and reuse identifier with a default value
+ * Optionally uses a cellNibName value to create the cell from a nib file
+ from the main bundle
+ * Handles cell dequeueing and provides a reference to the cell
+ in the `row` closure for you to modify in place.
+ * Provides the number of sections
+ * Provides the number of rows
+
+ This method simply sets basic default behavior. This means that you can
+ override the default table view handlers after this method is called.
+ However, remember that order matters. If you call this method after you
+ override the `numberOfSectionsIn` callback, for instance, the closure
+ you passed into `numberOfSectionsIn` will be wiped out by this method
+ and you will have to override that closure handler again.
+
+ * Important:
+ Please remember that Swift `Array`s are value types. This means that they
+ are copied when mutaded. When the values or sequence of your array changes, you will
+ need to call this method again, just before you
+ call reloadData(). If you have a lot of table view customization in addtion to a lot
+ of updates to your array, it is more appropriate to use the individual
+ closure handlers instead of this method.
+
+ * * * *
+ #### An example of calling this method:
+ ```swift
+ tableView.addElements(<#myArray#>, cell: <#MyUITableViewCellClass#>.self) { element, cell, index in
+ cell.textLabel!.text = <#T##someString(from: element)##String#>
+ }
+ ```
+ * parameter array: An Array to be used for each row.
+ * parameter cell: A type of cell to use when calling `dequeueReusableCell(withIdentifier:for:)`
+ * parameter cellNibName: If non-nil, the cell will be dequeued using a nib with this name from the main bundle
+ * parameter row: A closure that's called when a cell is about to be shown and needs to be configured.
+
+ * returns: itself so you can daisy chain the other datasource calls
+ */
+ @discardableResult
+ public func addElements(
+ _ elements: [Element],
+ cell: Cell.Type,
+ cellNibName: String? = nil,
+ row: @escaping (_ element: Element, _ cell: inout Cell,_ index: Int) -> Void) -> Self
+ where Cell: UITableViewCell {
+ return addSections([elements], cell: cell, cellNibName: cellNibName, row: row)
+ }
+
+ /**
+ This method defaults many of the boilerplate callbacks needed to populate a
+ UITableView when using an `Array` as a data source.
+ The defaults that this method takes care of:
+
+ * Registers the cell's class and reuse identifier with a default value
+ * Optionally uses a cellNibName value to create the cell from a nib file
+ from the main bundle
+ * Handles cell dequeueing and provides a reference to the cell
+ in the `row` closure for you to modify in place.
+ * Provides the number of sections
+ * Provides the number of rows
+ * Calls the headerView handler when each section title view needs to be shown.
+
+ This method simply sets basic default behavior. This means that you can
+ override the default table view handlers after this method is called.
+ However, remember that order matters. If you call this method after you
+ override the `numberOfSectionsIn` callback, for instance, the closure
+ you passed into `numberOfSectionsIn` will be wiped out by this method
+ and you will have to override that closure handler again.
+
+ * Important:
+ Please remember that Swift `Array`s are value types. This means that they
+ are copied when mutaded. When the values or sequence of your array changes, you will
+ need to call this method again, just before you
+ call reloadData(). If you have a lot of table view customization in addtion to a lot
+ of updates to your array, it is more appropriate to use the individual
+ closure handlers instead of this method.
+
+ * * * *
+ #### An example of calling this method:
+ ```swift
+ tableView.addSections(
+ <#my2dArray#>,
+ cell: <#MyUITableViewCellClass#>.self,
+ headerView: { array, index in
+ <#T##aView(from: array)##UIView#>},
+ row: { element, cell, index in
+ cell.textLabel!.text = <#T##someString(from: element)##String#>}
+ )
+ ```
+ * parameter array: A two dimensional `Array` to be used for each section.
+ * parameter cell: A type of cell to use when calling `dequeueReusableCell(withIdentifier:for:)`
+ * parameter cellNibName: If non-nil, the cell will be dequeued using a nib with this name from the main bundle
+ * parameter headerView: A closure that provides the information needed to display the section's header
+ as an instance of `UIView`.
+ * parameter row: A closure that's called when a cell is about to be shown and needs to be configured.
+
+ * returns: itself so you can daisy chain the other datasource calls
+ */
+ @discardableResult
+ public func addSections(
+ _ elements: [[Element]],
+ cell: Cell.Type,
+ cellNibName: String? = nil,
+ headerView: @escaping ((_ elements: [Element], _ index: Int) -> UIView),
+ row: @escaping (_ element: Element, _ cell: inout Cell,_ index: Int) -> Void) -> Self
+ where Cell: UITableViewCell {
+
+ return _addSections(elements, cell: cell, cellNibName: cellNibName, row: row)
+ .viewForHeaderInSection() {
+ return headerView(elements[$0], $0)
+ }
+ }
+
+ /**
+ This method defaults many of the boilerplate callbacks needed to populate a
+ UITableView when using an `Array` as a data source.
+ The defaults that this method takes care of:
+
+ * Registers the cell's class and reuse identifier with a default value
+ * Optionally uses a cellNibName value to create the cell from a nib file
+ from the main bundle
+ * Handles cell dequeueing and provides a reference to the cell
+ in the `row` closure for you to modify in place.
+ * Provides the number of sections
+ * Provides the number of rows
+ * Calls the headerView handler when each section title view needs to be shown.
+
+ This method simply sets basic default behavior. This means that you can
+ override the default table view handlers after this method is called.
+ However, remember that order matters. If you call this method after you
+ override the `numberOfSectionsIn` callback, for instance, the closure
+ you passed into `numberOfSectionsIn` will be wiped out by this method
+ and you will have to override that closure handler again.
+
+ * Important:
+ Please remember that Swift `Array`s are value types. This means that they
+ are copied when mutaded. When the values or sequence of your array changes, you will
+ need to call this method again, just before you
+ call reloadData(). If you have a lot of table view customization in addtion to a lot
+ of updates to your array, it is more appropriate to use the individual
+ closure handlers instead of this method.
+
+ * * * *
+ #### An example of calling this method:
+ ```swift
+ tableView.addSections(
+ <#my2dArray#>,
+ cell: <#MyUITableViewCellClass#>.self,
+ headerTitle: { array, index in
+ <#T##aTitle(from: array)##String#>},
+ row: { element, cell, index in
+ cell.textLabel!.text = <#T##someString(from: element)##String#>}
+ )
+ ```
+ * parameter array: A two dimensional `Array` to be used for each section.
+ * parameter cell: A type of cell to use when calling `dequeueReusableCell(withIdentifier:for:)`
+ * parameter cellNibName: If non-nil, the cell will be dequeued using a nib with this name from the main bundle
+ * parameter headerTitle: A closure that provides the information needed to display the section's header
+ as a `String`.
+ * parameter row: A closure that's called when a cell is about to be shown and needs to be configured.
+
+ * returns: itself so you can daisy chain the other datasource calls
+ */
+ @discardableResult
+ public func addSections(
+ _ elements: [[Element]],
+ cell: Cell.Type,
+ cellNibName: String? = nil,
+ headerTitle: ((_ elements: [Element], _ index: Int) -> String)? = nil,
+ row: @escaping (_ element: Element, _ cell: inout Cell,_ index: Int) -> Void) -> Self
+ where Cell: UITableViewCell {
+
+ _addSections(elements, cell: cell, cellNibName: cellNibName, row: row)
+ if let headerTitle = headerTitle {
+ titleForHeaderInSection {headerTitle(elements[$0], $0)}
+ }
+ return self
+ }
+
+ @discardableResult
+ private func _addSections(
+ _ elements: [[Element]],
+ cell: Cell.Type,
+ cellNibName: String? = nil,
+ row: @escaping (_ element: Element, _ cell: inout Cell,_ index: Int) -> Void) -> Self
+ where Cell: UITableViewCell {
+
+ DelegateWrapper.remove(delegator: self, from: &TableViewDelegate.delegates)
+ delegate = nil
+ dataSource = nil
+ let reuseIdentifier = "\(Element.self).\(cell)"
+
+ if let nibName = cellNibName {
+ register(UINib(nibName: nibName, bundle: nil), forCellReuseIdentifier: reuseIdentifier)
+ } else {
+ register(Cell.self, forCellReuseIdentifier: reuseIdentifier)
+ }
+
+ return numberOfSectionsIn
+ {
+ return elements.count
+ }.numberOfRows {
+ return elements[$0].count
+ }.cellForRow { [unowned self] in
+ let element = elements[$0.section][$0.row]
+ var cell = self.dequeueReusableCell(withIdentifier: reuseIdentifier, for: $0) as! Cell
+ row(element, &cell, $0.row)
+ return cell
+ }
+ }
+}
+
+class TableViewDelegate: ScrollViewDelegate, UITableViewDelegate, UITableViewDataSource {
+ fileprivate static var delegates = Set>()
+
+ fileprivate var willDisplay: ((_ cell: UITableViewCell, _ forRowAt: IndexPath) -> Void)?
+ fileprivate var willDisplayHeaderView: ((_ view: UIView, _ forSection: Int) -> Void)?
+ fileprivate var willDisplayFooterView: ((_ view: UIView, _ section: Int) -> Void)?
+ fileprivate var didEndDisplaying: ((_ cell: UITableViewCell, _ indexPath: IndexPath) -> Void)?
+ fileprivate var didEndDisplayingHeaderView: ((_ view: UIView, _ section: Int) -> Void)?
+ fileprivate var didEndDisplayingFooterView: ((_ view: UIView, _ section: Int) -> Void)?
+ fileprivate var heightForRowAt: ((_ indexPath: IndexPath) -> CGFloat)?
+ fileprivate var heightForHeaderInSection: ((_ section: Int) -> CGFloat)?
+ fileprivate var heightForFooterInSection: ((_ section: Int) -> CGFloat)?
+ fileprivate var estimatedHeightForRowAt: ((_ indexPath: IndexPath) -> CGFloat)?
+ fileprivate var estimatedHeightForHeaderInSection: ((_ section: Int) -> CGFloat)?
+ fileprivate var estimatedHeightForFooterInSection: ((_ section: Int) -> CGFloat)?
+ fileprivate var viewForHeaderInSection: ((_ section: Int) -> UIView?)?
+ fileprivate var viewForFooterInSection: ((_ section: Int) -> UIView?)?
+ fileprivate var accessoryButtonTappedForRowWith: ((_ indexPath: IndexPath) -> Void)?
+ fileprivate var shouldHighlightRowAt: ((_ indexPath: IndexPath) -> Bool)?
+ fileprivate var didHighlightRowAt: ((_ indexPath: IndexPath) -> Void)?
+ fileprivate var didUnhighlightRowAt: ((_ indexPath: IndexPath) -> Void)?
+ fileprivate var willSelectRowAt: ((_ indexPath: IndexPath) -> IndexPath?)?
+ fileprivate var willDeselectRowAt: ((_ indexPath: IndexPath) -> IndexPath?)?
+ fileprivate var didSelectRowAt: ((_ indexPath: IndexPath) -> Void)?
+ fileprivate var didDeselectRowAt: ((_ indexPath: IndexPath) -> Void)?
+ fileprivate var editingStyleForRowAt: ((_ indexPath: IndexPath) -> UITableViewCellEditingStyle)?
+ fileprivate var titleForDeleteConfirmationButtonForRowAt: ((_ indexPath: IndexPath) -> String?)?
+ fileprivate var editActionsForRowAt: ((_ indexPath: IndexPath) -> [UITableViewRowAction]?)?
+ fileprivate var shouldIndentWhileEditingRowAt: ((_ indexPath: IndexPath) -> Bool)?
+ fileprivate var willBeginEditingRowAt: ((_ indexPath: IndexPath) -> Void)?
+ fileprivate var didEndEditingRowAt: ((_ indexPath: IndexPath?) -> Void)?
+ fileprivate var targetIndexPathForMoveFromRowAt: ((_ sourceIndexPath: IndexPath, _ proposedDestinationIndexPath: IndexPath) -> IndexPath)?
+ fileprivate var indentationLevelForRowAt: ((_ indexPath: IndexPath) -> Int)?
+ fileprivate var shouldShowMenuForRowAt: ((_ indexPath: IndexPath) -> Bool)?
+ fileprivate var canPerformAction: ((_ action: Selector, _ indexPath: IndexPath, _ sender: Any?) -> Bool)?
+ fileprivate var performAction: ((_ action: Selector, _ indexPath: IndexPath, _ sender: Any?) -> Void)?
+ fileprivate var canFocusRowAt: ((_ indexPath: IndexPath) -> Bool)?
+ fileprivate var shouldUpdateFocus: ((_ context: UITableViewFocusUpdateContext) -> Bool)?
+ fileprivate var didUpdateFocus: ((_ context: UITableViewFocusUpdateContext, _ coordinator: UIFocusAnimationCoordinator) -> Void)?
+ fileprivate var indexPathForPreferredFocusedView: (() -> IndexPath?)?
+ fileprivate var numberOfRows: ((_ section: Int) -> Int)?
+ fileprivate var cellForRow: ((_ indexPath: IndexPath) -> UITableViewCell)?
+ fileprivate var numberOfSections: (() -> Int)?
+ fileprivate var titleForHeaderInSection: ((_ section: Int) -> String?)?
+ fileprivate var titleForFooterInSection: ((_ section: Int) -> String?)?
+ fileprivate var canEditRowAt: ((_ indexPath: IndexPath) -> Bool)?
+ fileprivate var canMoveRowAt: ((_ indexPath: IndexPath) -> Bool)?
+ fileprivate var sectionIndexTitles: (() -> [String]?)?
+ fileprivate var sectionForSectionIndexTitle: ((_ title: String, _ index: Int) -> Int)?
+ fileprivate var commit: ((_ editingStyle: UITableViewCellEditingStyle, _ indexPath: IndexPath) -> Void)?
+ fileprivate var moveRowAt: ((_ sourceIndexPath: IndexPath, _ destinationIndexPath: IndexPath) -> Void)?
+ private var _leadingSwipeActionsConfigurationForRowAt: Any?
+ @available(iOS 11, *)
+ fileprivate var leadingSwipeActionsConfigurationForRowAt: ((_ indexPath: IndexPath) -> UISwipeActionsConfiguration?)? {
+ get {
+ return _leadingSwipeActionsConfigurationForRowAt as? (_ indexPath: IndexPath) -> UISwipeActionsConfiguration?
+ }
+ set {
+ _leadingSwipeActionsConfigurationForRowAt = newValue
+ }
+ }
+ private var _trailingSwipeActionsConfigurationForRowAt: Any?
+ @available(iOS 11, *)
+ fileprivate var trailingSwipeActionsConfigurationForRowAt: ((_ indexPath: IndexPath) -> UISwipeActionsConfiguration?)? {
+ get {
+ return _trailingSwipeActionsConfigurationForRowAt as? (_ indexPath: IndexPath) -> UISwipeActionsConfiguration?
+ }
+ set {
+ _trailingSwipeActionsConfigurationForRowAt = newValue
+ }
+ }
+ private var _shouldSpringLoadRowAt: Any?
+ @available(iOS 11, *)
+ fileprivate var shouldSpringLoadRowAt: ((_ indexPath: IndexPath, _ context: UISpringLoadedInteractionContext) -> Bool)? {
+ get {
+ return _shouldSpringLoadRowAt as? (_ indexPath: IndexPath, _ context: UISpringLoadedInteractionContext) -> Bool
+ }
+ set {
+ _shouldSpringLoadRowAt = newValue
+ }
+ }
+
+ override func responds(to aSelector: Selector!) -> Bool {
+ if #available(iOS 11, *) {
+ switch aSelector {
+ case #selector(TableViewDelegate.tableView(_:leadingSwipeActionsConfigurationForRowAt:)):
+ return _leadingSwipeActionsConfigurationForRowAt != nil
+ case #selector(TableViewDelegate.tableView(_:trailingSwipeActionsConfigurationForRowAt:)):
+ return _trailingSwipeActionsConfigurationForRowAt != nil
+ case #selector(TableViewDelegate.tableView(_:shouldSpringLoadRowAt:with:)):
+ return _shouldSpringLoadRowAt != nil
+ default:
+ break
+ }
+ }
+
+ switch aSelector {
+ case #selector(TableViewDelegate.tableView(_:willDisplay:forRowAt:)):
+ return willDisplay != nil
+ case #selector(TableViewDelegate.tableView(_:willDisplayHeaderView:forSection:)):
+ return willDisplayHeaderView != nil
+ case #selector(TableViewDelegate.tableView(_:willDisplayFooterView:forSection:)):
+ return willDisplayFooterView != nil
+ case #selector(TableViewDelegate.tableView(_:didEndDisplaying:forRowAt:)):
+ return didEndDisplaying != nil
+ case #selector(TableViewDelegate.tableView(_:didEndDisplayingHeaderView:forSection:)):
+ return didEndDisplayingHeaderView != nil
+ case #selector(TableViewDelegate.tableView(_:didEndDisplayingFooterView:forSection:)):
+ return didEndDisplayingFooterView != nil
+ case #selector(TableViewDelegate.tableView(_:heightForRowAt:)):
+ return heightForRowAt != nil
+ case #selector(TableViewDelegate.tableView(_:heightForHeaderInSection:)):
+ return heightForHeaderInSection != nil
+ case #selector(TableViewDelegate.tableView(_:heightForFooterInSection:)):
+ return heightForFooterInSection != nil
+ case #selector(TableViewDelegate.tableView(_:estimatedHeightForRowAt:)):
+ return estimatedHeightForRowAt != nil
+ case #selector(TableViewDelegate.tableView(_:estimatedHeightForHeaderInSection:)):
+ return estimatedHeightForHeaderInSection != nil
+ case #selector(TableViewDelegate.tableView(_:estimatedHeightForFooterInSection:)):
+ return estimatedHeightForFooterInSection != nil
+ case #selector(TableViewDelegate.tableView(_:viewForHeaderInSection:)):
+ return viewForHeaderInSection != nil
+ case #selector(TableViewDelegate.tableView(_:viewForFooterInSection:)):
+ return viewForFooterInSection != nil
+ case #selector(TableViewDelegate.tableView(_:accessoryButtonTappedForRowWith:)):
+ return accessoryButtonTappedForRowWith != nil
+ case #selector(TableViewDelegate.tableView(_:shouldHighlightRowAt:)):
+ return shouldHighlightRowAt != nil
+ case #selector(TableViewDelegate.tableView(_:didHighlightRowAt:)):
+ return didHighlightRowAt != nil
+ case #selector(TableViewDelegate.tableView(_:didUnhighlightRowAt:)):
+ return didUnhighlightRowAt != nil
+ case #selector(TableViewDelegate.tableView(_:willSelectRowAt:)):
+ return willSelectRowAt != nil
+ case #selector(TableViewDelegate.tableView(_:willDeselectRowAt:)):
+ return willDeselectRowAt != nil
+ case #selector(TableViewDelegate.tableView(_:didSelectRowAt:)):
+ return didSelectRowAt != nil
+ case #selector(TableViewDelegate.tableView(_:didDeselectRowAt:)):
+ return didDeselectRowAt != nil
+ case #selector(TableViewDelegate.tableView(_:editingStyleForRowAt:)):
+ return editingStyleForRowAt != nil
+ case #selector(TableViewDelegate.tableView(_:titleForDeleteConfirmationButtonForRowAt:)):
+ return titleForDeleteConfirmationButtonForRowAt != nil
+ case #selector(TableViewDelegate.tableView(_:editActionsForRowAt:)):
+ return editActionsForRowAt != nil
+ case #selector(TableViewDelegate.tableView(_:shouldIndentWhileEditingRowAt:)):
+ return shouldIndentWhileEditingRowAt != nil
+ case #selector(TableViewDelegate.tableView(_:willBeginEditingRowAt:)):
+ return willBeginEditingRowAt != nil
+ case #selector(TableViewDelegate.tableView(_:didEndEditingRowAt:)):
+ return didEndEditingRowAt != nil
+ case #selector(TableViewDelegate.tableView(_:targetIndexPathForMoveFromRowAt:toProposedIndexPath:)):
+ return targetIndexPathForMoveFromRowAt != nil
+ case #selector(TableViewDelegate.tableView(_:indentationLevelForRowAt:)):
+ return indentationLevelForRowAt != nil
+ case #selector(TableViewDelegate.tableView(_:shouldShowMenuForRowAt:)):
+ return shouldShowMenuForRowAt != nil
+ case #selector(TableViewDelegate.tableView(_:canPerformAction:forRowAt:withSender:)):
+ return canPerformAction != nil
+ case #selector(TableViewDelegate.tableView(_:canPerformAction:forRowAt:withSender:)):
+ return performAction != nil
+ case #selector(TableViewDelegate.tableView(_:canFocusRowAt:)):
+ return canFocusRowAt != nil
+ case #selector(TableViewDelegate.tableView(_:shouldUpdateFocusIn:)):
+ return shouldUpdateFocus != nil
+ case #selector(TableViewDelegate.tableView(_:didUpdateFocusIn:with:)):
+ return didUpdateFocus != nil
+ case #selector(TableViewDelegate.indexPathForPreferredFocusedView(in:)):
+ return indexPathForPreferredFocusedView != nil
+ case #selector(TableViewDelegate.tableView(_:numberOfRowsInSection:)):
+ return true
+ case #selector(TableViewDelegate.tableView(_:cellForRowAt:)):
+ return true
+ case #selector(TableViewDelegate.numberOfSections(in:)):
+ return true
+ case #selector(TableViewDelegate.tableView(_:titleForHeaderInSection:)):
+ return titleForHeaderInSection != nil
+ case #selector(TableViewDelegate.tableView(_:titleForFooterInSection:)):
+ return titleForFooterInSection != nil
+ case #selector(TableViewDelegate.tableView(_:canEditRowAt:)):
+ return canEditRowAt != nil
+ case #selector(TableViewDelegate.tableView(_:canMoveRowAt:)):
+ return canMoveRowAt != nil
+ case #selector(TableViewDelegate.tableView(_:sectionForSectionIndexTitle:at:)):
+ return sectionIndexTitles != nil
+ case #selector(TableViewDelegate.tableView(_:sectionForSectionIndexTitle:at:)):
+ return sectionForSectionIndexTitle != nil
+ case #selector(TableViewDelegate.tableView(_:commit:forRowAt:)):
+ return commit != nil
+ case #selector(TableViewDelegate.tableView(_:moveRowAt:to:)):
+ return moveRowAt != nil
+ default:
+ return super.responds(to: aSelector)
+ }
+ }
+}
+
+extension TableViewDelegate {
+ func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
+ willDisplay?(cell, indexPath)
+ }
+
+ func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
+ willDisplayHeaderView?(view, section)
+ }
+
+ func tableView(_ tableView: UITableView, willDisplayFooterView view: UIView, forSection section: Int) {
+ willDisplayFooterView?(view, section)
+ }
+
+ func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
+ didEndDisplaying?(cell, indexPath)
+ }
+
+ func tableView(_ tableView: UITableView, didEndDisplayingHeaderView view: UIView, forSection section: Int) {
+ didEndDisplayingHeaderView?(view, section)
+ }
+
+ func tableView(_ tableView: UITableView, didEndDisplayingFooterView view: UIView, forSection section: Int) {
+ didEndDisplayingFooterView?(view, section)
+ }
+
+ func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
+ return heightForRowAt?(indexPath) ?? tableView.rowHeight
+ }
+
+ func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
+ return heightForHeaderInSection?(section) ?? 0
+ }
+
+ func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
+ return heightForFooterInSection?(section) ?? 0
+ }
+
+ func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
+ return estimatedHeightForRowAt?(indexPath) ?? UITableViewAutomaticDimension
+ }
+
+ func tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat {
+ return estimatedHeightForHeaderInSection?(section) ?? 0
+ }
+
+ func tableView(_ tableView: UITableView, estimatedHeightForFooterInSection section: Int) -> CGFloat {
+ return estimatedHeightForFooterInSection?(section) ?? 0
+ }
+
+ func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
+ return viewForHeaderInSection?(section) ?? nil
+ }
+
+ func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
+ return viewForFooterInSection?(section) ?? nil
+ }
+
+ func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
+ accessoryButtonTappedForRowWith?(indexPath)
+ }
+
+ func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
+ return shouldHighlightRowAt?(indexPath) ?? true
+ }
+
+ func tableView(_ tableView: UITableView, didHighlightRowAt indexPath: IndexPath) {
+ didHighlightRowAt?(indexPath)
+ }
+
+ func tableView(_ tableView: UITableView, didUnhighlightRowAt indexPath: IndexPath) {
+ didUnhighlightRowAt?(indexPath)
+ }
+
+ func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
+ return willSelectRowAt?(indexPath) ?? indexPath
+ }
+
+ func tableView(_ tableView: UITableView, willDeselectRowAt indexPath: IndexPath) -> IndexPath? {
+ return willDeselectRowAt?(indexPath) ?? indexPath
+ }
+
+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+ didSelectRowAt?(indexPath)
+ }
+
+ func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
+ didDeselectRowAt?(indexPath)
+ }
+
+ func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
+ return editingStyleForRowAt?(indexPath) ?? .delete
+ }
+
+ func tableView(_ tableView: UITableView, titleForDeleteConfirmationButtonForRowAt indexPath: IndexPath) -> String? {
+ return titleForDeleteConfirmationButtonForRowAt?(indexPath) ?? Bundle(identifier: "com.apple.UIKit")?.localizedString(forKey: "Delete", value: nil, table: nil)
+ }
+
+ func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
+ return editActionsForRowAt?(indexPath) ?? nil
+ }
+
+ func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
+ return shouldIndentWhileEditingRowAt?(indexPath) ?? true
+ }
+
+ func tableView(_ tableView: UITableView, willBeginEditingRowAt indexPath: IndexPath) {
+ willBeginEditingRowAt?(indexPath)
+ }
+
+ func tableView(_ tableView: UITableView, didEndEditingRowAt indexPath: IndexPath?) {
+ didEndEditingRowAt?(indexPath)
+ }
+
+ func tableView(_ tableView: UITableView, targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath, toProposedIndexPath proposedDestinationIndexPath: IndexPath) -> IndexPath {
+ return targetIndexPathForMoveFromRowAt?(sourceIndexPath, proposedDestinationIndexPath) ?? proposedDestinationIndexPath
+ }
+
+ func tableView(_ tableView: UITableView, indentationLevelForRowAt indexPath: IndexPath) -> Int {
+ return indentationLevelForRowAt?(indexPath) ?? 0
+ }
+
+ func tableView(_ tableView: UITableView, shouldShowMenuForRowAt indexPath: IndexPath) -> Bool {
+ return shouldShowMenuForRowAt?(indexPath) ?? false
+ }
+
+ func tableView(_ tableView: UITableView, canPerformAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) -> Bool {
+ return canPerformAction?(action, indexPath, sender) ?? false
+ }
+
+ func tableView(_ tableView: UITableView, performAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) {
+ performAction?(action, indexPath, sender)
+ }
+
+ func tableView(_ tableView: UITableView, canFocusRowAt indexPath: IndexPath) -> Bool {
+ return canFocusRowAt?(indexPath) ?? true
+ }
+
+ func tableView(_ tableView: UITableView, shouldUpdateFocusIn context: UITableViewFocusUpdateContext) -> Bool {
+ return shouldUpdateFocus?(context) ?? true
+ }
+
+ func tableView(_ tableView: UITableView, didUpdateFocusIn context: UITableViewFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
+ didUpdateFocus?(context, coordinator)
+ }
+
+ func indexPathForPreferredFocusedView(in tableView: UITableView) -> IndexPath? {
+ return indexPathForPreferredFocusedView?()
+ }
+
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+ return numberOfRows?(section) ?? 0
+ }
+
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+ return cellForRow?(indexPath) ?? UITableViewCell()
+ }
+
+ func numberOfSections(in tableView: UITableView) -> Int {
+ return numberOfSections?() ?? 1
+ }
+
+ func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
+ return titleForHeaderInSection?(section) ?? nil
+ }
+
+ func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
+ return titleForFooterInSection?(section) ?? nil
+ }
+
+ func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
+ return canEditRowAt?(indexPath) ?? true
+ }
+
+ func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
+ return canMoveRowAt?(indexPath) ?? true
+ }
+ func sectionIndexTitles(for tableView: UITableView) -> [String]? {
+ return sectionIndexTitles?() ?? nil
+ }
+
+ func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
+ return sectionForSectionIndexTitle?(title, index) ?? 0
+ }
+
+ func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
+ commit?(editingStyle, indexPath)
+ }
+
+ func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
+ moveRowAt?(sourceIndexPath, destinationIndexPath)
+ }
+
+ @available(iOS 11, *)
+ func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
+ return leadingSwipeActionsConfigurationForRowAt?(indexPath)
+ }
+
+ @available(iOS 11, *)
+ func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
+ return trailingSwipeActionsConfigurationForRowAt?(indexPath)
+ }
+
+ @available(iOS 11, *)
+ func tableView(_ tableView: UITableView, shouldSpringLoadRowAt indexPath: IndexPath, with context: UISpringLoadedInteractionContext) -> Bool {
+ return shouldSpringLoadRowAt?(indexPath, context) ?? true
+ }
+}
+
+extension UITableView {
+ // MARK: Delegate and DataSource Overrides
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:willDisplay:forRowAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func willDisplay(handler: @escaping (_ cell: UITableViewCell, _ forRowAt: IndexPath) -> Void) -> Self {
+ return update { $0.willDisplay = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:willDisplayHeaderView:forSection:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func willDisplayHeaderView(handler: @escaping (_ view: UIView, _ forSection: Int) -> Void) -> Self {
+ return update { $0.willDisplayHeaderView = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:willDisplayFooterView:forSection:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func willDisplayFooterView(handler: @escaping (_ view: UIView, _ section: Int) -> Void) -> Self {
+ return update { $0.willDisplayFooterView = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:didEndDisplaying:forRowAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func didEndDisplaying(handler: @escaping (_ cell: UITableViewCell, _ indexPath: IndexPath) -> Void) -> Self {
+ return update { $0.didEndDisplaying = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:didEndDisplayingHeaderView:forSection:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func didEndDisplayingHeaderView(handler: @escaping (_ view: UIView, _ section: Int) -> Void) -> Self {
+ return update { $0.didEndDisplayingHeaderView = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:didEndDisplayingFooterView:forSection:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func didEndDisplayingFooterView(handler: @escaping (_ view: UIView, _ section: Int) -> Void) -> Self {
+ return update { $0.didEndDisplayingFooterView = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:heightForRowAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func heightForRowAt(handler: @escaping (_ indexPath: IndexPath) -> CGFloat) -> Self {
+ return update { $0.heightForRowAt = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:heightForHeaderInSection:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func heightForHeaderInSection(handler: @escaping (_ section: Int) -> CGFloat) -> Self {
+ return update { $0.heightForHeaderInSection = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:heightForFooterInSection:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func heightForFooterInSection(handler: @escaping (_ section: Int) -> CGFloat) -> Self {
+ return update { $0.heightForFooterInSection = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:estimatedHeightForRowAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func estimatedHeightForRowAt(handler: @escaping (_ indexPath: IndexPath) -> CGFloat) -> Self {
+ return update { $0.estimatedHeightForRowAt = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:estimatedHeightForHeaderInSection:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func estimatedHeightForHeaderInSection(handler: @escaping (_ section: Int) -> CGFloat) -> Self {
+ return update { $0.estimatedHeightForHeaderInSection = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:estimatedHeightForFooterInSection:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func estimatedHeightForFooterInSection(handler: @escaping (_ section: Int) -> CGFloat) -> Self {
+ return update { $0.estimatedHeightForFooterInSection = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:viewForHeaderInSection:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func viewForHeaderInSection(handler: @escaping (_ section: Int) -> UIView?) -> Self {
+ return update { $0.viewForHeaderInSection = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:viewForFooterInSection:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func viewForFooterInSection(handler: @escaping (_ section: Int) -> UIView?) -> Self {
+ return update { $0.viewForFooterInSection = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:accessoryButtonTappedForRowWith:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func accessoryButtonTappedForRowWith(handler: @escaping (_ indexPath: IndexPath) -> Void) -> Self {
+ return update { $0.accessoryButtonTappedForRowWith = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:shouldHighlightRowAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func shouldHighlightRowAt(handler: @escaping (_ indexPath: IndexPath) -> Bool) -> Self {
+ return update { $0.shouldHighlightRowAt = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:didHighlightRowAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func didHighlightRowAt(handler: @escaping (_ indexPath: IndexPath) -> Void) -> Self {
+ return update { $0.didHighlightRowAt = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:didUnhighlightRowAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func didUnhighlightRowAt(handler: @escaping (_ indexPath: IndexPath) -> Void) -> Self {
+ return update { $0.didUnhighlightRowAt = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:willSelectRowAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func willSelectRowAt(handler: @escaping (_ indexPath: IndexPath) -> IndexPath?) -> Self {
+ return update { $0.willSelectRowAt = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:willDeselectRowAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func willDeselectRowAt(handler: @escaping (_ indexPath: IndexPath) -> IndexPath?) -> Self {
+ return update { $0.willDeselectRowAt = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:didSelectRowAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func didSelectRowAt(handler: @escaping (_ indexPath: IndexPath) -> Void) -> Self {
+ return update { $0.didSelectRowAt = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:didDeselectRowAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func didDeselectRowAt(handler: @escaping (_ indexPath: IndexPath) -> Void) -> Self {
+ return update { $0.didDeselectRowAt = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:editingStyleForRowAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func editingStyleForRowAt(handler: @escaping (_ indexPath: IndexPath) -> UITableViewCellEditingStyle) -> Self {
+ return update { $0.editingStyleForRowAt = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:titleForDeleteConfirmationButtonForRowAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func titleForDeleteConfirmationButtonForRowAt(handler: @escaping (_ indexPath: IndexPath) -> String?) -> Self {
+ return update { $0.titleForDeleteConfirmationButtonForRowAt = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:editActionsForRowAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func editActionsForRowAt(handler: @escaping (_ indexPath: IndexPath) -> [UITableViewRowAction]?) -> Self {
+ return update { $0.editActionsForRowAt = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:shouldIndentWhileEditingRowAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func shouldIndentWhileEditingRowAt(handler: @escaping (_ indexPath: IndexPath) -> Bool) -> Self {
+ return update { $0.shouldIndentWhileEditingRowAt = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:willBeginEditingRowAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func willBeginEditingRowAt(handler: @escaping (_ indexPath: IndexPath) -> Void) -> Self {
+ return update { $0.willBeginEditingRowAt = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:didEndEditingRowAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func didEndEditingRowAt(handler: @escaping (_ indexPath: IndexPath?) -> Void) -> Self {
+ return update { $0.didEndEditingRowAt = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:targetIndexPathForMoveFromRowAt:toProposedIndexPath:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func targetIndexPathForMoveFromRowAt(handler: @escaping (_ sourceIndexPath: IndexPath, _ proposedDestinationIndexPath: IndexPath) -> IndexPath) -> Self {
+ return update { $0.targetIndexPathForMoveFromRowAt = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:indentationLevelForRowAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func indentationLevelForRowAt(handler: @escaping (_ indexPath: IndexPath) -> Int) -> Self {
+ return update { $0.indentationLevelForRowAt = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:shouldShowMenuForRowAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func shouldShowMenuForRowAt(handler: @escaping (_ indexPath: IndexPath) -> Bool) -> Self {
+ return update { $0.shouldShowMenuForRowAt = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:canPerformAction:forRowAt:withSender:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func canPerformAction(handler: @escaping (_ action: Selector, _ indexPath: IndexPath, _ sender: Any?) -> Bool) -> Self {
+ return update { $0.canPerformAction = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:performAction:forRowAt:withSender:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func performAction(handler: @escaping (_ action: Selector, _ indexPath: IndexPath, _ sender: Any?) -> Void) -> Self {
+ return update { $0.performAction = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:canFocusRowAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func canFocusRowAt(handler: @escaping (_ indexPath: IndexPath) -> Bool) -> Self {
+ return update { $0.canFocusRowAt = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:shouldUpdateFocusIn:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func shouldUpdateFocus(handler: @escaping (_ context: UITableViewFocusUpdateContext) -> Bool) -> Self {
+ return update { $0.shouldUpdateFocus = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:didUpdateFocusIn:with:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func didUpdateFocus(handler: @escaping (_ context: UITableViewFocusUpdateContext, _ coordinator: UIFocusAnimationCoordinator) -> Void) -> Self {
+ return update { $0.didUpdateFocus = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's indexPathForPreferredFocusedView(in:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @discardableResult
+ public func indexPathForPreferredFocusedView(handler: @escaping () -> IndexPath?) -> Self {
+ return update { $0.indexPathForPreferredFocusedView = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDataSource's tableView(_:numberOfRowsInSection:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent datasource method
+
+ * returns: itself so you can daisy chain the other datasource calls
+ */
+ @discardableResult
+ public func numberOfRows(handler: @escaping (_ section: Int) -> Int) -> Self {
+ return update { $0.numberOfRows = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDataSource's tableView(_:cellForRowAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent datasource method
+
+ * returns: itself so you can daisy chain the other datasource calls
+ */
+ @discardableResult
+ public func cellForRow(handler: @escaping (_ indexPath: IndexPath) -> UITableViewCell) -> Self {
+ return update { $0.cellForRow = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDataSource's numberOfSections(in:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent datasource method
+
+ * returns: itself so you can daisy chain the other datasource calls
+ */
+ @discardableResult
+ public func numberOfSectionsIn(handler: @escaping () -> Int) -> Self {
+ return update { $0.numberOfSections = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDataSource's tableView(_:titleForHeaderInSection:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent datasource method
+
+ * returns: itself so you can daisy chain the other datasource calls
+ */
+ @discardableResult
+ public func titleForHeaderInSection(handler: @escaping (_ section: Int) -> String?) -> Self {
+ return update { $0.titleForHeaderInSection = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDataSource's tableView(_:titleForFooterInSection:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent datasource method
+
+ * returns: itself so you can daisy chain the other datasource calls
+ */
+ @discardableResult
+ public func titleForFooterInSection(handler: @escaping (_ section: Int) -> String?) -> Self {
+ return update { $0.titleForFooterInSection = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDataSource's tableView(_:canEditRowAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent datasource method
+
+ * returns: itself so you can daisy chain the other datasource calls
+ */
+ @discardableResult
+ public func canEditRowAt(handler: @escaping (_ indexPath: IndexPath) -> Bool) -> Self {
+ return update { $0.canEditRowAt = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDataSource's tableView(_:canMoveRowAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent datasource method
+
+ * returns: itself so you can daisy chain the other datasource calls
+ */
+ @discardableResult
+ public func canMoveRowAt(handler: @escaping (_ indexPath: IndexPath) -> Bool) -> Self {
+ return update { $0.canMoveRowAt = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDataSource's sectionIndexTitles(for:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent datasource method
+
+ * returns: itself so you can daisy chain the other datasource calls
+ */
+ @discardableResult
+ public func sectionIndexTitles(handler: @escaping () -> [String]?) -> Self {
+ return update { $0.sectionIndexTitles = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDataSource's tableView(_:sectionForSectionIndexTitle:at:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent datasource method
+
+ * returns: itself so you can daisy chain the other datasource calls
+ */
+ @discardableResult
+ public func sectionForSectionIndexTitle(handler: @escaping (_ title: String, _ index: Int) -> Int) -> Self {
+ return update { $0.sectionForSectionIndexTitle = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDataSource's tableView(_:commit:forRowAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent datasource method
+
+ * returns: itself so you can daisy chain the other datasource calls
+ */
+ @discardableResult
+ public func commit(handler: @escaping (_ editingStyle: UITableViewCellEditingStyle, _ indexPath: IndexPath) -> Void) -> Self {
+ return update { $0.commit = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDataSource's tableView(_:moveRowAt:to:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent datasource method
+
+ * returns: itself so you can daisy chain the other datasource calls
+ */
+ @discardableResult
+ public func moveRowAt(handler: @escaping (_ sourceIndexPath: IndexPath, _ destinationIndexPath: IndexPath) -> Void) -> Self {
+ return update { $0.moveRowAt = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:leadingSwipeActionsConfigurationForRowAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @available(iOS 11, *) @discardableResult
+ public func leadingSwipeActionsConfigurationForRowAt(handler: @escaping (_ indexPath: IndexPath) -> UISwipeActionsConfiguration?) -> Self {
+ return update { $0.leadingSwipeActionsConfigurationForRowAt = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:trailingSwipeActionsConfigurationForRowAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @available(iOS 11, *) @discardableResult
+ public func trailingSwipeActionsConfigurationForRowAt(handler: @escaping (_ indexPath: IndexPath) -> UISwipeActionsConfiguration?) -> Self {
+ return update { $0.trailingSwipeActionsConfigurationForRowAt = handler }
+ }
+
+ /**
+ Equivalent to implementing UITableViewDelegate's tableView(_:shouldSpringLoadRowAt:) method
+
+ * parameter handler: The closure that will be called in place of its equivalent delegate method
+
+ * returns: itself so you can daisy chain the other delegate calls
+ */
+ @available(iOS 11, *) @discardableResult
+ public func shouldSpringLoadRowAt(handler: @escaping (_ indexPath: IndexPath, _ context: UISpringLoadedInteractionContext) -> Bool) -> Self {
+ return update { $0.shouldSpringLoadRowAt = handler }
+ }
+}
+
+extension UITableView {
+ @discardableResult
+ @objc override func update(handler: (_ delegate: TableViewDelegate) -> Void) -> Self {
+ DelegateWrapper.update(self,
+ delegate: TableViewDelegate(),
+ delegates: &TableViewDelegate.delegates,
+ bind: UITableView.bind) {
+ handler($0.delegate)
+ }
+ return self
+ }
+
+ // MARK: Reset
+ /**
+ Clears any delegate/dataSource closures that were assigned to this
+ `UITableView`. This cleans up memory as well as sets the
+ delegate/dataSource properties to nil. This is typically only used for explicit
+ cleanup. You are not required to call this method.
+ */
+ @objc override public func clearClosureDelegates() {
+ DelegateWrapper.remove(delegator: self, from: &TableViewDelegate.delegates)
+ UITableView.bind(self, nil)
+ }
+
+ fileprivate static func bind(_ delegator: UITableView, _ delegate: TableViewDelegate?) {
+ delegator.delegate = nil
+ delegator.dataSource = nil
+ delegator.delegate = delegate
+ delegator.dataSource = delegate
+ }
+}
diff --git a/Xcode/ClosuresTests/Info.plist b/Xcode/ClosuresTests/Info.plist
new file mode 100644
index 0000000..6c6c23c
--- /dev/null
+++ b/Xcode/ClosuresTests/Info.plist
@@ -0,0 +1,22 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ BNDL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+
+
diff --git a/Xcode/ClosuresTests/KVOTests.swift b/Xcode/ClosuresTests/KVOTests.swift
new file mode 100644
index 0000000..4888f65
--- /dev/null
+++ b/Xcode/ClosuresTests/KVOTests.swift
@@ -0,0 +1,53 @@
+/**
+ The MIT License (MIT)
+ Copyright (c) 2017 Vincent Hesener
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+ is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+import XCTest
+@testable import Closures
+
+fileprivate var shouldRemove: Bool = false
+
+class KVOTests: XCTestCase {
+ fileprivate var object: AnObserved? = AnObserved()
+
+ func testObserving() {
+ shouldRemove = false
+ var didObserve = false
+ object?.observe(\.aValue, until: {_ in shouldRemove}) { _,_ in
+ didObserve = true
+ }
+ object?.aValue = "test1"
+ XCTAssertTrue(didObserve)
+ }
+
+ func testCleanup() {
+ shouldRemove = true
+ var didObserve = false
+ object?.observe(\.aValue, until: {_ in shouldRemove}) { _,_ in
+ didObserve = true
+ }
+ object?.aValue = "test2"
+ XCTAssertFalse(didObserve)
+ }
+
+}
+
+fileprivate class AnObserved: NSObject {
+ @objc dynamic var aValue: String?
+}
diff --git a/Xcode/ClosuresTests/UICollectionViewTests.swift b/Xcode/ClosuresTests/UICollectionViewTests.swift
new file mode 100644
index 0000000..a07f087
--- /dev/null
+++ b/Xcode/ClosuresTests/UICollectionViewTests.swift
@@ -0,0 +1,198 @@
+/**
+ The MIT License (MIT)
+ Copyright (c) 2017 Vincent Hesener
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+ is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+import XCTest
+@testable import Closures
+
+class UICollectionViewTests: XCTestCase {
+
+ func testCollectionViewDelegation() {
+ let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewLayout())
+ let exp = expectation(description: "Not all methods called")
+ exp.expectedFulfillmentCount = 33
+
+ collectionView.willDisplay { _,_ in
+ exp.fulfill()
+ }
+ collectionView.willDisplaySupplementaryView { _,_ in
+ exp.fulfill()
+ }
+ collectionView.didEndDisplaying { _,_ in
+ exp.fulfill()
+ }
+ collectionView.didEndDisplayingSupplementaryView { _,_ in
+ exp.fulfill()
+ }
+ collectionView.shouldHighlightItemAt { _ in
+ exp.fulfill()
+ return true
+ }
+ collectionView.didHighlightItemAt { _ in
+ exp.fulfill()
+ }
+ collectionView.didUnhighlightItemAt { _ in
+ exp.fulfill()
+ }
+ collectionView.shouldSelectItemAt { _ in
+ exp.fulfill()
+ return true
+ }
+ collectionView.shouldDeselectItemAt { _ in
+ exp.fulfill()
+ return true
+ }
+ collectionView.didSelectItemAt { _ in
+ exp.fulfill()
+ }
+ collectionView.didDeselectItemAt { _ in
+ exp.fulfill()
+ }
+ collectionView.shouldShowMenuForItemAt { _ in
+ exp.fulfill()
+ return true
+ }
+ collectionView.canPerformAction {_,_,_ in
+ exp.fulfill()
+ return true
+ }
+ collectionView.performAction { _,_,_ in
+ exp.fulfill()
+ }
+ collectionView.transitionLayoutForOldLayout {
+ exp.fulfill()
+ return UICollectionViewTransitionLayout(currentLayout: $0, nextLayout: $1)
+ }
+ collectionView.targetContentOffsetForProposedContentOffset { _ in
+ exp.fulfill()
+ return .zero
+ }
+ collectionView.targetIndexPathForMoveFromItemAt {
+ exp.fulfill()
+ return $1
+ }
+ collectionView.canFocusItemAt { _ in
+ exp.fulfill()
+ return true
+ }
+ collectionView.indexPathForPreferredFocusedViewIn {
+ exp.fulfill()
+ return nil
+ }
+ collectionView.cellForItemAt { _ in
+ exp.fulfill()
+ return UICollectionViewCell()
+ }
+ collectionView.numberOfItemsInSection { _ in
+ exp.fulfill()
+ return 0
+ }
+ collectionView.numberOfSectionsIn {
+ exp.fulfill()
+ return 0
+ }
+ collectionView.viewForSupplementaryElementOfKind { _,_ in
+ exp.fulfill()
+ return UICollectionReusableView()
+ }
+ collectionView.canMoveItemAt { _ in
+ exp.fulfill()
+ return true
+ }
+ collectionView.moveItemAt { _,_ in
+ exp.fulfill()
+ }
+ collectionView.indexTitlesFor {
+ exp.fulfill()
+ return []
+ }
+ collectionView.indexPathForIndexTitle { _,_ in
+ exp.fulfill()
+ return IndexPath()
+ }
+ collectionView.sizeForItemAt { _ in
+ exp.fulfill()
+ return .zero
+ }
+ collectionView.insetForSectionAt { _ in
+ exp.fulfill()
+ return .zero
+ }
+ collectionView.minimumLineSpacingForSectionAt { _ in
+ exp.fulfill()
+ return 0
+ }
+ collectionView.minimumInteritemSpacingForSectionAt { _ in
+ exp.fulfill()
+ return 0
+ }
+ collectionView.referenceSizeForHeaderInSection { _ in
+ exp.fulfill()
+ return .zero
+ }
+ collectionView.referenceSizeForFooterInSection { _ in
+ exp.fulfill()
+ return .zero
+ }
+
+ XCTAssertNotNil(collectionView.delegate)
+ XCTAssertNotNil(collectionView.dataSource)
+
+ let delegate = collectionView.delegate as! UICollectionViewDelegateFlowLayout
+ let datasource = collectionView.dataSource!
+ let iPath = IndexPath()
+
+ delegate.collectionView!(collectionView, willDisplay: UICollectionViewCell(), forItemAt: iPath)
+ delegate.collectionView!(collectionView, willDisplaySupplementaryView: UICollectionViewCell(), forElementKind: "", at: iPath)
+ delegate.collectionView!(collectionView, didEndDisplaying: UICollectionViewCell(), forItemAt: iPath)
+ delegate.collectionView!(collectionView, didEndDisplayingSupplementaryView: UICollectionViewCell(), forElementOfKind: "", at: iPath)
+
+ _ = delegate.collectionView!(collectionView, shouldHighlightItemAt: iPath)
+ delegate.collectionView!(collectionView, didHighlightItemAt: iPath)
+ delegate.collectionView!(collectionView, didUnhighlightItemAt: iPath)
+ _ = delegate.collectionView!(collectionView, shouldSelectItemAt: iPath)
+ _ = delegate.collectionView!(collectionView, shouldDeselectItemAt: iPath)
+ delegate.collectionView!(collectionView, didSelectItemAt: iPath)
+ delegate.collectionView!(collectionView, didDeselectItemAt: iPath)
+ _ = delegate.collectionView!(collectionView, shouldShowMenuForItemAt: iPath)
+ _ = delegate.collectionView!(collectionView, canPerformAction: #selector(UICollectionViewTests.setUp), forItemAt: iPath, withSender: nil)
+ delegate.collectionView!(collectionView, performAction: #selector(UICollectionViewTests.setUp), forItemAt: iPath, withSender: nil)
+ _ = delegate.collectionView!(collectionView, transitionLayoutForOldLayout: collectionView.collectionViewLayout, newLayout: collectionView.collectionViewLayout)
+ _ = delegate.collectionView!(collectionView, targetContentOffsetForProposedContentOffset: .zero)
+ _ = delegate.collectionView!(collectionView, targetIndexPathForMoveFromItemAt: iPath, toProposedIndexPath: iPath)
+ _ = delegate.collectionView!(collectionView, canFocusItemAt: iPath)
+ _ = delegate.indexPathForPreferredFocusedView!(in: collectionView)
+ _ = datasource.collectionView(collectionView, cellForItemAt: iPath)
+ _ = datasource.collectionView(collectionView, numberOfItemsInSection: 0)
+ _ = datasource.numberOfSections!(in: collectionView)
+ _ = datasource.collectionView!(collectionView, viewForSupplementaryElementOfKind: "", at: iPath)
+ _ = datasource.collectionView!(collectionView, canMoveItemAt: iPath)
+ _ = datasource.collectionView!(collectionView, moveItemAt: iPath, to: iPath)
+ _ = datasource.indexTitles!(for: collectionView)
+ _ = datasource.collectionView!(collectionView, indexPathForIndexTitle: "", at: 0)
+ _ = delegate.collectionView!(collectionView, layout: collectionView.collectionViewLayout, sizeForItemAt: iPath)
+ _ = delegate.collectionView!(collectionView, layout: collectionView.collectionViewLayout, insetForSectionAt: 0)
+ _ = delegate.collectionView!(collectionView, layout: collectionView.collectionViewLayout, minimumLineSpacingForSectionAt: 0)
+ _ = delegate.collectionView!(collectionView, layout: collectionView.collectionViewLayout, minimumInteritemSpacingForSectionAt: 0)
+ _ = delegate.collectionView!(collectionView, layout: collectionView.collectionViewLayout, referenceSizeForHeaderInSection: 0)
+ _ = delegate.collectionView!(collectionView, layout: collectionView.collectionViewLayout, referenceSizeForFooterInSection: 0)
+
+ waitForExpectations(timeout: 0.2)
+ }
+}
diff --git a/Xcode/ClosuresTests/UIControlTests.swift b/Xcode/ClosuresTests/UIControlTests.swift
new file mode 100644
index 0000000..47ad6e2
--- /dev/null
+++ b/Xcode/ClosuresTests/UIControlTests.swift
@@ -0,0 +1,138 @@
+/**
+ The MIT License (MIT)
+ Copyright (c) 2017 Vincent Hesener
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+ is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+import XCTest
+@testable import Closures
+
+class UIControlTests: XCTestCase {
+ let button1 = UIButton(type: .custom)
+ let button2 = UIButton(type: .custom)
+ let textField = UITextField()
+ let stepper = UIStepper()
+
+ override func setUp() {
+ super.setUp()
+ }
+
+ func testButtons() {
+ var tap1Fired = false
+ var tap2Fired = false
+ button1.onTap {
+ tap1Fired = true
+ }
+ button2.onTap {
+ tap2Fired = true
+ }
+ button1.touchUpInside(sender: button1, event: nil)
+ XCTAssert(tap1Fired)
+ XCTAssertFalse(tap2Fired)
+ }
+
+ func testCleanup() {
+ let description = NotificationCenter.closures.debugDescription
+ var button3: UIButton? = UIButton(type: .custom)
+ button3?.onTap {
+
+ }
+ XCTAssertNotEqual(description, NotificationCenter.closures.debugDescription)
+ button3?.touchUpInside(sender: button3!, event: nil)
+ button3 = nil
+ button1.touchUpInside(sender: button1, event: nil)
+ XCTAssertEqual(description, NotificationCenter.closures.debugDescription)
+ }
+
+ func testTextFields() {
+ textField.text = "old text"
+ let newText = "new text"
+ var textChangeFired = false
+ textField.onChange { text in
+ textChangeFired = true
+ XCTAssertEqual(text, newText)
+ XCTAssertEqual(self.textField.text, newText)
+ }
+ textField.text = newText
+ textField.editingChanged(sender: textField, event: nil)
+ XCTAssert(textChangeFired)
+ }
+
+ func testValueChanges() {
+ stepper.value = 0.0
+ let newValue = 1.0
+ var stepperFired = false
+ stepper.onChange { value in
+ stepperFired = true
+ XCTAssertEqual(value, newValue)
+ XCTAssertEqual(self.stepper.value, newValue)
+ }
+ stepper.value = newValue
+ stepper.valueChanged(sender: stepper, event: nil)
+ XCTAssert(stepperFired)
+ }
+
+ func testTextFieldDelegation() {
+ let textField = UITextField()
+ let exp = expectation(description: "Not all methods called")
+ exp.expectedFulfillmentCount = 7
+ textField.shouldBeginEditing {exp.fulfill(); return true}
+ .didBeginEditing {exp.fulfill()}
+ .shouldEndEditing {exp.fulfill(); return true}
+ .didEndEditing {exp.fulfill()}
+ .shouldChangeCharacters { (_, _) -> Bool in exp.fulfill(); return true;}
+ .shouldChangeString {
+ exp.fulfill()
+ XCTAssertEqual($0, "old text")
+ XCTAssertEqual($1, "new text")
+ return true
+ }
+ .shouldClear {exp.fulfill(); return true}
+ .shouldReturn {exp.fulfill(); return true}
+
+ let delegate = textField.delegate
+ XCTAssertNotNil(delegate)
+
+ _ = delegate?.textFieldShouldBeginEditing!(textField)
+ delegate?.textFieldDidBeginEditing!(textField)
+ _ = delegate?.textFieldShouldEndEditing!(textField)
+ delegate?.textFieldDidEndEditing!(textField)
+ textField.text = "old text"
+ _ = delegate?.textField!(textField, shouldChangeCharactersIn: NSMakeRange(0, 3), replacementString: "new")
+ _ = delegate?.textFieldShouldClear!(textField)
+ _ = delegate?.textFieldShouldReturn!(textField)
+
+ waitForExpectations(timeout: 0.2)
+ }
+
+ func testDelegateCleanup() {
+ weak var textField1: UITextField?
+ let textField2 = UITextField()
+ let textField3 = UITextField()
+
+ textField2.didBeginEditing {}
+ XCTAssertEqual(textFieldDelegates.count, 1)
+ autoreleasepool {
+ textField1 = UITextField()
+ textField1?.didBeginEditing {}
+ XCTAssertEqual(textFieldDelegates.count, 2)
+ }
+ XCTAssertEqual(textFieldDelegates.count, 2)
+ textField3.didBeginEditing {}
+ XCTAssertEqual(textFieldDelegates.count, 2)
+ }
+}
diff --git a/Xcode/ClosuresTests/UIGestureRecognizerTests.swift b/Xcode/ClosuresTests/UIGestureRecognizerTests.swift
new file mode 100644
index 0000000..c72db52
--- /dev/null
+++ b/Xcode/ClosuresTests/UIGestureRecognizerTests.swift
@@ -0,0 +1,72 @@
+/**
+ The MIT License (MIT)
+ Copyright (c) 2017 Vincent Hesener
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+ is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+import XCTest
+@testable import Closures
+
+class UIGestureRecognizerTests: XCTestCase {
+ func testDelegateCalls() {
+ let gesture = UIGestureRecognizer()
+ let exp = expectation(description: "Not all methods called")
+ exp.expectedFulfillmentCount = 6
+ let increaseFulfillment: ()->Bool = {
+ exp.fulfill()
+ return true
+ }
+ gesture
+ .shouldBegin(handler: increaseFulfillment)
+ .shouldRecognizeSimultaneouslyWith(handler: {_ in increaseFulfillment()})
+ .shouldRequireFailureOf(handler: {_ in increaseFulfillment()})
+ .shouldBeRequiredToFailBy(handler: {_ in increaseFulfillment()})
+ .shouldReceiveTouch(handler: {_ in increaseFulfillment()})
+ .shouldReceivePress(handler: {_ in increaseFulfillment()})
+
+ _ = gesture.delegate!.gestureRecognizerShouldBegin!(gesture)
+ _ = gesture.delegate!.gestureRecognizer!(gesture, shouldRecognizeSimultaneouslyWith: gesture)
+ _ = gesture.delegate!.gestureRecognizer!(gesture, shouldRequireFailureOf: gesture)
+ _ = gesture.delegate!.gestureRecognizer!(gesture, shouldBeRequiredToFailBy: gesture)
+ _ = gesture.delegate!.gestureRecognizer!(gesture, shouldReceive: UITouch())
+ _ = gesture.delegate!.gestureRecognizer!(gesture, shouldReceive: UIPress())
+
+ waitForExpectations(timeout: 0.2)
+ }
+
+ func testTargetActionCycle() {
+ let exp = expectation(description: "Action handler never fired")
+ let gesture = CustomGesture(exp.fulfill())
+ gesture.trigger()
+ waitForExpectations(timeout: 0.2)
+ }
+}
+
+import UIKit.UIGestureRecognizerSubclass
+public class CustomGesture: UIGestureRecognizer {
+ convenience init(_ handler: @autoclosure @escaping ()->Void) {
+ self.init()
+ Closures.configure(gesture: self) { _ in
+ handler()
+ }
+ }
+
+ func trigger() {
+ let sel = NSSelectorFromString("gestureRecognized")
+ perform(sel)
+ }
+}
diff --git a/Xcode/ClosuresTests/UIImagePickerControllerTests.swift b/Xcode/ClosuresTests/UIImagePickerControllerTests.swift
new file mode 100644
index 0000000..4f29867
--- /dev/null
+++ b/Xcode/ClosuresTests/UIImagePickerControllerTests.swift
@@ -0,0 +1,50 @@
+/**
+ The MIT License (MIT)
+ Copyright (c) 2017 Vincent Hesener
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+ is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+import XCTest
+@testable import Closures
+
+class UIImagePickerControllerTests: XCTestCase {
+ func testDelegateCalls() {
+ let picker = UIImagePickerController()
+ let exp = expectation(description: "Not all methods called")
+ exp.expectedFulfillmentCount = 2
+ let increaseFulfillment: ()->Void = {
+ exp.fulfill()
+ }
+ picker
+ .didFinishPickingMedia {_ in increaseFulfillment()}
+ .didCancel {increaseFulfillment()}
+
+ picker.delegate!.imagePickerController!(picker, didFinishPickingMediaWithInfo: [:])
+ picker.delegate!.imagePickerControllerDidCancel!(picker)
+
+ waitForExpectations(timeout: 0.2)
+ }
+
+ func testPublicRemove() {
+ let picker = UIImagePickerController()
+ picker.didCancel {
+ XCTAssert(false)
+ }
+ picker.clearClosureDelegates()
+ picker.delegate?.imagePickerControllerDidCancel!(picker)
+ }
+}
diff --git a/Xcode/ClosuresTests/UIPickerViewTests.swift b/Xcode/ClosuresTests/UIPickerViewTests.swift
new file mode 100644
index 0000000..799904f
--- /dev/null
+++ b/Xcode/ClosuresTests/UIPickerViewTests.swift
@@ -0,0 +1,50 @@
+/**
+ The MIT License (MIT)
+ Copyright (c) 2017 Vincent Hesener
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+ is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+import XCTest
+@testable import Closures
+
+class UIPickerViewTests: XCTestCase {
+ func testDelegateCalls() {
+ let picker = UIPickerView()
+ let exp = expectation(description: "Not all methods called")
+ exp.expectedFulfillmentCount = 8
+ picker
+ .rowHeightForComponent() { _ in exp.fulfill(); return 0 }
+ .widthForComponent() { _ in exp.fulfill(); return 0 }
+ .titleForRow() { _,_ in exp.fulfill(); return "" }
+ .attributedTitleForRow() { _,_ in exp.fulfill(); return nil }
+ .viewForRow() { _,_,_ in exp.fulfill(); return UIView() }
+ .didSelectRow() { _,_ in exp.fulfill() }
+ .numberOfComponents() { exp.fulfill(); return 0 }
+ .numberOfRowsInComponent() { _ in exp.fulfill(); return 0 }
+
+ _ = picker.delegate!.pickerView!(picker, rowHeightForComponent: 0)
+ _ = picker.delegate!.pickerView!(picker, widthForComponent: 0)
+ _ = picker.delegate!.pickerView!(picker, titleForRow: 0, forComponent: 0)
+ _ = picker.delegate!.pickerView!(picker, attributedTitleForRow: 0, forComponent: 0)
+ _ = picker.delegate!.pickerView!(picker, viewForRow: 0, forComponent: 0, reusing: UIView())
+ _ = picker.delegate?.pickerView!(picker, didSelectRow: 0, inComponent: 0)
+ _ = picker.dataSource?.numberOfComponents(in: picker)
+ _ = picker.dataSource?.pickerView(picker, numberOfRowsInComponent: 0)
+
+ waitForExpectations(timeout: 0.2)
+ }
+}
diff --git a/Xcode/ClosuresTests/UITableViewTests.swift b/Xcode/ClosuresTests/UITableViewTests.swift
new file mode 100644
index 0000000..30c7384
--- /dev/null
+++ b/Xcode/ClosuresTests/UITableViewTests.swift
@@ -0,0 +1,291 @@
+/**
+ The MIT License (MIT)
+ Copyright (c) 2017 Vincent Hesener
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
+ is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+import XCTest
+@testable import Closures
+import ObjectiveC
+
+class UITableViewTests: XCTestCase {
+ class CustomUpdateContext: UITableViewFocusUpdateContext { init(blah: Int){}}
+ func testTableViewDelegation() {
+ let tableView = UITableView()
+ let exp = expectation(description: "Not all methods called")
+ exp.expectedFulfillmentCount = 46
+ tableView.willDisplay { _,_ in
+ exp.fulfill()
+ }
+ tableView.willDisplayHeaderView { _,_ in
+ exp.fulfill()
+ }
+ tableView.willDisplayFooterView { _,_ in
+ exp.fulfill()
+ }
+ tableView.didEndDisplaying { _,_ in
+ exp.fulfill()
+ }
+ tableView.didEndDisplayingHeaderView { _,_ in
+ exp.fulfill()
+ }
+ tableView.didEndDisplayingFooterView { _,_ in
+ exp.fulfill()
+ }
+ tableView.heightForRowAt { _ in
+ exp.fulfill()
+ return 0
+ }
+ tableView.heightForHeaderInSection { _ in
+ exp.fulfill()
+ return 0
+ }
+ tableView.heightForFooterInSection { _ in
+ exp.fulfill()
+ return 0
+ }
+ tableView.estimatedHeightForRowAt { _ in
+ exp.fulfill()
+ return 0
+ }
+ tableView.estimatedHeightForHeaderInSection { _ in
+ exp.fulfill()
+ return 0
+ }
+ tableView.estimatedHeightForFooterInSection { _ in
+ exp.fulfill()
+ return 0
+ }
+ tableView.viewForHeaderInSection { _ in
+ exp.fulfill()
+ return nil
+ }
+ tableView.viewForFooterInSection { _ in
+ exp.fulfill()
+ return nil
+ }
+ tableView.accessoryButtonTappedForRowWith { _ in
+ exp.fulfill()
+ }
+ tableView.shouldHighlightRowAt { _ in
+ exp.fulfill()
+ return true
+ }
+ tableView.didHighlightRowAt { _ in
+ exp.fulfill()
+ }
+ tableView.didUnhighlightRowAt { _ in
+ exp.fulfill()
+ }
+ tableView.willSelectRowAt { _ in
+ exp.fulfill()
+ return nil
+ }
+ tableView.willDeselectRowAt { _ in
+ exp.fulfill()
+ return nil
+ }
+ tableView.didSelectRowAt { _ in
+ exp.fulfill()
+ }
+ tableView.didDeselectRowAt { _ in
+ exp.fulfill()
+ }
+ tableView.editingStyleForRowAt { _ in
+ exp.fulfill()
+ return .delete
+ }
+ tableView.titleForDeleteConfirmationButtonForRowAt { _ in
+ exp.fulfill()
+ return nil
+ }
+ tableView.editActionsForRowAt { _ in
+ exp.fulfill()
+ return nil
+ }
+ tableView.shouldIndentWhileEditingRowAt { _ in
+ exp.fulfill()
+ return true
+ }
+ tableView.willBeginEditingRowAt { _ in
+ exp.fulfill()
+ }
+ tableView.didEndEditingRowAt { _ in
+ exp.fulfill()
+ }
+ tableView.targetIndexPathForMoveFromRowAt {
+ exp.fulfill()
+ return $1
+ }
+ tableView.indentationLevelForRowAt { _ in
+ exp.fulfill()
+ return 0
+ }
+ tableView.shouldShowMenuForRowAt { _ in
+ exp.fulfill()
+ return true
+ }
+ tableView.canPerformAction {_,_,_ in
+ exp.fulfill()
+ return true
+ }
+ tableView.performAction { _,_,_ in
+ exp.fulfill()
+ }
+ tableView.canFocusRowAt { _ in
+ exp.fulfill()
+ return true
+ }
+ tableView.indexPathForPreferredFocusedView {
+ exp.fulfill()
+ return nil
+ }
+ tableView.numberOfRows { _ in
+ exp.fulfill()
+ return 0
+ }
+ tableView.cellForRow { _ in
+ exp.fulfill()
+ return UITableViewCell()
+ }
+ tableView.numberOfSectionsIn {
+ exp.fulfill()
+ return 0
+ }
+ tableView.titleForHeaderInSection { _ in
+ exp.fulfill()
+ return nil
+ }
+ tableView.titleForFooterInSection { _ in
+ exp.fulfill()
+ return nil
+ }
+ tableView.canEditRowAt { _ in
+ exp.fulfill()
+ return true
+ }
+ tableView.canMoveRowAt { _ in
+ exp.fulfill()
+ return true
+ }
+ tableView.sectionIndexTitles {
+ exp.fulfill()
+ return nil
+ }
+ tableView.sectionForSectionIndexTitle { _,_ in
+ exp.fulfill()
+ return 0
+ }
+ tableView.commit { _,_ in
+ exp.fulfill()
+ }
+ tableView.moveRowAt { _,_ in
+ exp.fulfill()
+ }
+
+ XCTAssertNotNil(tableView.delegate)
+ XCTAssertNotNil(tableView.dataSource)
+
+ let delegate = tableView.delegate!
+ let datasource = tableView.dataSource!
+ let iPath = IndexPath()
+
+ delegate.tableView!(tableView, willDisplay: UITableViewCell(), forRowAt: iPath)
+ delegate.tableView!(tableView, willDisplayHeaderView: UITableViewCell(), forSection: 0)
+ delegate.tableView!(tableView, willDisplayFooterView: UITableViewCell(), forSection: 0)
+ delegate.tableView!(tableView, didEndDisplaying: UITableViewCell(), forRowAt: iPath)
+ delegate.tableView!(tableView, didEndDisplayingHeaderView: UITableViewCell(), forSection: 0)
+ delegate.tableView!(tableView, didEndDisplayingFooterView: UITableViewCell(), forSection: 0)
+ _ = delegate.tableView!(tableView, heightForRowAt: iPath)
+ _ = delegate.tableView!(tableView, heightForHeaderInSection: 0)
+ _ = delegate.tableView!(tableView, heightForFooterInSection: 0)
+ _ = delegate.tableView!(tableView, estimatedHeightForRowAt: iPath)
+ _ = delegate.tableView!(tableView, estimatedHeightForHeaderInSection: 0)
+ _ = delegate.tableView!(tableView, estimatedHeightForFooterInSection: 0)
+ _ = delegate.tableView!(tableView, viewForHeaderInSection: 0)
+ _ = delegate.tableView!(tableView, viewForFooterInSection: 0)
+ delegate.tableView!(tableView, accessoryButtonTappedForRowWith: iPath)
+ _ = delegate.tableView!(tableView, shouldHighlightRowAt: iPath)
+ delegate.tableView!(tableView, didHighlightRowAt: iPath)
+ delegate.tableView!(tableView, didUnhighlightRowAt: iPath)
+ _ = delegate.tableView!(tableView, willSelectRowAt: iPath)
+ _ = delegate.tableView!(tableView, willDeselectRowAt: iPath)
+ delegate.tableView!(tableView, didSelectRowAt: iPath)
+ delegate.tableView!(tableView, didDeselectRowAt: iPath)
+ _ = delegate.tableView!(tableView, editingStyleForRowAt: iPath)
+ _ = delegate.tableView!(tableView, titleForDeleteConfirmationButtonForRowAt: iPath)
+ _ = delegate.tableView!(tableView, editActionsForRowAt: iPath)
+ _ = delegate.tableView!(tableView, shouldIndentWhileEditingRowAt: iPath)
+ delegate.tableView!(tableView, willBeginEditingRowAt: iPath)
+ delegate.tableView!(tableView, didEndEditingRowAt: iPath)
+ _ = delegate.tableView!(tableView, targetIndexPathForMoveFromRowAt: iPath, toProposedIndexPath: iPath)
+ _ = delegate.tableView!(tableView, indentationLevelForRowAt: iPath)
+ _ = delegate.tableView!(tableView, shouldShowMenuForRowAt: iPath)
+ _ = delegate.tableView!(tableView, canPerformAction: #selector(UITableViewTests.setUp), forRowAt: iPath, withSender: nil)
+ _ = delegate.tableView!(tableView, performAction: #selector(UITableViewTests.setUp), forRowAt: iPath, withSender: nil)
+ _ = delegate.tableView!(tableView, canFocusRowAt: iPath)
+ _ = delegate.indexPathForPreferredFocusedView!(in: tableView)
+ _ = datasource.tableView(tableView, numberOfRowsInSection: 0)
+ _ = datasource.tableView(tableView, cellForRowAt: iPath)
+ _ = datasource.numberOfSections!(in: tableView)
+ _ = datasource.tableView!(tableView, titleForHeaderInSection: 0)
+ _ = datasource.tableView!(tableView, titleForFooterInSection: 0)
+ _ = datasource.tableView!(tableView, canEditRowAt: iPath)
+ _ = datasource.tableView!(tableView, canMoveRowAt: iPath)
+ _ = datasource.sectionIndexTitles!(for: tableView)
+ _ = datasource.tableView!(tableView, sectionForSectionIndexTitle: "", at: 0)
+ datasource.tableView!(tableView, commit: .delete, forRowAt: iPath)
+ datasource.tableView!(tableView, moveRowAt: iPath, to: iPath)
+
+ waitForExpectations(timeout: 0.2)
+ }
+
+ class ASubClass: UITableView {}
+
+ func testSubclassing() {
+ let tableView = UITableView()
+ .accessoryButtonTappedForRowWith { _ in
+ }
+ .didScroll { _ in }
+ XCTAssertNotNil(tableView.delegate?.tableView?(tableView, accessoryButtonTappedForRowWith: IndexPath(row: 0, section: 0)))
+
+ let aSubClass = ASubClass()
+ .accessoryButtonTappedForRowWith { _ in
+ }
+ .didScroll { _ in }
+ XCTAssertNotNil(aSubClass.delegate?.tableView?(aSubClass, accessoryButtonTappedForRowWith: IndexPath(row: 0, section: 0)))
+ }
+
+ func testTableViewMethodExpectations() {
+ let lastKnownDelegateCount = 41
+ let lastKnownDataSourceCount = 11
+
+ let delegateCount = numberOfMethods(in: UITableViewDelegate.self)
+ let dataSourceCount = numberOfMethods(in: UITableViewDataSource.self)
+
+ XCTAssertEqual(lastKnownDelegateCount, delegateCount)
+ XCTAssertEqual(lastKnownDataSourceCount, dataSourceCount)
+ }
+
+ fileprivate func numberOfMethods(in p: Protocol) -> Int {
+ var optionalCount: UInt32 = 0
+ var requiredCount: UInt32 = 0
+ _ = protocol_copyMethodDescriptionList(p, false, true, &optionalCount)
+ _ = protocol_copyMethodDescriptionList(p, true, true, &requiredCount)
+ return Int(optionalCount + requiredCount)
+ }
+}
diff --git a/Xcode/Playground/ClosuresDemo.playground/Pages/KVO.xcplaygroundpage/Contents.swift b/Xcode/Playground/ClosuresDemo.playground/Pages/KVO.xcplaygroundpage/Contents.swift
new file mode 100644
index 0000000..8ad11ea
--- /dev/null
+++ b/Xcode/Playground/ClosuresDemo.playground/Pages/KVO.xcplaygroundpage/Contents.swift
@@ -0,0 +1,44 @@
+import Foundation
+import Closures
+//: [Go back](@previous)
+/*:
+ # Key-Value Observing
+
+ The Foundation team at Apple has made great
+ strides to become more closuresque, and Swift
+ 4 has truly proved that with KVO updates.
+
+ The API is almost perfect so there isn't too
+ much to add. There is only one convenience
+ method to improve some of the boiler plate
+ code for a typical use case.
+
+ The existing API requires you to save the observer
+ and store it for the duration of the observation.
+ Not bad, but with a little polish we can set and
+ forget. Although it looks like more effort to
+ provide a conditional remove handler, with the
+ helper var `selfDeinits` on every NSObject,
+ we don't have to hold onto distracting observer
+ variables in our view controllers.
+
+ In this example we'll observe the `text`
+ property of a UITextField.
+ */
+let textField = UITextField()
+class MyClass: NSObject {
+ override init() { super.init()
+ textField.observe(\.text, until: selfDeinits) { obj,change in
+ print("Observed the change to \"\(obj.text!)\"")
+ }
+ }
+}
+
+var myObject: MyClass? = MyClass()
+textField.text = "🐒" // this will be observed
+myObject = nil
+textField.text = "This will NOT be observed"
+
+//: * * * *
+//: [Click here to explore using **UIImagePickerController** closures](@next)
+//: * * * *
diff --git a/Xcode/Playground/ClosuresDemo.playground/Pages/UICollectionView.xcplaygroundpage/Contents.swift b/Xcode/Playground/ClosuresDemo.playground/Pages/UICollectionView.xcplaygroundpage/Contents.swift
new file mode 100644
index 0000000..007e608
--- /dev/null
+++ b/Xcode/Playground/ClosuresDemo.playground/Pages/UICollectionView.xcplaygroundpage/Contents.swift
@@ -0,0 +1,75 @@
+import UIKit;import PlaygroundSupport;PlaygroundPage.current.liveView = viewController
+import Closures
+//: [Go back](@previous)
+/*:
+ # UICollectionView
+ ## Delegate and DataSource
+
+ `UICollectionView` closures make it easy to implement `UICollectionViewDelegate` and
+ `UICollectionViewDataSource` protocol methods in an organized way. The following
+ is an example of a simple collection view that displays strings in a basic cell.
+ */
+let collectionView = viewController.collectionView!
+let countries = getAllCountries()
+
+func loadCollectionView() {
+ collectionView.register(MyCustomCollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
+
+ collectionView
+ .numberOfItemsInSection { _ in
+ countries.count
+ }.cellForItemAt { indexPath in
+ let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! MyCustomCollectionViewCell
+ cell.textLabel.text = countries[indexPath.item]
+ return cell
+ }.didSelectItemAt {
+ print("\(countries[$0.item]) selected")
+ }.reloadData()
+}
+loadCollectionView()
+/*:
+ ## Arrays
+ These operations are common. Usually, they involve populating the `UICollectionView`
+ with the values from an array. `Closures` framework gives you a convenient
+ way to pass your array to the collection view, so that it can perform the boilerplate
+ operations for you, especially the ones that are required to make the collection
+ view perform at a basic level.
+ */
+/*:
+ Let's setup our segmented control to load the collection view with different options.
+ When binding to an Array it will show the same countries, but reversed, so
+ you can visually see the change after tapping the segmented control.
+ */
+let segmentedControl = viewController.segmentedControl!
+segmentedControl.onChange {
+ switch $0 {
+ case 1:
+ loadCollectionView(countries: Array(countries.reversed()))
+ default:
+ loadCollectionView()
+ }
+}
+/*:
+ * Important:
+ Please remember that Swift `Array`s are value types. This means that they
+ are copied when mutated. When the values or sequence of your array changes, you will
+ need to call `addFlowElements` again, just before you call reloadData().
+ */
+func loadCollectionView(countries: [String]) {
+ collectionView
+ .addFlowElements(countries, cell: MyCustomCollectionViewCell.self) { country, cell, index in
+ cell.textLabel.text = country
+ }.reloadData()
+
+ /**
+ This also allows you to override any default behavior so
+ you aren't overly committed, even though you're initially binding everything
+ to the `Array`.
+ */
+ collectionView.didSelectItemAt {
+ print("\(countries[$0.item]) selected from array")
+ }
+}
+//: * * * *
+//: [Click here to explore using **UIGestureRecognizer** closures](@next)
+//: * * * *
diff --git a/Xcode/Playground/ClosuresDemo.playground/Pages/UICollectionView.xcplaygroundpage/Resources/CollectionViewDemoViewController.xib b/Xcode/Playground/ClosuresDemo.playground/Pages/UICollectionView.xcplaygroundpage/Resources/CollectionViewDemoViewController.xib
new file mode 100644
index 0000000..4acd89e
--- /dev/null
+++ b/Xcode/Playground/ClosuresDemo.playground/Pages/UICollectionView.xcplaygroundpage/Resources/CollectionViewDemoViewController.xib
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Xcode/Playground/ClosuresDemo.playground/Pages/UICollectionView.xcplaygroundpage/Sources/Setup.swift b/Xcode/Playground/ClosuresDemo.playground/Pages/UICollectionView.xcplaygroundpage/Sources/Setup.swift
new file mode 100644
index 0000000..fec843f
--- /dev/null
+++ b/Xcode/Playground/ClosuresDemo.playground/Pages/UICollectionView.xcplaygroundpage/Sources/Setup.swift
@@ -0,0 +1,25 @@
+import UIKit
+import PlaygroundSupport
+
+public class CollectionViewDemoViewController: BaseViewController {
+ @IBOutlet public var collectionView: UICollectionView!
+ @IBOutlet public var segmentedControl: UISegmentedControl!
+}
+
+public class MyCustomCollectionViewCell: UICollectionViewCell {
+ required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }
+ public let textLabel = UILabel()
+ public override init(frame: CGRect) {
+ super.init(frame: frame)
+ textLabel.font = .systemFont(ofSize: 8)
+ let labelContainer = contentView
+ textLabel.translatesAutoresizingMaskIntoConstraints = false
+ labelContainer.addSubview(textLabel)
+ textLabel.leadingAnchor.constraint(equalTo: labelContainer.leadingAnchor).isActive = true
+ textLabel.trailingAnchor.constraint(equalTo: labelContainer.trailingAnchor).isActive = true
+ textLabel.centerYAnchor.constraint(equalTo: labelContainer.centerYAnchor).isActive = true
+ }
+}
+
+public let viewController = CollectionViewDemoViewController(nibName: "CollectionViewDemoViewController", bundle: nil)
+public let view = viewController.view!
diff --git a/Xcode/Playground/ClosuresDemo.playground/Pages/UIControls.xcplaygroundpage/Contents.swift b/Xcode/Playground/ClosuresDemo.playground/Pages/UIControls.xcplaygroundpage/Contents.swift
new file mode 100644
index 0000000..1250082
--- /dev/null
+++ b/Xcode/Playground/ClosuresDemo.playground/Pages/UIControls.xcplaygroundpage/Contents.swift
@@ -0,0 +1,107 @@
+import UIKit;import PlaygroundSupport;PlaygroundPage.current.liveView = viewController
+import Closures
+/*:
+ # UIControl
+
+ `Closures` framework adds closures to many features of `UIControl` subclasses.
+ Below are some common actions on some common controls.
+
+ ## UIButton Tap
+
+ A common target-action used is a button tap event. This one is
+ really simple:
+ */
+button.onTap {
+ log("Button tapped")
+}
+/*:
+ ## Value Changed Events
+ Most other `UIControl` types are only interesting for their
+ value changes. The following are examples of how to observe
+ value changes on other popular `UIControl`s.
+ */
+/*:
+ * * * *
+ ### UISlider
+ */
+slider.onChange { value in
+ log("slider: \(value)")
+}
+/*:
+ * * * *
+ ### UISegmentedControl
+ */
+segmentedControl.onChange { index in
+ log("segment: \(index)")
+}
+/*:
+ * * * *
+ ### UIStepper
+ */
+stepper.onChange { value in
+ log("stepper: \(value)")
+}
+/*:
+ * * * *
+ ### UIPageControl
+ */
+pageControl.onChange { index in
+ log("page: \(index)")
+}
+/*:
+ * * * *
+ ### UISwitch
+ */
+uiSwitch.onChange { isOn in
+ log("swith is: \(isOn ? "on" : "off")")
+}
+/*:
+ * * * *
+ ### UIDatePicker
+ */
+datePicker.onChange { date in
+ log(date)
+}
+/*:
+ * * * *
+ ### UITextField
+
+ In addtion to text changes, `UITextField` has some other convenient wrappers
+ around some commonly needed actions. Below are examples of some events that
+ can you can observe. Notice the use of daisy chaining in order to keep it
+ concise and organized.
+ */
+textfield
+ .onChange { newText in
+ log(newText)
+ }.onEditingBegan {
+ log("Editing began")
+ }.onEditingEnded {
+ log("Editing ended")
+ }.onReturn {
+ log("Return key tapped")
+}
+/*:
+ ## Delegation
+ `UITextField` also employs delegation to help define its behavior. Below
+ is how you would implement `UITextFieldDelegate` methods using closures.
+ */
+textfield
+ .didBeginEditing {
+ log("Did begin editing delegate")
+ }.shouldClear {
+ log("Text clearing")
+ return true
+ }.shouldChangeCharacters { range, string in
+ return true
+}
+/*:
+ Although these convenience closures are not exhaustive, there is a way to
+ use a closure for any `UIControlEvents`.
+ */
+button.on(.touchDragInside) { sender, event in
+ log("Dragging inside button")
+}
+//: * * * *
+//: [Click here to explore using **UITableView** closures](@next)
+//: * * * *
diff --git a/Xcode/Playground/ClosuresDemo.playground/Pages/UIControls.xcplaygroundpage/Resources/ControlsDemoViewController.xib b/Xcode/Playground/ClosuresDemo.playground/Pages/UIControls.xcplaygroundpage/Resources/ControlsDemoViewController.xib
new file mode 100644
index 0000000..fbe4287
--- /dev/null
+++ b/Xcode/Playground/ClosuresDemo.playground/Pages/UIControls.xcplaygroundpage/Resources/ControlsDemoViewController.xib
@@ -0,0 +1,145 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Courier
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Xcode/Playground/ClosuresDemo.playground/Pages/UIControls.xcplaygroundpage/Sources/Setup.swift b/Xcode/Playground/ClosuresDemo.playground/Pages/UIControls.xcplaygroundpage/Sources/Setup.swift
new file mode 100644
index 0000000..264fd4f
--- /dev/null
+++ b/Xcode/Playground/ClosuresDemo.playground/Pages/UIControls.xcplaygroundpage/Sources/Setup.swift
@@ -0,0 +1,58 @@
+import UIKit
+import PlaygroundSupport
+
+public class ControlsDemoViewController: BaseViewController, PrintableController {
+
+ @IBOutlet public var button: UIButton!
+ @IBOutlet public var textField: UITextField!
+ @IBOutlet public var uiSwitch: UISwitch!
+ @IBOutlet public var slider: UISlider!
+ @IBOutlet public var segmentedControl: UISegmentedControl!
+ @IBOutlet public var stepper: UIStepper!
+ @IBOutlet public var pageControl: UIPageControl!
+ @IBOutlet public var datePicker: UIDatePicker!
+ @IBOutlet public var scrollView: UIScrollView!
+ @IBOutlet public var stackView: UIStackView!
+ @IBOutlet public var printableTextView: UITextView?
+
+ public override func touchesEnded(_ touches: Set, with event: UIEvent?) {
+ view.endEditing(true)
+ }
+
+ public override func viewDidAppear(_ animated: Bool) {
+ super.viewDidAppear(animated)
+ scrollView.contentSize = stackView.bounds.size
+ }
+
+ @IBAction public func doneTapped() {
+ view.endEditing(true)
+ }
+
+ public func convert(date: Date) -> String {
+ let formatter = DateFormatter()
+ formatter.dateFormat = "MM/dd/yyyy"
+ return formatter.string(from: date)
+ }
+}
+
+public let viewController = ControlsDemoViewController(nibName: "ControlsDemoViewController", bundle: nil)
+public let view = viewController.view!
+
+public func log(_ string: String) {
+ viewController.print(text: string)
+ print(string)
+}
+
+public func log(_ date: Date) {
+ let dateString = viewController.convert(date: date)
+ log(dateString)
+}
+
+public let button = viewController.button!
+public let textfield = viewController.textField!
+public let slider = viewController.slider!
+public let segmentedControl = viewController.segmentedControl!
+public let stepper = viewController.stepper!
+public let pageControl = viewController.pageControl!
+public let uiSwitch = viewController.uiSwitch!
+public let datePicker = viewController.datePicker!
diff --git a/Xcode/Playground/ClosuresDemo.playground/Pages/UIGestureRecognizer.xcplaygroundpage/Contents.swift b/Xcode/Playground/ClosuresDemo.playground/Pages/UIGestureRecognizer.xcplaygroundpage/Contents.swift
new file mode 100644
index 0000000..555170d
--- /dev/null
+++ b/Xcode/Playground/ClosuresDemo.playground/Pages/UIGestureRecognizer.xcplaygroundpage/Contents.swift
@@ -0,0 +1,58 @@
+import UIKit;import PlaygroundSupport;PlaygroundPage.current.liveView = viewController
+import Closures
+//: [Go back](@previous)
+/*:
+ # UIGestureRecognizer
+
+ The `UIGestureRecognizer` initializers and delegation wrappers
+ make it easy to add gesture recognizers to views. It also uses
+ closures instead of target-action and delegation.
+
+ ## Target-Action Initializers
+
+ The following is how you would add a double tap gesture
+ recognizer to your view using one of the custom initializers.
+ As always, we have a closure handler to respond to the gesture's
+ double tap action.
+ */
+let doubleTapGesture = UITapGestureRecognizer(tapsRequired: 2) { _ in
+ log("double tapped")
+}
+view.addGestureRecognizer(doubleTapGesture)
+/*:
+ These convenience initializers, delegate closures, and closure recognizers
+ have been added to all of the existing concrete subclasses, including:
+
+ * `UITapGestureRecognizer`
+ * `UIPinchGestureRecognizer`
+ * `UIRotationGestureRecognizer`
+ * `UISwipeGestureRecognizer`
+ * `UIPanGestureRecognizer`
+ * `UIScreenEdgePanGestureRecognizer`
+ * `UILongPressGestureRecognizer`
+
+ There is also a method for you to configure a custom gesture recognizer
+ to use closure handlers for recognition:
+ */
+let myCustomGesture = MyCustomGestureRecognizer()
+configure(gesture: myCustomGesture) { _ in
+ /// a closure that's called when the gesture has ended
+}
+/*:
+ ## Delegation
+ With convenient extension methods on `UIGestureRecognizer` and `UIView`,
+ we can daisy chain an entire gesture cycle, including responding
+ to `UIGestureRecognizerDelegate` methods.
+ */
+view
+ .addPanGesture() { pan in
+ guard pan.state == .ended else { return }
+ log("view panned")
+ }.shouldBegin() {
+ true
+ }.shouldRecognizeSimultaneouslyWith {
+ $0 === doubleTapGesture
+}
+//: * * * *
+//: [Click here to explore using **UIPickerView** closures](@next)
+//: * * * *
diff --git a/Xcode/Playground/ClosuresDemo.playground/Pages/UIGestureRecognizer.xcplaygroundpage/Resources/GesturesDemoViewController.xib b/Xcode/Playground/ClosuresDemo.playground/Pages/UIGestureRecognizer.xcplaygroundpage/Resources/GesturesDemoViewController.xib
new file mode 100644
index 0000000..8d59557
--- /dev/null
+++ b/Xcode/Playground/ClosuresDemo.playground/Pages/UIGestureRecognizer.xcplaygroundpage/Resources/GesturesDemoViewController.xib
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+ Courier
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Xcode/Playground/ClosuresDemo.playground/Pages/UIGestureRecognizer.xcplaygroundpage/Sources/Setup.swift b/Xcode/Playground/ClosuresDemo.playground/Pages/UIGestureRecognizer.xcplaygroundpage/Sources/Setup.swift
new file mode 100644
index 0000000..a78cb9b
--- /dev/null
+++ b/Xcode/Playground/ClosuresDemo.playground/Pages/UIGestureRecognizer.xcplaygroundpage/Sources/Setup.swift
@@ -0,0 +1,16 @@
+import UIKit
+import PlaygroundSupport
+
+public class GesturesDemoViewController: BaseViewController, PrintableController {
+ @IBOutlet public var printableTextView: UITextView?
+}
+
+public let viewController = GesturesDemoViewController(nibName: "GesturesDemoViewController", bundle: nil)
+public let view = viewController.view!
+
+public func log(_ string: String) {
+ viewController.print(text: string)
+ print(string)
+}
+
+public class MyCustomGestureRecognizer: UIGestureRecognizer {}
diff --git a/Xcode/Playground/ClosuresDemo.playground/Pages/UIImagePickerController.xcplaygroundpage/Contents.swift b/Xcode/Playground/ClosuresDemo.playground/Pages/UIImagePickerController.xcplaygroundpage/Contents.swift
new file mode 100644
index 0000000..67fdb99
--- /dev/null
+++ b/Xcode/Playground/ClosuresDemo.playground/Pages/UIImagePickerController.xcplaygroundpage/Contents.swift
@@ -0,0 +1,44 @@
+import UIKit
+import Closures
+//: [Go back](@previous)
+/*:
+ # UIImagePickerController
+
+ * Callout(Meh):
+ UIImagePickerController doesn't exactly work in playgrounds yet
+ so this is just example code for now.
+
+ The following is an example of using closures to
+ select media from a `UIImagePickerController`.
+ A simple picker is created which defaults to allowing
+ images to be selected from the photos library. This
+ initializer will call the handler when an image is selected.
+ When preseting with the `present(from:)` method,
+ the `UIImagePickerController` will dismiss itself on user cancel
+ or after the picker has picked its content (after the closure
+ is called).
+
+ ```swift
+ UIImagePickerController() { result,picker in
+ myImageView.image = result.editedImage
+ }.present(from: self)
+ ```
+
+ You can also customize the picker if you need more control, including
+ setting your own initial values and delegate callbacks.
+ The following is a verbose example using most of the possible
+ customization points.
+
+ ```swift
+ UIImagePickerController(
+ source: .camera,
+ allow: [.image, .movie],
+ cameraOverlay: nil,
+ showsCameraControls: true) { result,picker in
+ myImageView.image = result.originalImage
+ }.didCancel { [weak self] in
+ // some custom didCancel implementation
+ self?.dismiss(animated: animation.animate)
+ }.present(from: self)
+ ```
+ */
diff --git a/Xcode/Playground/ClosuresDemo.playground/Pages/UIPickerView.xcplaygroundpage/Contents.swift b/Xcode/Playground/ClosuresDemo.playground/Pages/UIPickerView.xcplaygroundpage/Contents.swift
new file mode 100644
index 0000000..1426798
--- /dev/null
+++ b/Xcode/Playground/ClosuresDemo.playground/Pages/UIPickerView.xcplaygroundpage/Contents.swift
@@ -0,0 +1,103 @@
+import UIKit;import PlaygroundSupport;PlaygroundPage.current.liveView = viewController
+import Closures
+//: [Go back](@previous)
+/*:
+ # UIPickerView
+ ## Delegate and DataSource
+
+ `UIPickerView` closures make it easy to implement `UIPickerViewDelegate` and
+ `UIPickerViewDataSource` protocol methods in an organized way. The following
+ is an example of a simple collection view that displays strings in each row.
+ */
+let pickerView = viewController.pickerView!
+let countries = getAllCountries()
+
+func loadPickerView() {
+ pickerView
+ .numberOfRowsInComponent() { _ in
+ countries.count
+ }.titleForRow() { row, component in
+ countries[row]
+ }.didSelectRow { row, component in
+ log("\(countries[row]) selected")
+ }.reloadAllComponents()
+}
+loadPickerView()
+/*:
+ ## Arrays
+ These operations are common. Usually, they involve populating the `UIPickerView`
+ with the values from an array. `Closures` framework gives you a convenient
+ way to pass your collection to the table view, so that it can perform the boilerplate
+ operations for you, especially the ones that are required to make the picker
+ view perform at a basic level.
+
+ Let's setup our segmented control to load the picker view with different options.
+ When binding to an Array it will show the same countries, but reversed, so
+ you can visually see the change after tapping the segmented control.
+ */
+let segmentedControl = viewController.segmentedControl!
+segmentedControl.onChange {
+ switch $0 {
+ case 1:
+ loadPickerView(countries: countries)
+ case 2:
+ loadPickerView(countries: getCountryDayComponents())
+ default:
+ loadPickerView()
+ }
+}
+/*:
+ * Important:
+ Please remember that Swift `Array`s are value types. This means that they
+ are copied when mutaded. When the values or sequence of your array changes, you will
+ need to call one of the `add` methods again, just before you
+ call reloadData().
+ */
+func loadPickerView(countries: [String]) {
+ let reversed = Array(countries.reversed())
+ pickerView
+ .addStrings(reversed) { country,component,row in
+ log("\(country) selected from array")
+ }.reloadAllComponents()
+}
+/*:
+ * Note:
+ Be sure to note that most of the closure callbacks in these array binding
+ methods switch the order of the parameters of row and component. Most of the
+ `UIPickerView` delegate/datasource method parameters pass `row,component`. The
+ parameters on the `add` methods switch the order and send `component,row`
+
+ ### Multiple Components
+ And finally, you can just as easily show mutliple components by binding a
+ 2-dimensional array. When using this method, the outer dimension of the array is the
+ component (columns) and the inner dimension are the rows in that component.
+
+ ```swift
+ let anElement = myTwoDArray[component][row]
+ ```
+
+ In this example, the more verbose row handler is provided just to show the
+ different variations. Adding multiple components has a convenient method to
+ deal with string only arrays also.
+ */
+func loadPickerView(countries: [[String]]) {
+ pickerView.addComponents(
+ countries,
+ rowTitle: { country,component,row in
+ country},
+ didSelect: { country,component,row in
+ log("\(country) selected from 2D Array")
+ })
+
+ /**
+ This also allows you to override any default behavior so
+ you aren't overly committed, even though you're initially binding everything
+ to the `Array`.
+ */
+ pickerView.widthForComponent { component in
+ component == 0 ? 200 : 100
+ }.reloadAllComponents()
+}
+//: * * * *
+//: [Click here to explore using **KVO** closures](@next)
+//: * * * *
diff --git a/Xcode/Playground/ClosuresDemo.playground/Pages/UIPickerView.xcplaygroundpage/Resources/PickerDemoViewController.xib b/Xcode/Playground/ClosuresDemo.playground/Pages/UIPickerView.xcplaygroundpage/Resources/PickerDemoViewController.xib
new file mode 100644
index 0000000..32a6456
--- /dev/null
+++ b/Xcode/Playground/ClosuresDemo.playground/Pages/UIPickerView.xcplaygroundpage/Resources/PickerDemoViewController.xib
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+ Courier
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Xcode/Playground/ClosuresDemo.playground/Pages/UIPickerView.xcplaygroundpage/Sources/Setup.swift b/Xcode/Playground/ClosuresDemo.playground/Pages/UIPickerView.xcplaygroundpage/Sources/Setup.swift
new file mode 100644
index 0000000..d889330
--- /dev/null
+++ b/Xcode/Playground/ClosuresDemo.playground/Pages/UIPickerView.xcplaygroundpage/Sources/Setup.swift
@@ -0,0 +1,22 @@
+import UIKit
+import PlaygroundSupport
+
+public class PickerDemoViewController: BaseViewController, PrintableController {
+ @IBOutlet public var segmentedControl: UISegmentedControl!
+ @IBOutlet public var pickerView: UIPickerView!
+ @IBOutlet public var printableTextView: UITextView?
+}
+
+public let viewController = PickerDemoViewController(nibName: "PickerDemoViewController", bundle: nil)
+public let view = viewController.view!
+
+public func log(_ string: String) {
+ viewController.print(text: string)
+ print(string)
+}
+
+public func getCountryDayComponents() -> [[String]] {
+ let days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
+ let countries = getAllCountries()
+ return [countries, days]
+}
diff --git a/Xcode/Playground/ClosuresDemo.playground/Pages/UITableView.xcplaygroundpage/Contents.swift b/Xcode/Playground/ClosuresDemo.playground/Pages/UITableView.xcplaygroundpage/Contents.swift
new file mode 100644
index 0000000..e0aaf9e
--- /dev/null
+++ b/Xcode/Playground/ClosuresDemo.playground/Pages/UITableView.xcplaygroundpage/Contents.swift
@@ -0,0 +1,97 @@
+import UIKit;import PlaygroundSupport;PlaygroundPage.current.liveView = viewController
+import Closures
+//: [Go back](@previous)
+/*:
+ # UITableView
+ ## Delegate and DataSource
+
+ `UITableView` closures make it easy to implement `UITableViewDelegate` and
+ `UITableViewDataSource` protocol methods in an organized way. The following
+ is an example of a simple table view that displays strings in a basic cell.
+ */
+let tableView = viewController.tableView!
+let countries = getAllCountries()
+
+func loadTableView() {
+ tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
+
+ tableView
+ .numberOfRows { _ in
+ countries.count
+ }.cellForRow { indexPath in
+ let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
+ cell.textLabel!.text = countries[indexPath.row]
+ return cell
+ }.didSelectRowAt {
+ print("\(countries[$0.row]) selected")
+ }.reloadData()
+}
+loadTableView()
+/*:
+ ## Arrays
+ These operations are common. Usually, they involve populating the `UITableView`
+ with the values from an array. `Closures` framework gives you a convenient
+ way to pass your array to the table view, so that it can perform the boiler
+ plate operations for you, especially the ones that are required to make the table
+ view perform at a basic level.
+ */
+/*:
+ Let's setup our segmented control to load the table view with different options.
+ When binding to an Array it will show the same countries, but reversed, so
+ you can visually see the change after tapping the segmented control.
+ */
+let segmentedControl = viewController.segmentedControl!
+segmentedControl.onChange {
+ switch $0 {
+ case 1:
+ loadTableView(countries: Array(countries.reversed()))
+ case 2:
+ loadTableView(countries: countries.sectionedByFirstLetter)
+ default:
+ loadTableView()
+ }
+}
+/*:
+ * Important:
+ Please remember that Swift `Array`s are value types. This means that they
+ are copied when mutated. When the values or sequence of your array changes, you will
+ need to call `addElements` again, just before you call reloadData().
+ */
+func loadTableView(countries: [String]) {
+ tableView
+ .addElements(countries, cell: UITableViewCell.self) { country, cell, index in
+ cell.textLabel!.text = country
+ }.reloadData()
+}
+/*:
+ ### Multiple Sections
+ * * * *
+ And finally, you can just as easily have a grouped table view, by binding a
+ 2-dimensional array. Before calling this method, we grouped the countries by their
+ first letter.
+ */
+func loadTableView(countries: [[String]]) {
+ tableView.addSections(
+ countries,
+ cell: UITableViewCell.self,
+ headerTitle: { countryArray,index in
+ String(countryArray.first!.first!)},
+ row: { country, cell, index in
+ cell.textLabel!.text = country
+ })
+
+ /**
+ This also allows you to override any default behavior so
+ you aren't overly committed, even though you're initially binding everything
+ to the `Array`.
+ */
+ tableView
+ .estimatedHeightForHeaderInSection { _ in
+ 30
+ }.heightForHeaderInSection { _ in
+ 30
+ }.reloadData()
+}
+//: * * * *
+//: [Click here to explore using **UICollectionView** closures](@next)
+//: * * * *
diff --git a/Xcode/Playground/ClosuresDemo.playground/Pages/UITableView.xcplaygroundpage/Resources/TableViewDemoViewController.xib b/Xcode/Playground/ClosuresDemo.playground/Pages/UITableView.xcplaygroundpage/Resources/TableViewDemoViewController.xib
new file mode 100644
index 0000000..4687fcd
--- /dev/null
+++ b/Xcode/Playground/ClosuresDemo.playground/Pages/UITableView.xcplaygroundpage/Resources/TableViewDemoViewController.xib
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Xcode/Playground/ClosuresDemo.playground/Pages/UITableView.xcplaygroundpage/Sources/Setup.swift b/Xcode/Playground/ClosuresDemo.playground/Pages/UITableView.xcplaygroundpage/Sources/Setup.swift
new file mode 100644
index 0000000..e3af5f2
--- /dev/null
+++ b/Xcode/Playground/ClosuresDemo.playground/Pages/UITableView.xcplaygroundpage/Sources/Setup.swift
@@ -0,0 +1,10 @@
+import UIKit
+import PlaygroundSupport
+
+public class TableViewDemoViewController: BaseViewController {
+ @IBOutlet public var tableView: UITableView!
+ @IBOutlet public var segmentedControl: UISegmentedControl!
+}
+
+public let viewController = TableViewDemoViewController(nibName: "TableViewDemoViewController", bundle: nil)
+public let view = viewController.view!
diff --git a/Xcode/Playground/ClosuresDemo.playground/Sources/Setup.swift b/Xcode/Playground/ClosuresDemo.playground/Sources/Setup.swift
new file mode 100644
index 0000000..3d4e280
--- /dev/null
+++ b/Xcode/Playground/ClosuresDemo.playground/Sources/Setup.swift
@@ -0,0 +1,41 @@
+import UIKit
+import PlaygroundSupport
+
+public protocol PrintableController {
+ var printableTextView: UITextView? {get}
+ func print(text: String)
+}
+
+extension PrintableController {
+ public func print(text: String) {
+ guard let textView = printableTextView else {
+ return
+ }
+ textView.text = textView.text + "\n" + text
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
+ guard let location = textView.text?.count,
+ location > 0 else {
+ return
+ }
+ textView.scrollRangeToVisible(NSMakeRange(location, 1))
+ }
+ }
+}
+
+open class BaseViewController: UIViewController {}
+
+public func getAllCountries() -> [String] {
+ let locale = Locale(identifier:"en_US")
+ return Locale.isoRegionCodes.flatMap {
+ locale.localizedString(forRegionCode: $0)!
+ }.sorted()
+}
+
+public extension Array where Element == String {
+ var sectionedByFirstLetter: [[String]] {
+ let sections = Dictionary(grouping: self) { $0.first! }
+ return sections.keys.sorted().map {
+ sections[$0]!
+ }
+ }
+}
diff --git a/Xcode/Playground/ClosuresDemo.playground/contents.xcplayground b/Xcode/Playground/ClosuresDemo.playground/contents.xcplayground
new file mode 100644
index 0000000..10242dd
--- /dev/null
+++ b/Xcode/Playground/ClosuresDemo.playground/contents.xcplayground
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/docsets/Closures.docset/Contents/Resources/Documents/index.html b/docs/docsets/Closures.docset/Contents/Resources/Documents/index.html
index 46270b7..dfae9f0 100644
--- a/docs/docsets/Closures.docset/Contents/Resources/Documents/index.html
+++ b/docs/docsets/Closures.docset/Contents/Resources/Documents/index.html
@@ -258,14 +258,14 @@
To play with the Playground demo, open the Closures workspace (Closures.xcworkspace file), build the Closuresbeta framework target, then click on the Closures Demo playground. Be sure to show the Assistant Editor and Live View as shown below:
-
Class Reference Documentation
+
Class Reference Documentation
The Reference Documentation has all of the detailed usage information including all the public methods, parameters, and convenience initializers.
Download or clone the project files found in the master branch. Drag and drop
-all .swift files located in the 'Closures’ subdirectory into your Xcode project. Check the option Copy items
+all .swift files located in the 'Closures/Source’ subdirectory into your Xcode project. Check the option Copy items
if needed.