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 @@

There are several ways to learn more about the Closuresbeta 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

+

   Playground

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:

Playgrounds


-

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.

@@ -276,8 +276,6 @@

CocoaPods, add the following to your Podfile:

- -
pod 'Closures'
 

Carthage

@@ -288,7 +286,7 @@

Carthage

Manual

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.


diff --git a/docs/docsets/Closures.tgz b/docs/docsets/Closures.tgz index d247ac621b890df17531743eea12042f5dda9452..6be5d1ff55c7f9feef91dec68133d8fae6fa34b3 100644 GIT binary patch delta 110600 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

There are several ways to learn more about the Closuresbeta 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

+

   Playground

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:

Playgrounds


-

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.

@@ -276,8 +276,6 @@

CocoaPods, add the following to your Podfile:

- -
pod 'Closures'
 

Carthage

@@ -288,7 +286,7 @@

Carthage

Manual

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.


diff --git a/docs/undocumented.json b/docs/undocumented.json index 67f5848..71a135d 100644 --- a/docs/undocumented.json +++ b/docs/undocumented.json @@ -1,26 +1,26 @@ { "warnings": [ { - "file": "/Users/vhesener/Documents/xcode/Closures/Xcode/Closures/Core.swift", - "line": 29, + "file": "/Users/vhesener/Documents/xcode/Github/Closures/Xcode/Closures/Source/Core.swift", + "line": 26, "symbol": "DelegatorProtocol", "symbol_kind": "source.lang.swift.decl.protocol", "warning": "undocumented" }, { - "file": "/Users/vhesener/Documents/xcode/Closures/Xcode/Closures/KVO.swift", + "file": "/Users/vhesener/Documents/xcode/Github/Closures/Xcode/Closures/Source/KVO.swift", "line": 24, "symbol": "NSObject.selfDeinits", "symbol_kind": "source.lang.swift.decl.var.instance", "warning": "undocumented" }, { - "file": "/Users/vhesener/Documents/xcode/Closures/Xcode/Closures/UIImagePickerController.swift", + "file": "/Users/vhesener/Documents/xcode/Github/Closures/Xcode/Closures/Source/UIImagePickerController.swift", "line": 122, "symbol": "UIImagePickerController.dismissFromPresenting(_:)", "symbol_kind": "source.lang.swift.decl.function.method.static", "warning": "undocumented" } ], - "source_directory": "/Users/vhesener/Documents/xcode/Closures/Xcode" + "source_directory": "/Users/vhesener/Documents/xcode/Github/Closures/Xcode" } \ No newline at end of file

v9iwh4K2pHGIg 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 K7zk%9i2nsf_CUP= diff --git a/docs/index.html b/docs/index.html index 46270b7..dfae9f0 100644 --- a/docs/index.html +++ b/docs/index.html @@ -258,14 +258,14 @@