├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── Package.swift ├── README.md ├── Supporting ├── cocoapods │ ├── .swift-version │ └── Closures.podspec.json └── jazzy │ ├── abstracts │ ├── Controls.md │ ├── Gesture Recognizers.md │ ├── KVO.md │ ├── UIBarButtonItem.md │ ├── UICollectionView.md │ ├── UIImagePickerController.md │ ├── UIPickerView.md │ └── UITableView.md │ ├── generate_docs.sh │ └── jazzy.yml ├── Xcode ├── Closures.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ ├── IDETemplateMacros.plist │ │ └── xcschemes │ │ └── Closures.xcscheme ├── Closures.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── Closures │ ├── Closures.h │ ├── Info.plist │ └── Source │ │ ├── Core.swift │ │ ├── KVO.swift │ │ ├── UIBarButtonItem.swift │ │ ├── UICollectionView.swift │ │ ├── UIControl.swift │ │ ├── UIGestureRecognizer.swift │ │ ├── UIImagePickerController.swift │ │ ├── UIPickerView.swift │ │ ├── UIScrollView.swift │ │ └── UITableView.swift ├── ClosuresTests │ ├── Info.plist │ ├── KVOTests.swift │ ├── UIBarButtonItemTests.swift │ ├── UICollectionViewTests.swift │ ├── UIControlTests.swift │ ├── UIGestureRecognizerTests.swift │ ├── UIImagePickerControllerTests.swift │ ├── UIPickerViewTests.swift │ └── UITableViewTests.swift └── Playground │ └── ClosuresDemo.playground │ ├── Pages │ ├── KVO.xcplaygroundpage │ │ └── Contents.swift │ ├── UIBarButtonItem.xcplaygroundpage │ │ ├── Contents.swift │ │ ├── Resources │ │ │ └── BarButtonDemoViewController.xib │ │ └── Sources │ │ │ └── Setup.swift │ ├── UICollectionView.xcplaygroundpage │ │ ├── Contents.swift │ │ ├── Resources │ │ │ └── CollectionViewDemoViewController.xib │ │ └── Sources │ │ │ └── Setup.swift │ ├── UIControls.xcplaygroundpage │ │ ├── Contents.swift │ │ ├── Resources │ │ │ └── ControlsDemoViewController.xib │ │ └── Sources │ │ │ └── Setup.swift │ ├── UIGestureRecognizer.xcplaygroundpage │ │ ├── Contents.swift │ │ ├── Resources │ │ │ └── GesturesDemoViewController.xib │ │ └── Sources │ │ │ └── Setup.swift │ ├── UIImagePickerController.xcplaygroundpage │ │ └── Contents.swift │ ├── UIPickerView.xcplaygroundpage │ │ ├── Contents.swift │ │ ├── Resources │ │ │ └── PickerDemoViewController.xib │ │ └── Sources │ │ │ └── Setup.swift │ └── UITableView.xcplaygroundpage │ │ ├── Contents.swift │ │ ├── Resources │ │ └── TableViewDemoViewController.xib │ │ └── Sources │ │ └── Setup.swift │ ├── Setup.o │ ├── Sources │ └── Setup.swift │ └── contents.xcplayground └── docs ├── Controllers.html ├── Controls.html ├── Extensions ├── UIBarButtonItem.html ├── UIButton.html ├── UICollectionView.html ├── UIControl.html ├── UIDatePicker.html ├── UIGestureRecognizer.html ├── UIImagePickerController.html ├── UIImagePickerController │ ├── MediaFilter.html │ └── Result.html ├── UILongPressGestureRecognizer.html ├── UIPageControl.html ├── UIPanGestureRecognizer.html ├── UIPickerView.html ├── UIPinchGestureRecognizer.html ├── UIRotationGestureRecognizer.html ├── UIScreenEdgePanGestureRecognizer.html ├── UIScrollView.html ├── UISegmentedControl.html ├── UISlider.html ├── UIStepper.html ├── UISwipeGestureRecognizer.html ├── UISwitch.html ├── UITableView.html ├── UITapGestureRecognizer.html ├── UITextField.html ├── UIView.html └── _5FKeyValueCodingAndObserving.html ├── Gesture Recognizers.html ├── KVO.html ├── Scrolling Views.html ├── _config.yml ├── css ├── highlight.css └── jazzy.css ├── docsets ├── Closures.docset │ └── Contents │ │ ├── Info.plist │ │ └── Resources │ │ ├── Documents │ │ ├── Controllers.html │ │ ├── Controls.html │ │ ├── Extensions │ │ │ ├── UIBarButtonItem.html │ │ │ ├── UIButton.html │ │ │ ├── UICollectionView.html │ │ │ ├── UIControl.html │ │ │ ├── UIDatePicker.html │ │ │ ├── UIGestureRecognizer.html │ │ │ ├── UIImagePickerController.html │ │ │ ├── UIImagePickerController │ │ │ │ ├── MediaFilter.html │ │ │ │ └── Result.html │ │ │ ├── UILongPressGestureRecognizer.html │ │ │ ├── UIPageControl.html │ │ │ ├── UIPanGestureRecognizer.html │ │ │ ├── UIPickerView.html │ │ │ ├── UIPinchGestureRecognizer.html │ │ │ ├── UIRotationGestureRecognizer.html │ │ │ ├── UIScreenEdgePanGestureRecognizer.html │ │ │ ├── UIScrollView.html │ │ │ ├── UISegmentedControl.html │ │ │ ├── UISlider.html │ │ │ ├── UIStepper.html │ │ │ ├── UISwipeGestureRecognizer.html │ │ │ ├── UISwitch.html │ │ │ ├── UITableView.html │ │ │ ├── UITapGestureRecognizer.html │ │ │ ├── UITextField.html │ │ │ ├── UIView.html │ │ │ └── _5FKeyValueCodingAndObserving.html │ │ ├── Gesture Recognizers.html │ │ ├── KVO.html │ │ ├── Scrolling Views.html │ │ ├── css │ │ │ ├── highlight.css │ │ │ └── jazzy.css │ │ ├── img │ │ │ ├── carat.png │ │ │ ├── dash.png │ │ │ └── gh.png │ │ ├── index.html │ │ ├── js │ │ │ ├── jazzy.js │ │ │ └── jquery.min.js │ │ └── search.json │ │ └── docSet.dsidx └── Closures.tgz ├── img ├── carat.png ├── dash.png └── gh.png ├── index.html ├── js ├── jazzy.js └── jquery.min.js └── search.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xcuserstate 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | # Packages/ 38 | .build/ 39 | 40 | # CocoaPods 41 | # 42 | # We recommend against adding the Pods directory to your .gitignore. However 43 | # you should judge for yourself, the pros and cons are mentioned at: 44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 45 | # 46 | # Pods/ 47 | 48 | # Carthage 49 | # 50 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 51 | # Carthage/Checkouts 52 | 53 | Carthage/Build 54 | 55 | # fastlane 56 | # 57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 58 | # screenshots whenever they are needed. 59 | # For more information about the recommended setup visit: 60 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 61 | 62 | fastlane/report.xml 63 | fastlane/Preview.html 64 | fastlane/screenshots 65 | fastlane/test_output 66 | 67 | # Jazzy 68 | *undocumented.json 69 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | The following rules generally apply for PRs and code changes: 2 | 3 | **Submit Pull Requests to the `develop` branch** 4 | 5 | This is where all experimental and other beta-ish features will go. 6 | `develop` will collect many new features until a release is planned. At a point where a stable release is ready, 7 | `develop` branch will then be merged into `master` and a release tag will be generated for the general public. 8 | 9 | **Markdown documentation and header-level comment changes need to run Jazzy** 10 | 11 | If making a change to the documentation or changes inside of method/property quick look comments, 12 | [Jazzy](https://github.com/realm/jazzy) needs to be run. Please install Jazzy and run the following 13 | Terminal command from the Supporting/jazzy directory: 14 | 15 | `./generate_docs.sh` 16 | 17 | **Blend with existing patterns** 18 | 19 | If, for instance, you are contributing by adding another 20 | [Closure API](https://github.com/vhesener/Closures/issues?q=is%3Aissue+is%3Aopen+label%3A%22Closure+API+Request%22) 21 | and that API has a precedent for implementation, it is best to mimic the existing precedent's pattern. 22 | If however, you think both the new API and it's counterparts could use improvements, let's definitely 23 | discuss how to update all of the existing APIs as well. 24 | 25 | Let's take a simple example of adding a new API for a delegate protocol. The following is almost universal: 26 | 27 | - [x] Use the Delegate/Delegator wrapper mechanism used by other delegate APIs 28 | - [x] Unit tests to make sure all delegate methods are covered 29 | - [x] Documentation on any public initializers, methods, or properties 30 | - [x] Playground example showing how to use it, along with explanations 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2017 Vincent Hesener 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 5 | associated documentation files (the "Software"), to deal in the Software without restriction, 6 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 7 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or 11 | substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 14 | NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 15 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 16 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "Closures", 7 | products: [ 8 | .library(name: "Closures", targets: ["Closures"]) 9 | ], 10 | dependencies: [], 11 | targets: [ 12 | .target(name: "Closures", path: "Xcode/Closures") 13 | ] 14 | ) 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Closures logo](https://raw.githubusercontent.com/vhesener/Closures/assets/assets/logo3.1.png) 2 | 3 | [![Language](https://img.shields.io/badge/Swift-5.1-blue.svg?style=plastic&colorB=68B7EB)]() 4 | [![License](https://img.shields.io/github/license/vhesener/Closures.svg?style=plastic&colorB=68B7EB)]() 5 | [![Release](https://img.shields.io/github/release/vhesener/Closures.svg?style=plastic&colorB=68B7EB)]() 6 | 7 | `Closures` 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. 8 | 9 | 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. 10 | 11 | 12 | *** 13 | ## [Usage Overview](#usage-overview) 14 | 15 | ### **Convenient Closures** 16 | 17 | Some days, you just feel like dealing with [UIControl](https://vhesener.github.io/Closures/Controls.html)'s target-action using a closure instead. 18 | 19 | ```swift 20 | button.onTap { 21 | // UIButton tapped code 22 | } 23 | ``` 24 | 25 | ```swift 26 | mySwitch.onChange { isOn in 27 | // UISwitch value changed code 28 | } 29 | ``` 30 | 31 | *** 32 | 33 | Adding a [gesture recognizer](https://vhesener.github.io/Closures/Gesture%20Recognizers.html) can be compacted into one method. 34 | 35 | ```swift 36 | view.addPanGesture() { pan in 37 | // UIPanGesutreRecognizer recognized code 38 | } 39 | ``` 40 | 41 | *** 42 | 43 | Populating views with an array? I gotchu. 44 | 45 | ```swift 46 | tableView.addElements(myArray, cell: MyTableViewCell.self) { element, cell, index in 47 | cell.textLabel!.text = "\(element)" 48 | } 49 | ``` 50 | 51 | ```swift 52 | collectionView.addFlowElements(myArray, cell: MyCustomCollectionViewCell.self) { element, cell, index in 53 | cell.myImageViewProperty.image = element.thumbImage 54 | } 55 | ``` 56 | 57 | ```swift 58 | pickerView.addStrings(myStrings) { title, component, row in 59 | // UIPickerView item selected code 60 | } 61 | ``` 62 | *** 63 | ### **Daisy Chaining** 64 | 65 | 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 concise way. Using [UITextField](https://vhesener.github.io/Closures/Extensions/UITextField.html) as an example, we can organize and visualize all of the `UITextFieldDelegate` behavior. 66 | 67 | ```swift 68 | textField 69 | .didBeginEditing { 70 | // UITextField did begin editing code 71 | }.shouldClear { 72 | true 73 | }.shouldChangeCharacters { range, string in 74 | // some custom character change code 75 | return false 76 | } 77 | ``` 78 | *** 79 | ### **Retain Control** 80 | 81 | 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. 82 | 83 | ```swift 84 | tableView.register(MyTableViewCell.self, forCellReuseIdentifier: "Cell") 85 | tableView 86 | .numberOfRows { _ in 87 | myArray.count 88 | }.cellForRow { indexPath in 89 | let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) 90 | cell.textLabel!.text = myArray[indexPath.row] 91 | return cell 92 | }.didSelectRowAt { indexPath in 93 | // IndexPath selected code 94 | } 95 | ``` 96 | 97 | *** 98 | 99 | You aren't limited to which delegate/dataSource methods you wish to implement. Similarly, you can act on any 100 | [UIControl](https://vhesener.github.io/Closures/Extensions/UIControl.html#/s:So9UIControlC8ClosuresE2onABXDSC0A6EventsV_yAB_So7UIEventCSgtc7handlertF) events. 101 | 102 | ```swift 103 | anyControl.on(.touchDown) { control, event in 104 | // UIControlEvents.touchDown event code 105 | } 106 | ``` 107 | 108 | *** 109 | 110 | 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. 111 | 112 | ```swift 113 | UIImagePickerController(source: .camera, allow: .image) { result, picker in 114 | myImageView.image = result.editedImage 115 | }.present(from: self) 116 | ``` 117 | ```swift 118 | let pickerController = UIImagePickerController() 119 | pickerController.sourceType = .camera 120 | pickerController.mediaTypes = [kUTTypeImage] 121 | pickerController.didFinishPickingMedia { [weak self] info in 122 | myImageView.image = info[UIImagePickerControllerEditedImage] as? UIImage 123 | self?.dismiss(animated: true) 124 | }.didCancel { [weak self] in 125 | self?.dismiss(animated: true) 126 | } 127 | self.present(pickerController, animated: true) 128 | ``` 129 | *** 130 | ## [Dive Deeper](#dive-deeper) 131 | 132 | There are several ways to learn more about the `Closures` 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. 133 | 134 | *** 135 | ###    **Playground** 136 | 137 | To play with the Playground demo, open the `Closures` workspace (Closures.xcworkspace file), build the `Closures` framework target, then click on the `ClosuresDemo` playground, and click on the play button: 138 | 139 | ![Playgrounds](https://raw.githubusercontent.com/vhesener/Closures/assets/assets/playground_general2.gif) 140 | 141 | *** 142 | ###    **Class Reference Documentation** 143 | 144 | The [Reference Documentation](https://vhesener.github.io/Closures) has all of the detailed usage information including all the public methods, parameters, and convenience initializers. 145 | 146 | [![Class Reference Documentation](https://raw.githubusercontent.com/vhesener/Closures/assets/assets/reference_large.png)](https://vhesener.github.io/Closures) 147 | 148 | *** 149 | ## [Installation](#installation) 150 | 151 | ### **Swift Package Manager** 152 | 153 | If using [Swift Package Manager](), in Xcode, go to `File > Swift Packages > Add Package Dependency...` and enter the following URL: 154 | 155 | ``` 156 | https://github.com/vhesener/Closures 157 | ``` 158 | 159 | ### **CocoaPods** 160 | 161 | If using [CocoaPods](https://cocoapods.org/), add the following to your Podfile: 162 | 163 | ```ruby 164 | pod 'Closures' 165 | ``` 166 | 167 | ### **Carthage** 168 | 169 | If using [Carthage](https://github.com/Carthage/Carthage), add the following to your Cartfile: 170 | 171 | ```shell 172 | github "vhesener/Closures" 173 | ``` 174 | 175 | ### **Manual** 176 | 177 | Download or clone the project files found in the [master branch](https://github.com/vhesener/Closures). Drag and drop 178 | all .swift files located in the 'Closures/Source' subdirectory into your Xcode project. Check the option *Copy items 179 | if needed*. 180 | 181 | *** 182 | ## [Background](#background) 183 | 184 | Inspired by [BlocksKit](https://github.com/BlocksKit/BlocksKit), there was a need for a more *Swifty* version 185 | of the same library. The goal of this library was to provide similar usefulness, but with the following 186 | constraints: 187 | 188 | * Use Swift's strong-typed system as much as possible in the API. 189 | * Not use the [Objective-C runtime](https://github.com/BlocksKit/BlocksKit/search?utf8=%E2%9C%93&q=objc_setAssociatedObject&type=). 190 | There are many reasons for this, but mostly because 191 | * It was arbitrarily challenging. 192 | * It was in the spirit of Swift. 193 | * Create a scalable mechanism to easily add additional closure wrappers in the future. 194 | 195 | It is our goal to become irrelevant via [sherlock](http://www.urbandictionary.com/define.php?term=sherlocked). 196 | In addition to not having to support this library anymore, it would actually be flattering 197 | to have been validated by the API folks at Apple. 198 | 199 | *** 200 | ## [Want more?](#want-more) 201 | 202 | If you were hoping to see an API converted using closures and came up empty handed, there's a 203 | chance all can be right. [Simply vote on a feature](https://github.com/vhesener/Closures/labels/Closure%20API%20Request) by adding a 👍 reaction. 204 | 205 | *** 206 | ## [License](#license) 207 | 208 | Closures is provided under the [MIT License](https://github.com/vhesener/Closures/blob/master/LICENSE). 209 | 210 | ```text 211 | The MIT License (MIT) 212 | Copyright (c) 2017 Vincent Hesener 213 | 214 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 215 | associated documentation files (the "Software"), to deal in the Software without restriction, 216 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 217 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 218 | is furnished to do so, subject to the following conditions: 219 | 220 | The above copyright notice and this permission notice shall be included in all copies or 221 | substantial portions of the Software. 222 | 223 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 224 | NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 225 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 226 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 227 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 228 | ``` 229 | -------------------------------------------------------------------------------- /Supporting/cocoapods/.swift-version: -------------------------------------------------------------------------------- 1 | 5.1 2 | -------------------------------------------------------------------------------- /Supporting/cocoapods/Closures.podspec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Closures", 3 | "version": "0.7", 4 | "summary": "Swifty closures for UIKit and Foundation", 5 | "homepage": "https://github.com/vhesener/Closures", 6 | "screenshots": [ 7 | "https://raw.githubusercontent.com/vhesener/Closures/assets/assets/playground_general.gif", 8 | "https://raw.githubusercontent.com/vhesener/Closures/assets/assets/reference_large.png" 9 | ], 10 | "license": "MIT", 11 | "authors": "Vinnie Hesener", 12 | "platforms": { 13 | "ios": "9.0" 14 | }, 15 | "source": { 16 | "git": "https://github.com/vhesener/Closures.git", 17 | "tag": "v0.7" 18 | }, 19 | "source_files": "Xcode/Closures/Source", 20 | "documentation_url": "https://vhesener.github.io/Closures/", 21 | "description": "Closures is an iOS Framework that adds closure handlers to many of the popular\nUIKit and Foundation classes. Although this framework is a substitute for \nsome Cocoa Touch design patterns, such as Delegation and Data Sources, and \nTarget-Action, the authors make no claim regarding which is a better way to \naccomplish the same type of task. Most of the time it is a matter of style, \npreference, or convenience that will determine if any of these closure extensions \nare beneficial.\n\nWhether you’re a functional purist, dislike a particular API, or simply just \nwant to organize your code a little bit, you might enjoy using this library." 22 | } 23 | -------------------------------------------------------------------------------- /Supporting/jazzy/abstracts/Controls.md: -------------------------------------------------------------------------------- 1 | `Closures` framework adds closures to many features of `UIControl` subclasses. 2 | Below are some common actions on some common controls. 3 | 4 | ## UIButton Tap 5 | 6 | A common target-action used is a button tap event. This one is 7 | really simple: 8 | 9 | ```swift 10 | button.onTap { 11 | log("Button tapped") 12 | } 13 | ``` 14 | ## Value Changed Events 15 | Most other `UIControl` types are only interesting for their 16 | value changes. The following are examples of how to observe 17 | value changes on other popular `UIControl`s. 18 | 19 | ### UISlider 20 | 21 | ```swift 22 | slider.onChange { value in 23 | log("slider: \(value)") 24 | } 25 | ``` 26 | 27 | * * * * 28 | ### UISegmentedControl 29 | 30 | ```swift 31 | segmentedControl.onChange { index in 32 | log("segment: \(index)") 33 | } 34 | ``` 35 | 36 | * * * * 37 | ### UIStepper 38 | 39 | ```swift 40 | stepper.onChange { value in 41 | log("stepper: \(value)") 42 | } 43 | ``` 44 | 45 | * * * * 46 | ### UIPageControl 47 | 48 | ```swift 49 | pageControl.onChange { index in 50 | log("page: \(index)") 51 | } 52 | ``` 53 | 54 | * * * * 55 | ### UISwitch 56 | 57 | ```swift 58 | uiSwitch.onChange { isOn in 59 | log("swith is: \(isOn ? "on" : "off")") 60 | } 61 | ``` 62 | 63 | * * * * 64 | ### UIDatePicker 65 | 66 | ```swift 67 | datePicker.onChange { date in 68 | log(date) 69 | } 70 | ``` 71 | 72 | * * * * 73 | ### UITextField 74 | 75 | In addtion to text changes, `UITextField` has some other convenient wrappers 76 | around some commonly needed actions. Below are examples of some events that 77 | can you can observe. Notice the use of daisy chaining in order to keep it 78 | concise and organized. 79 | 80 | ```swift 81 | textfield 82 | .onChange { newText in 83 | log(newText) 84 | }.onEditingBegan { 85 | log("Editing began") 86 | }.onEditingEnded { 87 | log("Editing ended") 88 | }.onReturn { 89 | log("Return key tapped") 90 | } 91 | ``` 92 | 93 | ## Delegation 94 | `UITextField` also employs delegation to help define its behavior. Below 95 | is how you would implement `UITextFieldDelegate` methods using closures. 96 | 97 | ```swift 98 | textfield 99 | .didBeginEditing { 100 | log("Did begin editing delegate") 101 | }.shouldClear { 102 | log("Text clearing") 103 | return true 104 | }.shouldChangeCharacters { range, string in 105 | return true 106 | } 107 | ``` 108 | 109 | Although these convenience closures are not exhaustive, there is a way to 110 | use a closure for any `UIControlEvents`. 111 | 112 | ```swift 113 | button.on(.touchDragInside) { sender, event in 114 | log("Dragging inside button") 115 | } 116 | ``` -------------------------------------------------------------------------------- /Supporting/jazzy/abstracts/Gesture Recognizers.md: -------------------------------------------------------------------------------- 1 | The `UIGestureRecognizer` initializers and delegation wrappers 2 | make it easy to add gesture recognizers to views. It also uses 3 | closures instead of target-action and delegation. 4 | 5 | ## Target-Action Initializers 6 | 7 | The following is how you would add a double tap gesture 8 | recognizer to your view using one of the custom initializers. 9 | As always, we have a closure handler to respond to the gesture's 10 | double tap action. 11 | 12 | ```swift 13 | let doubleTapGesture = UITapGestureRecognizer(tapsRequired: 2) { _ in 14 | log("double tapped") 15 | } 16 | view.addGestureRecognizer(doubleTapGesture) 17 | ``` 18 | 19 | These convenience initializers, delegate closures, and closure recognizers 20 | have been added to all of the existing concrete subclasses, including: 21 | 22 | * `UITapGestureRecognizer` 23 | * `UIPinchGestureRecognizer` 24 | * `UIRotationGestureRecognizer` 25 | * `UISwipeGestureRecognizer` 26 | * `UIPanGestureRecognizer` 27 | * `UIScreenEdgePanGestureRecognizer` 28 | * `UILongPressGestureRecognizer` 29 | 30 | There is also a method for you to configure a custom gesture recognizer 31 | to use closure handlers for recognition: 32 | 33 | ```swift 34 | let myCustomGesture = MyCustomGestureRecognizer() 35 | configure(gesture: myCustomGesture) { _ in 36 | /// a closure that's called when the gesture has ended 37 | } 38 | ``` 39 | 40 | ## Delegation 41 | With convenient extension methods on `UIGestureRecognizer` and `UIView`, 42 | we can daisy chain an entire gesture cycle, including responding 43 | to `UIGestureRecognizerDelegate` methods. 44 | 45 | ```swift 46 | view 47 | .addPanGesture() { pan in 48 | guard pan.state == .ended else { return } 49 | log("view panned") 50 | }.shouldBegin() { 51 | true 52 | }.shouldRecognizeSimultaneouslyWith { 53 | $0 === doubleTapGesture 54 | } 55 | ``` 56 | 57 | 58 | -------------------------------------------------------------------------------- /Supporting/jazzy/abstracts/KVO.md: -------------------------------------------------------------------------------- 1 | The Foundation team at Apple has made great 2 | strides to become more closuresque, and Swift 3 | 4 has truly proved that with KVO updates. 4 | 5 | The API is almost perfect so there isn't too 6 | much to add. There is only one convenience 7 | method to improve some of the boiler plate 8 | code for a typical use case. 9 | 10 | The existing API requires you to save the observer 11 | and store it for the duration of the observation. 12 | Not bad, but with a little polish we can set and 13 | forget. Although it looks like more effort to 14 | provide a conditional remove handler, with the 15 | helper var `selfDeinits` on every NSObject, 16 | we don't have to hold onto distracting observer 17 | variables in our view controllers. 18 | 19 | In this example we'll observe the `text` 20 | property of a UITextField. 21 | 22 | ```swift 23 | let textField = UITextField() 24 | class MyClass: NSObject { 25 | override init() { super.init() 26 | textField.observe(\.text, until: selfDeinits) { obj,change in 27 | print("Observed the change to \"\(obj.text!)\"") 28 | } 29 | } 30 | } 31 | 32 | var myObject: MyClass? = MyClass() 33 | textField.text = "🐒" // this will be observed 34 | myObject = nil 35 | textField.text = "This will NOT be observed" 36 | ``` -------------------------------------------------------------------------------- /Supporting/jazzy/abstracts/UIBarButtonItem.md: -------------------------------------------------------------------------------- 1 | `Closures` framework adds closures for `UIBarButtonItem` tap events, usually 2 | found in a UINavigationBar. 3 | 4 | All initializers that support the target-action pattern now have an equivalent 5 | initialier that contains a `handler` parameter. 6 | 7 | ```swift 8 | navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Left item", style: .plain) { 9 | // left bar button item tapped 10 | } 11 | ``` 12 | 13 | To add the closure handler to an existing `UIBarButtonItem`, simply call the 14 | `onTap(handler:)` method. For instance, if you created your button 15 | in a storyboard, you could call the following in your `viewDidLoad` method. 16 | 17 | ```swift 18 | let myRightBarButton = navigationItem.rightBarButtonItem! 19 | myRightBarButton.onTap { 20 | // right bar button item tapped 21 | } 22 | ``` 23 | 24 | -------------------------------------------------------------------------------- /Supporting/jazzy/abstracts/UICollectionView.md: -------------------------------------------------------------------------------- 1 | ## Delegate and DataSource 2 | 3 | `UICollectionView` closures make it easy to implement `UICollectionViewDelegate` and 4 | `UICollectionViewDataSource` protocol methods in an organized way. The following 5 | is an example of a simple collection view that displays strings in a basic cell. 6 | 7 | ```swift 8 | func loadCollectionView() { 9 | collectionView.register(MyCustomCollectionViewCell.self, forCellWithReuseIdentifier: "Cell") 10 | 11 | collectionView 12 | .numberOfItemsInSection { _ in 13 | countries.count 14 | }.cellForItemAt { indexPath in 15 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! MyCustomCollectionViewCell 16 | cell.textLabel.text = countries[indexPath.item] 17 | return cell 18 | }.didSelectItemAt { 19 | print("\(countries[$0.item]) selected") 20 | }.reloadData() 21 | } 22 | ``` 23 | 24 | ## Arrays 25 | These operations are common. Usually, they involve populating the `UICollectionView` 26 | with the values from an array. `Closures` framework gives you a convenient 27 | way to pass your array to the collection view, so that it can perform the boilerplate 28 | operations for you, especially the ones that are required to make the collection 29 | view perform at a basic level. 30 | 31 | * Important: 32 | Please remember that Swift `Array`s are value types. This means that they 33 | are copied when mutated. When the values or sequence of your array changes, you will 34 | need to call `addFlowElements` again, just before you call reloadData(). 35 | 36 | ```swift 37 | func loadCollectionView(countries: [String]) { 38 | collectionView 39 | .addFlowElements(countries, cell: MyCustomCollectionViewCell.self) { country, cell, index in 40 | cell.textLabel.text = country 41 | }.reloadData() 42 | 43 | /** 44 | This also allows you to override any default behavior so 45 | you aren't overly committed, even though you're initially binding everything 46 | to the `Array`. 47 | */ 48 | collectionView.didSelectItemAt { 49 | print("\(countries[$0.item]) selected from array") 50 | } 51 | } 52 | ``` 53 | -------------------------------------------------------------------------------- /Supporting/jazzy/abstracts/UIImagePickerController.md: -------------------------------------------------------------------------------- 1 | The following is an example of using closures to 2 | select media from a `UIImagePickerController`. 3 | A simple picker is created which defaults to allowing 4 | images to be selected from the photos library. This 5 | initializer will call the handler when an image is selected. 6 | When preseting with the `present(from:)` method, 7 | the `UIImagePickerController` will dismiss itself on user cancel 8 | or after the picker has picked its content (after the closure 9 | is called). 10 | 11 | ```swift 12 | UIImagePickerController() { result,picker in 13 | myImageView.image = result.editedImage 14 | }.present(from: self) 15 | ``` 16 | 17 | You can also customize the picker if you need more control, including 18 | setting your own initial values and delegate callbacks. 19 | The following is a verbose example using most of the possible 20 | customization points. 21 | 22 | ```swift 23 | UIImagePickerController( 24 | source: .camera, 25 | allow: [.image, .movie], 26 | cameraOverlay: nil, 27 | showsCameraControls: false) { result,picker in 28 | myImageView.image = result.originalImage 29 | }.didCancel { [weak self] in 30 | // some custom didCancel implementation 31 | self?.dismiss(animated: animation.animate) 32 | }.present(from: self) 33 | ``` -------------------------------------------------------------------------------- /Supporting/jazzy/abstracts/UIPickerView.md: -------------------------------------------------------------------------------- 1 | ## Delegate and DataSource 2 | 3 | `UIPickerView` closures make it easy to implement `UIPickerViewDelegate` and 4 | `UIPickerViewDataSource` protocol methods in an organized way. The following 5 | is an example of a simple collection view that displays strings in each row. 6 | 7 | ```swift 8 | func loadPickerView() { 9 | pickerView 10 | .numberOfRowsInComponent() { _ in 11 | countries.count 12 | }.titleForRow() { row, component in 13 | countries[row] 14 | }.didSelectRow { row, component in 15 | log("\(countries[row]) selected") 16 | }.reloadAllComponents() 17 | } 18 | ``` 19 | 20 | ## Arrays 21 | These operations are common. Usually, they involve populating the `UIPickerView` 22 | with the values from an array. `Closures` framework gives you a convenient 23 | way to pass your collection to the table view, so that it can perform the boilerplate 24 | operations for you, especially the ones that are required to make the picker 25 | view perform at a basic level. 26 | 27 | * Important: 28 | Please remember that Swift `Array`s are value types. This means that they 29 | are copied when mutaded. When the values or sequence of your array changes, you will 30 | need to call one of the `add` methods again, just before you 31 | call reloadData(). 32 | 33 | ```swift 34 | func loadPickerView(countries: [String]) { 35 | let reversed = Array(countries.reversed()) 36 | pickerView 37 | .addStrings(reversed) { country,component,row in 38 | log("\(country) selected from array") 39 | }.reloadAllComponents() 40 | } 41 | ``` 42 | 43 | * Note: 44 | Be sure to note that most of the closure callbacks in these array binding 45 | methods switch the order of the parameters of row and component. Most of the 46 | `UIPickerView` delegate/datasource method parameters pass `row,component`. The 47 | parameters on the `add` methods switch the order and send `component,row` 48 | 49 | ### Multiple Components 50 | And finally, you can just as easily show mutliple components by binding a 51 | 2-dimensional array. When using this method, the outer dimension of the array is the 52 | component (columns) and the inner dimension are the rows in that component. 53 | 54 | ```swift 55 | let anElement = myTwoDArray[component][row] 56 | ``` 57 | 58 | In this example, the more verbose row handler is provided just to show the 59 | different variations. Adding multiple components has a convenient method to 60 | deal with string only arrays also. 61 | 62 | ```swift 63 | func loadPickerView(components: [[String]]) { 64 | pickerView.addComponents( 65 | components, 66 | rowTitle: { country,component,row in 67 | country}, 68 | didSelect: { country,component,row in 69 | log("\(country) selected from 2D Array") 70 | }) 71 | 72 | /** 73 | This also allows you to override any default behavior so 74 | you aren't overly committed, even though you're initially binding everything 75 | to the `Array`. 76 | */ 77 | pickerView.widthForComponent { component in 78 | component == 0 ? 200 : 100 79 | }.reloadAllComponents() 80 | } 81 | ``` 82 | -------------------------------------------------------------------------------- /Supporting/jazzy/abstracts/UITableView.md: -------------------------------------------------------------------------------- 1 | ## Delegate and DataSource 2 | 3 | `UITableView` closures make it easy to implement `UITableViewDelegate` and 4 | `UITableViewDataSource` protocol methods in an organized way. The following 5 | is an example of a simple table view that displays strings in a basic cell. 6 | 7 | ```swift 8 | func loadTableView() { 9 | tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell") 10 | 11 | tableView 12 | .numberOfRows { _ in 13 | countries.count 14 | }.cellForRow { indexPath in 15 | let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) 16 | cell.textLabel!.text = countries[indexPath.row] 17 | return cell 18 | }.didSelectRowAt { 19 | print("\(countries[$0.row]) selected") 20 | }.reloadData() 21 | } 22 | ``` 23 | 24 | ## Arrays 25 | These operations are common. Usually, they involve populating the `UITableView` 26 | with the values from an array. `Closures` framework gives you a convenient 27 | way to pass your array to the table view, so that it can perform the boiler 28 | plate operations for you, especially the ones that are required to make the table 29 | view perform at a basic level. 30 | 31 | * Important: 32 | Please remember that Swift `Array`s are value types. This means that they 33 | are copied when mutated. When the values or sequence of your array changes, you will 34 | need to call `addElements` again, just before you call reloadData(). 35 | 36 | ```swift 37 | func loadTableView(countries: [String]) { 38 | tableView 39 | .addElements(countries, cell: UITableViewCell.self) { country, cell, index in 40 | cell.textLabel!.text = country 41 | }.reloadData() 42 | } 43 | ``` 44 | 45 | ### Multiple Sections 46 | * * * * 47 | And finally, you can just as easily have a grouped table view, by binding a 48 | 2-dimensional array. Before calling this method, we grouped the countries by their 49 | first letter. 50 | 51 | ```swift 52 | func loadTableView(countries: [[String]]) { 53 | tableView.addSections( 54 | countries, 55 | cell: UITableViewCell.self, 56 | headerTitle: { countryArray,index in 57 | String(countryArray.first!.first!)}, 58 | row: { country, cell, index in 59 | cell.textLabel!.text = country 60 | }) 61 | 62 | /** 63 | This also allows you to override any default behavior so 64 | you aren't overly committed, even though you're initially binding everything 65 | to the `Array`. 66 | */ 67 | tableView 68 | .estimatedHeightForHeaderInSection { _ in 69 | 30 70 | }.heightForHeaderInSection { _ in 71 | 30 72 | }.reloadData() 73 | } 74 | ``` 75 | -------------------------------------------------------------------------------- /Supporting/jazzy/generate_docs.sh: -------------------------------------------------------------------------------- 1 | jazzy --config jazzy.yml; 2 | 3 | ## Have to create this exception in order to have underscores in a class name 4 | ## display in the github documentation generator 5 | echo "include: 6 | - \"_5FKeyValueCodingAndObserving.html\" 7 | " > ../../docs/_config.yml -------------------------------------------------------------------------------- /Supporting/jazzy/jazzy.yml: -------------------------------------------------------------------------------- 1 | clean: true 2 | output: "../../docs/" 3 | min_acl: public 4 | author: Vinnie Hesener 5 | copyright: 2017 Vincent Hesener 6 | author_url: https://git.io/vQ6dA 7 | github_url: https://github.com/vhesener/Closures 8 | sdk: iphone 9 | skip_undocumented: true 10 | hide_documentation_coverage: true 11 | use_safe_filenames: true 12 | readme: "../../README.md" 13 | module: Closures 14 | abstract: "abstracts/*.md" 15 | source_directory: "../../Xcode" 16 | custom_categories: 17 | - name: Controls 18 | children: 19 | - UIButton 20 | - UISwitch 21 | - UITextField 22 | - UIDatePicker 23 | - UIPageControl 24 | - UISegmentedControl 25 | - UIStepper 26 | - UISlider 27 | - UIControl 28 | - UIBarButtonItem 29 | - name: Scrolling Views 30 | children: 31 | - UITableView 32 | - UICollectionView 33 | - UIPickerView 34 | - UIScrollView 35 | - name: Gesture Recognizers 36 | children: 37 | - UIGestureRecognizer 38 | - UITapGestureRecognizer 39 | - UILongPressGestureRecognizer 40 | - UIPinchGestureRecognizer 41 | - UIPanGestureRecognizer 42 | - UISwipeGestureRecognizer 43 | - UIRotationGestureRecognizer 44 | - UIScreenEdgePanGestureRecognizer 45 | - UIView 46 | - "configure(gesture:handler:)" 47 | - name: Controllers 48 | children: 49 | - UIImagePickerController 50 | - name: KVO 51 | children: 52 | - _KeyValueCodingAndObserving 53 | -------------------------------------------------------------------------------- /Xcode/Closures.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Xcode/Closures.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Xcode/Closures.xcodeproj/xcshareddata/IDETemplateMacros.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | FILEHEADER 6 | 7 | /** 8 | The MIT License (MIT) 9 | Copyright (c) ___YEAR___ Vincent Hesener 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 12 | associated documentation files (the "Software"), to deal in the Software without restriction, 13 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 14 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 15 | is furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all copies or 18 | substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 21 | NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 23 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | */ 26 | 27 | -------------------------------------------------------------------------------- /Xcode/Closures.xcodeproj/xcshareddata/xcschemes/Closures.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 38 | 39 | 40 | 41 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 64 | 70 | 71 | 72 | 73 | 79 | 80 | 86 | 87 | 88 | 89 | 91 | 92 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /Xcode/Closures.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Xcode/Closures.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Xcode/Closures.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Xcode/Closures/Closures.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | //! Project version number for Closures. 4 | FOUNDATION_EXPORT double ClosuresVersionNumber; 5 | 6 | //! Project version string for Closures. 7 | FOUNDATION_EXPORT const unsigned char ClosuresVersionString[]; 8 | -------------------------------------------------------------------------------- /Xcode/Closures/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 0.1 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Xcode/Closures/Source/Core.swift: -------------------------------------------------------------------------------- 1 | /** 2 | The MIT License (MIT) 3 | Copyright (c) 2017 Vincent Hesener 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 6 | associated documentation files (the "Software"), to deal in the Software without restriction, 7 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 8 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 9 | is furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or 12 | substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 15 | NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 16 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | */ 20 | 21 | import Foundation 22 | 23 | protocol DelegateProtocol: class { 24 | } 25 | 26 | @available(iOS 9.0, *) 27 | public protocol DelegatorProtocol: class { 28 | /** 29 | Clears any delegates/datasources that were assigned by the `Closures` 30 | framework for this object. This cleans up memory as well as sets the 31 | delegate/datasource properties to nil. 32 | */ 33 | @available(iOS 9.0, *) 34 | func clearClosureDelegates() 35 | } 36 | 37 | @available(iOS 9.0, *) 38 | class DelegateWrapper: NSObject { 39 | weak var delegator: Delegator? 40 | let delegate: Delegate 41 | 42 | init(delegator: Delegator, delegate: Delegate) { 43 | self.delegate = delegate 44 | self.delegator = delegator 45 | } 46 | 47 | var tupac: Bool { return delegator == nil } 48 | 49 | public static func wrapper(delegator: Delegator, 50 | delegate: @autoclosure () -> Delegate, 51 | delegates: inout Set>, 52 | bind: (_ delegator: Delegator, _ delegate: Delegate) -> Void) -> DelegateWrapper { 53 | var deadRappers = [DelegateWrapper]() 54 | defer { 55 | delegates.subtract(deadRappers) 56 | } 57 | 58 | if let wrapper = delegates.first(where: { 59 | // lazy, inaccurate cleanup. 60 | if $0.tupac { 61 | deadRappers.append($0) 62 | } 63 | return $0.delegator === delegator 64 | }) { 65 | return wrapper 66 | } 67 | let delegate = delegate() 68 | let wrapper: DelegateWrapper = DelegateWrapper(delegator: delegator, delegate: delegate) 69 | bind(delegator, delegate) 70 | delegates.insert(wrapper) 71 | 72 | return wrapper 73 | } 74 | 75 | public static func remove(delegator: Delegator, from delegates: inout Set>) { 76 | if let wrapper = delegates.first(where: { $0.delegator === delegator }) { 77 | delegates.remove(wrapper) 78 | } 79 | } 80 | 81 | @available(iOS 9.0, *) 82 | public static func update(_ delegator: Delegator, 83 | delegate: @autoclosure () -> Delegate, 84 | delegates: inout Set>, 85 | bind: (_ delegator: Delegator, _ delegate: Delegate) -> Void, 86 | with updateHandler: (_ wrapper: DelegateWrapper) -> Void) { 87 | let wrapper = self.wrapper(delegator: delegator, delegate: delegate(), delegates: &delegates, bind: bind) 88 | updateHandler(wrapper) 89 | bind(delegator, wrapper.delegate) 90 | } 91 | } 92 | 93 | fileprivate class BundleHook {} 94 | extension Bundle { 95 | static let closures = Bundle(for: BundleHook.self) 96 | } 97 | 98 | extension String { 99 | static let namespace = Bundle.closures.bundleIdentifier ?? "" 100 | } 101 | 102 | extension NotificationCenter { 103 | static func selfObserve(name: Notification.Name, 104 | target: T, 105 | closure: @escaping (_ target: T, _ userInfo: [AnyHashable : Any]?) -> Void) where T: AnyObject { 106 | NotificationCenter.closures.selfObserve(name: name, target: target, closure: closure) 107 | } 108 | 109 | func selfObserve(name: Notification.Name, 110 | target: T, 111 | closure: @escaping (_ target: T, _ userInfo: [AnyHashable : Any]?) -> Void) where T: AnyObject { 112 | 113 | // Post a cleanup notification to remove any duplicates 114 | let cleanupKey = "com.vhesener.notificationkey.selfobserved.cleanup" 115 | post(name: name, object: target, userInfo: [cleanupKey: target]) 116 | 117 | var observer: NSObjectProtocol? 118 | observer = addObserver( 119 | forName: name, 120 | // Can't use the object for this parameter. Since the object 121 | // is the one sending the post, it will never clean up. The observer 122 | // will always stay in the notification center and I'm not sure of 123 | // the concequences of that yet. 124 | object: nil, 125 | queue: nil) { [weak target, weak self] in 126 | // Cleanup any notification with this name;target combo 127 | if let cleanupTarget = $0.userInfo?[cleanupKey] as? T { 128 | if cleanupTarget === target, 129 | $0.name == name { 130 | self?.removeObserver(observer!) 131 | } 132 | return 133 | } 134 | // Remove if target is nil (target-action on self is fruitless) 135 | guard let target = target else { 136 | self?.removeObserver(observer!) 137 | observer = nil 138 | return 139 | } 140 | // Defensive check that self is posting and the target 141 | guard let object = $0.object as? T, 142 | object === target else { 143 | return 144 | } 145 | closure(target, $0.userInfo) 146 | } 147 | } 148 | 149 | @discardableResult 150 | static func observeUntil( 151 | _ removeCondition: @escaping (_ object: T?) -> Bool, 152 | object: T, 153 | name: Notification.Name, 154 | closure: @escaping (_ object: T, _ userInfo: [AnyHashable : Any]?) -> Void) -> NSObjectProtocol where T: AnyObject { 155 | return NotificationCenter.closures.observeUntil(removeCondition, object: object, name: name, closure: closure) 156 | } 157 | 158 | @discardableResult 159 | func observeUntil( 160 | _ removeCondition: @escaping (_ object: T?) -> Bool, 161 | object: T, 162 | name: Notification.Name, 163 | closure: @escaping (_ object: T, _ userInfo: [AnyHashable : Any]?) -> Void) -> NSObjectProtocol where T: AnyObject { 164 | var observer: NSObjectProtocol? 165 | observer = addObserver( 166 | forName: name, 167 | object: object, 168 | queue: nil) { [weak object, weak self] in 169 | // Explicit cleanup condition for this observer. 170 | guard !removeCondition(object), 171 | let object = object else { 172 | self?.removeObserver(observer!) 173 | observer = nil 174 | return 175 | } 176 | closure(object, $0.userInfo) 177 | } 178 | return observer! 179 | } 180 | } 181 | 182 | extension NotificationCenter { 183 | static let closures = NotificationCenter() 184 | } 185 | -------------------------------------------------------------------------------- /Xcode/Closures/Source/KVO.swift: -------------------------------------------------------------------------------- 1 | /** 2 | The MIT License (MIT) 3 | Copyright (c) 2017 Vincent Hesener 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 6 | associated documentation files (the "Software"), to deal in the Software without restriction, 7 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 8 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 9 | is furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or 12 | substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 15 | NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 16 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | */ 20 | 21 | import Foundation 22 | 23 | extension NSObject { 24 | public var selfDeinits: (_KeyValueCodingAndObserving) -> Bool { 25 | return { [weak self] _ in 26 | return self == nil 27 | } 28 | } 29 | } 30 | 31 | extension _KeyValueCodingAndObserving { 32 | /** 33 | This convenience method puts only a tiny bit of polish on 34 | Swift's latest closure-based KVO implementation. Although 35 | there aren't many obvious differences between this 36 | method and the one in `Foundation`, there are a few helpers, which 37 | are describe below. 38 | 39 | First, it passes a remove condition, `until`, which is simpy a closure 40 | that gets called to determine whether to remove the observation 41 | closure. Returning true will remove the observer, otherwise, the 42 | observing will continue. `Foundation`'s method 43 | automatically removes observation when the `NSKeyValueObservation` 44 | is released, but this requires you to save it somewhere in your 45 | view controller as a property, thereby cluttering up your view 46 | controller with plumbing-only members. 47 | 48 | Second, this method attempts to slightly improve the clutter. You 49 | do not have to save the observer. `@discardableResult` allows you 50 | to disregard the returned object entirely. 51 | 52 | Finally, a typical pattern is to remove observers when deinit is 53 | called on the object that owns the observed object. 54 | For instance, if you are observing a model object property on your 55 | view controller, you will probably want to stop observing when the 56 | view controller gets released from memory. Because this is a 57 | common pattern, there's a convenient var available on all subclasses 58 | of NSObject named `selfDeinits`. Simply pass this as a parameter 59 | into the `until` paramaeter, and the observation will be removed 60 | when `self` is deallocated. 61 | 62 | * * * * 63 | #### An example of calling this method: 64 | ```swift 65 | <#someObject#>.observe(\.<#some.key.path#>, until: selfDeinits) { obj,change in 66 | <#do something#> 67 | } 68 | ``` 69 | 70 | * parameter keyPath: The keyPath you wish to observe on this object 71 | * parameter options: The observing options 72 | * parameter until: The closure called when this handler should stop 73 | observing. Return true if you want to forcefully stop observing. 74 | * parameter changeHandler: The callback that will be called when 75 | the keyPath change has occurred. 76 | 77 | * returns: The observer object needed to remove observation 78 | */ 79 | @discardableResult 80 | public func observe( 81 | _ keyPath: KeyPath, 82 | options: NSKeyValueObservingOptions = [], 83 | until removeCondition: @escaping (Self) -> Bool, 84 | changeHandler: @escaping (Self, NSKeyValueObservedChange) -> Void) 85 | -> NSKeyValueObservation { 86 | var observer: NSKeyValueObservation? 87 | observer = self.observe(keyPath, options: options) { obj, change in 88 | guard !removeCondition(obj) else { 89 | observer?.invalidate() 90 | observer = nil 91 | return 92 | } 93 | changeHandler(obj, change) 94 | } 95 | return observer! 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Xcode/Closures/Source/UIBarButtonItem.swift: -------------------------------------------------------------------------------- 1 | /** 2 | The MIT License (MIT) 3 | Copyright (c) 2017 Vincent Hesener 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 6 | associated documentation files (the "Software"), to deal in the Software without restriction, 7 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 8 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 9 | is furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or 12 | substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 15 | NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 16 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | */ 20 | 21 | import UIKit 22 | 23 | /// :nodoc: 24 | private let jzyBug = 0 // Prevent the license header from showing up in Jazzy Docs for UICollectionView 25 | 26 | public extension UIBarButtonItem { 27 | /** 28 | A convenience initializer for a UIBarButtonItem so that the tap event can 29 | be handled with a closure. This is equivalent of using the init(image:style:target:action:) 30 | initializer. 31 | 32 | * parameter image: The image to use for the button 33 | * parameter style: The `UIBarButtonItemStyle` of the button 34 | * parameter handler: The closure that is called when the button is tapped 35 | */ 36 | convenience init(image: UIImage?, style: UIBarButtonItem.Style, handler: @escaping () -> Void) { 37 | self.init(image: image, style: style, target: nil, action: nil) 38 | onTap(handler: handler) 39 | } 40 | 41 | /** 42 | A convenience initializer for a UIBarButtonItem so that the tap event can 43 | be handled with a closure. This is equivalent of using the init(image:landscapeImagePhone:style:target:action:) 44 | initializer. 45 | 46 | * parameter image: The image to use for the button 47 | * parameter landscapeImagePhone: The image to use for the compressed landscape bar item 48 | * parameter style: The `UIBarButtonItemStyle` of the button 49 | * parameter handler: The closure that is called when the button is tapped 50 | */ 51 | @available(iOS 5.0, *) 52 | convenience init(image: UIImage?, landscapeImagePhone: UIImage?, style: UIBarButtonItem.Style, handler: @escaping () -> Void) { 53 | self.init(image: image, landscapeImagePhone: landscapeImagePhone, style: style, target: nil, action: nil) 54 | onTap(handler: handler) 55 | } 56 | 57 | /** 58 | A convenience initializer for a UIBarButtonItem so that the tap event can 59 | be handled with a closure. This is equivalent of using the init(title:style:target:action:) 60 | initializer. 61 | 62 | * parameter title: The text to use for the button 63 | * parameter style: The `UIBarButtonItemStyle` of the button 64 | * parameter handler: The closure that is called when the button is tapped 65 | */ 66 | convenience init(title: String?, style: UIBarButtonItem.Style, handler: @escaping () -> Void) { 67 | self.init(title: title, style: style, target: nil, action: nil) 68 | onTap(handler: handler) 69 | } 70 | 71 | /** 72 | A convenience initializer for a UIBarButtonItem so that the tap event can 73 | be handled with a closure. This is equivalent of using the init(barButtonSystemItem:target:action:) 74 | initializer. 75 | 76 | * parameter barButtonSystemItem: The `UIBarButtonSystemItem` to be used for the button 77 | * parameter handler: The closure that is called when the button is tapped 78 | */ 79 | convenience init(barButtonSystemItem systemItem: UIBarButtonItem.SystemItem, handler: @escaping () -> Void) { 80 | self.init(barButtonSystemItem: systemItem, target: nil, action: nil) 81 | onTap(handler: handler) 82 | } 83 | 84 | /** 85 | This method is a convenience method to add a closure handler to a `UIBarButtonItem`. 86 | Use this method if you are creating a `UIBarButtonItem` using an initializer 87 | other than the convience ones provide, or if the item was created by a 88 | Storyboard or xib. 89 | 90 | * * * * 91 | #### An example that adds a closure handler to an existing `UIBarButtonItem`: 92 | 93 | ```swift 94 | myBarButtonItem.onTap { 95 | // bar button tapped code 96 | } 97 | ``` 98 | 99 | * parameter handler: The closure that will be called when the tap is recognized. 100 | */ 101 | func onTap(handler: @escaping () -> Void) { 102 | target = self 103 | action = #selector(UIBarButtonItem.buttonTapped) 104 | NotificationCenter.selfObserve(name: .barButtonItemTapped, target: self) { button, userInfo in 105 | handler() 106 | } 107 | } 108 | } 109 | 110 | fileprivate extension UIBarButtonItem { 111 | @objc func buttonTapped() { 112 | NotificationCenter.closures.post(name: .barButtonItemTapped, object: self) 113 | } 114 | } 115 | 116 | fileprivate extension Notification.Name { 117 | static let barButtonItemTapped = Notification.Name("UIBarButtonItem.tapped") 118 | } 119 | -------------------------------------------------------------------------------- /Xcode/ClosuresTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Xcode/ClosuresTests/KVOTests.swift: -------------------------------------------------------------------------------- 1 | /** 2 | The MIT License (MIT) 3 | Copyright (c) 2017 Vincent Hesener 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 6 | associated documentation files (the "Software"), to deal in the Software without restriction, 7 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 8 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 9 | is furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or 12 | substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 15 | NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 16 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | */ 20 | 21 | import XCTest 22 | @testable import Closures 23 | 24 | fileprivate var shouldRemove: Bool = false 25 | 26 | class KVOTests: XCTestCase { 27 | fileprivate var object: AnObserved? = AnObserved() 28 | 29 | func testObserving() { 30 | shouldRemove = false 31 | var didObserve = false 32 | object?.observe(\.aValue, until: {_ in shouldRemove}) { _,_ in 33 | didObserve = true 34 | } 35 | object?.aValue = "test1" 36 | XCTAssertTrue(didObserve) 37 | } 38 | 39 | func testCleanup() { 40 | shouldRemove = true 41 | var didObserve = false 42 | object?.observe(\.aValue, until: {_ in shouldRemove}) { _,_ in 43 | didObserve = true 44 | } 45 | object?.aValue = "test2" 46 | XCTAssertFalse(didObserve) 47 | } 48 | 49 | } 50 | 51 | fileprivate class AnObserved: NSObject { 52 | @objc dynamic var aValue: String? 53 | } 54 | -------------------------------------------------------------------------------- /Xcode/ClosuresTests/UIBarButtonItemTests.swift: -------------------------------------------------------------------------------- 1 | /** 2 | The MIT License (MIT) 3 | Copyright (c) 2017 Vincent Hesener 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 6 | associated documentation files (the "Software"), to deal in the Software without restriction, 7 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 8 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 9 | is furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or 12 | substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 15 | NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 16 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | */ 20 | 21 | import XCTest 22 | @testable import Closures 23 | 24 | class UIBarButtonItemTests: XCTestCase { 25 | func testTargetActionInitializers() { 26 | let exp = expectation(description: "Not all initializers handled") 27 | exp.expectedFulfillmentCount = 4 28 | let handler = { exp.fulfill() } 29 | let buttons = [ 30 | UIBarButtonItem(image: nil, style: .plain, handler: handler), 31 | UIBarButtonItem(image: nil, landscapeImagePhone: nil, style: .plain, handler: handler), 32 | UIBarButtonItem(title: nil, style: .plain, handler: handler), 33 | UIBarButtonItem(barButtonSystemItem: .action, handler: handler)] 34 | 35 | buttons.forEach { 36 | _ = $0.target!.perform($0.action) 37 | } 38 | waitForExpectations(timeout: 0.2) 39 | } 40 | 41 | func testCleanup() { 42 | let button = UIBarButtonItem(barButtonSystemItem: .action) { 43 | XCTAssert(false, "Closures are being added, not replaced") 44 | } 45 | button.onTap { 46 | } 47 | NotificationCenter.closures.post(name: Notification.Name("UIBarButtonItem.tapped"), object: button) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Xcode/ClosuresTests/UICollectionViewTests.swift: -------------------------------------------------------------------------------- 1 | /** 2 | The MIT License (MIT) 3 | Copyright (c) 2017 Vincent Hesener 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 6 | associated documentation files (the "Software"), to deal in the Software without restriction, 7 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 8 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 9 | is furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or 12 | substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 15 | NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 16 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | */ 20 | 21 | import XCTest 22 | @testable import Closures 23 | 24 | class UICollectionViewTests: XCTestCase { 25 | 26 | func testCollectionViewDelegation() { 27 | let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewLayout()) 28 | let exp = expectation(description: "Not all methods called") 29 | exp.expectedFulfillmentCount = 33 30 | 31 | collectionView.willDisplay { _,_ in 32 | exp.fulfill() 33 | } 34 | collectionView.willDisplaySupplementaryView { _,_ in 35 | exp.fulfill() 36 | } 37 | collectionView.didEndDisplaying { _,_ in 38 | exp.fulfill() 39 | } 40 | collectionView.didEndDisplayingSupplementaryView { _,_ in 41 | exp.fulfill() 42 | } 43 | collectionView.shouldHighlightItemAt { _ in 44 | exp.fulfill() 45 | return true 46 | } 47 | collectionView.didHighlightItemAt { _ in 48 | exp.fulfill() 49 | } 50 | collectionView.didUnhighlightItemAt { _ in 51 | exp.fulfill() 52 | } 53 | collectionView.shouldSelectItemAt { _ in 54 | exp.fulfill() 55 | return true 56 | } 57 | collectionView.shouldDeselectItemAt { _ in 58 | exp.fulfill() 59 | return true 60 | } 61 | collectionView.didSelectItemAt { _ in 62 | exp.fulfill() 63 | } 64 | collectionView.didDeselectItemAt { _ in 65 | exp.fulfill() 66 | } 67 | collectionView.shouldShowMenuForItemAt { _ in 68 | exp.fulfill() 69 | return true 70 | } 71 | collectionView.canPerformAction {_,_,_ in 72 | exp.fulfill() 73 | return true 74 | } 75 | collectionView.performAction { _,_,_ in 76 | exp.fulfill() 77 | } 78 | collectionView.transitionLayoutForOldLayout { 79 | exp.fulfill() 80 | return UICollectionViewTransitionLayout(currentLayout: $0, nextLayout: $1) 81 | } 82 | collectionView.targetContentOffsetForProposedContentOffset { _ in 83 | exp.fulfill() 84 | return .zero 85 | } 86 | collectionView.targetIndexPathForMoveFromItemAt { 87 | exp.fulfill() 88 | return $1 89 | } 90 | collectionView.canFocusItemAt { _ in 91 | exp.fulfill() 92 | return true 93 | } 94 | collectionView.indexPathForPreferredFocusedViewIn { 95 | exp.fulfill() 96 | return nil 97 | } 98 | collectionView.cellForItemAt { _ in 99 | exp.fulfill() 100 | return UICollectionViewCell() 101 | } 102 | collectionView.numberOfItemsInSection { _ in 103 | exp.fulfill() 104 | return 0 105 | } 106 | collectionView.numberOfSectionsIn { 107 | exp.fulfill() 108 | return 0 109 | } 110 | collectionView.viewForSupplementaryElementOfKind { _,_ in 111 | exp.fulfill() 112 | return UICollectionReusableView() 113 | } 114 | collectionView.canMoveItemAt { _ in 115 | exp.fulfill() 116 | return true 117 | } 118 | collectionView.moveItemAt { _,_ in 119 | exp.fulfill() 120 | } 121 | collectionView.indexTitlesFor { 122 | exp.fulfill() 123 | return [] 124 | } 125 | collectionView.indexPathForIndexTitle { _,_ in 126 | exp.fulfill() 127 | return IndexPath() 128 | } 129 | collectionView.sizeForItemAt { _ in 130 | exp.fulfill() 131 | return .zero 132 | } 133 | collectionView.insetForSectionAt { _ in 134 | exp.fulfill() 135 | return .zero 136 | } 137 | collectionView.minimumLineSpacingForSectionAt { _ in 138 | exp.fulfill() 139 | return 0 140 | } 141 | collectionView.minimumInteritemSpacingForSectionAt { _ in 142 | exp.fulfill() 143 | return 0 144 | } 145 | collectionView.referenceSizeForHeaderInSection { _ in 146 | exp.fulfill() 147 | return .zero 148 | } 149 | collectionView.referenceSizeForFooterInSection { _ in 150 | exp.fulfill() 151 | return .zero 152 | } 153 | 154 | XCTAssertNotNil(collectionView.delegate) 155 | XCTAssertNotNil(collectionView.dataSource) 156 | 157 | let delegate = collectionView.delegate as! UICollectionViewDelegateFlowLayout 158 | let datasource = collectionView.dataSource! 159 | let iPath = IndexPath() 160 | 161 | delegate.collectionView!(collectionView, willDisplay: UICollectionViewCell(), forItemAt: iPath) 162 | delegate.collectionView!(collectionView, willDisplaySupplementaryView: UICollectionViewCell(), forElementKind: "", at: iPath) 163 | delegate.collectionView!(collectionView, didEndDisplaying: UICollectionViewCell(), forItemAt: iPath) 164 | delegate.collectionView!(collectionView, didEndDisplayingSupplementaryView: UICollectionViewCell(), forElementOfKind: "", at: iPath) 165 | 166 | _ = delegate.collectionView!(collectionView, shouldHighlightItemAt: iPath) 167 | delegate.collectionView!(collectionView, didHighlightItemAt: iPath) 168 | delegate.collectionView!(collectionView, didUnhighlightItemAt: iPath) 169 | _ = delegate.collectionView!(collectionView, shouldSelectItemAt: iPath) 170 | _ = delegate.collectionView!(collectionView, shouldDeselectItemAt: iPath) 171 | delegate.collectionView!(collectionView, didSelectItemAt: iPath) 172 | delegate.collectionView!(collectionView, didDeselectItemAt: iPath) 173 | if #available(iOS 13, *) { 174 | exp.expectedFulfillmentCount -= 3 175 | } else { 176 | _ = delegate.collectionView!(collectionView, shouldShowMenuForItemAt: iPath) 177 | _ = delegate.collectionView!(collectionView, canPerformAction: #selector(UICollectionViewTests.setUp), forItemAt: iPath, withSender: nil) 178 | delegate.collectionView!(collectionView, performAction: #selector(UICollectionViewTests.setUp), forItemAt: iPath, withSender: nil) 179 | } 180 | _ = delegate.collectionView!(collectionView, transitionLayoutForOldLayout: collectionView.collectionViewLayout, newLayout: collectionView.collectionViewLayout) 181 | _ = delegate.collectionView!(collectionView, targetContentOffsetForProposedContentOffset: .zero) 182 | _ = delegate.collectionView!(collectionView, targetIndexPathForMoveFromItemAt: iPath, toProposedIndexPath: iPath) 183 | _ = delegate.collectionView!(collectionView, canFocusItemAt: iPath) 184 | _ = delegate.indexPathForPreferredFocusedView!(in: collectionView) 185 | _ = datasource.collectionView(collectionView, cellForItemAt: iPath) 186 | _ = datasource.collectionView(collectionView, numberOfItemsInSection: 0) 187 | _ = datasource.numberOfSections!(in: collectionView) 188 | _ = datasource.collectionView!(collectionView, viewForSupplementaryElementOfKind: "", at: iPath) 189 | _ = datasource.collectionView!(collectionView, canMoveItemAt: iPath) 190 | _ = datasource.collectionView!(collectionView, moveItemAt: iPath, to: iPath) 191 | _ = datasource.indexTitles!(for: collectionView) 192 | _ = datasource.collectionView!(collectionView, indexPathForIndexTitle: "", at: 0) 193 | _ = delegate.collectionView!(collectionView, layout: collectionView.collectionViewLayout, sizeForItemAt: iPath) 194 | _ = delegate.collectionView!(collectionView, layout: collectionView.collectionViewLayout, insetForSectionAt: 0) 195 | _ = delegate.collectionView!(collectionView, layout: collectionView.collectionViewLayout, minimumLineSpacingForSectionAt: 0) 196 | _ = delegate.collectionView!(collectionView, layout: collectionView.collectionViewLayout, minimumInteritemSpacingForSectionAt: 0) 197 | _ = delegate.collectionView!(collectionView, layout: collectionView.collectionViewLayout, referenceSizeForHeaderInSection: 0) 198 | _ = delegate.collectionView!(collectionView, layout: collectionView.collectionViewLayout, referenceSizeForFooterInSection: 0) 199 | 200 | waitForExpectations(timeout: 0.2) 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /Xcode/ClosuresTests/UIControlTests.swift: -------------------------------------------------------------------------------- 1 | /** 2 | The MIT License (MIT) 3 | Copyright (c) 2017 Vincent Hesener 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 6 | associated documentation files (the "Software"), to deal in the Software without restriction, 7 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 8 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 9 | is furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or 12 | substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 15 | NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 16 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | */ 20 | 21 | import XCTest 22 | @testable import Closures 23 | 24 | class UIControlTests: XCTestCase { 25 | let button1 = UIButton(type: .custom) 26 | let button2 = UIButton(type: .custom) 27 | let textField = UITextField() 28 | let stepper = UIStepper() 29 | 30 | override func setUp() { 31 | super.setUp() 32 | } 33 | 34 | func testButtons() { 35 | var tap1Fired = false 36 | var tap2Fired = false 37 | button1.onTap { 38 | tap1Fired = true 39 | } 40 | button2.onTap { 41 | tap2Fired = true 42 | } 43 | button1.touchUpInside(sender: button1, event: nil) 44 | XCTAssert(tap1Fired) 45 | XCTAssertFalse(tap2Fired) 46 | } 47 | 48 | func testCleanup() { 49 | let description = NotificationCenter.closures.debugDescription 50 | var button3: UIButton? = UIButton(type: .custom) 51 | button3?.onTap { 52 | 53 | } 54 | XCTAssertNotEqual(description, NotificationCenter.closures.debugDescription) 55 | button3?.touchUpInside(sender: button3!, event: nil) 56 | button3 = nil 57 | button1.touchUpInside(sender: button1, event: nil) 58 | XCTAssertEqual(description, NotificationCenter.closures.debugDescription) 59 | } 60 | 61 | func testTextFields() { 62 | textField.text = "old text" 63 | let newText = "new text" 64 | var textChangeFired = false 65 | textField.onChange { text in 66 | textChangeFired = true 67 | XCTAssertEqual(text, newText) 68 | XCTAssertEqual(self.textField.text, newText) 69 | } 70 | textField.text = newText 71 | textField.editingChanged(sender: textField, event: nil) 72 | XCTAssert(textChangeFired) 73 | } 74 | 75 | func testValueChanges() { 76 | stepper.value = 0.0 77 | let newValue = 1.0 78 | var stepperFired = false 79 | stepper.onChange { value in 80 | stepperFired = true 81 | XCTAssertEqual(value, newValue) 82 | XCTAssertEqual(self.stepper.value, newValue) 83 | } 84 | stepper.value = newValue 85 | stepper.valueChanged(sender: stepper, event: nil) 86 | XCTAssert(stepperFired) 87 | } 88 | 89 | func testTextFieldDelegation() { 90 | let textField = UITextField() 91 | let exp = expectation(description: "Not all methods called") 92 | exp.expectedFulfillmentCount = 7 93 | textField.shouldBeginEditing {exp.fulfill(); return true} 94 | .didBeginEditing {exp.fulfill()} 95 | .shouldEndEditing {exp.fulfill(); return true} 96 | .didEndEditing {exp.fulfill()} 97 | .shouldChangeCharacters { (_, _) -> Bool in exp.fulfill(); return true;} 98 | .shouldChangeString { 99 | exp.fulfill() 100 | XCTAssertEqual($0, "old text") 101 | XCTAssertEqual($1, "new text") 102 | return true 103 | } 104 | .shouldClear {exp.fulfill(); return true} 105 | .shouldReturn {exp.fulfill(); return true} 106 | 107 | let delegate = textField.delegate 108 | XCTAssertNotNil(delegate) 109 | 110 | _ = delegate?.textFieldShouldBeginEditing!(textField) 111 | delegate?.textFieldDidBeginEditing!(textField) 112 | _ = delegate?.textFieldShouldEndEditing!(textField) 113 | delegate?.textFieldDidEndEditing!(textField) 114 | textField.text = "old text" 115 | _ = delegate?.textField!(textField, shouldChangeCharactersIn: NSMakeRange(0, 3), replacementString: "new") 116 | _ = delegate?.textFieldShouldClear!(textField) 117 | _ = delegate?.textFieldShouldReturn!(textField) 118 | 119 | waitForExpectations(timeout: 0.2) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /Xcode/ClosuresTests/UIGestureRecognizerTests.swift: -------------------------------------------------------------------------------- 1 | /** 2 | The MIT License (MIT) 3 | Copyright (c) 2017 Vincent Hesener 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 6 | associated documentation files (the "Software"), to deal in the Software without restriction, 7 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 8 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 9 | is furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or 12 | substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 15 | NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 16 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | */ 20 | 21 | import XCTest 22 | @testable import Closures 23 | 24 | class UIGestureRecognizerTests: XCTestCase { 25 | func testDelegateCalls() { 26 | let gesture = UIGestureRecognizer() 27 | let exp = expectation(description: "Not all methods called") 28 | exp.expectedFulfillmentCount = 6 29 | let increaseFulfillment: ()->Bool = { 30 | exp.fulfill() 31 | return true 32 | } 33 | gesture 34 | .shouldBegin(handler: increaseFulfillment) 35 | .shouldRecognizeSimultaneouslyWith(handler: {_ in increaseFulfillment()}) 36 | .shouldRequireFailureOf(handler: {_ in increaseFulfillment()}) 37 | .shouldBeRequiredToFailBy(handler: {_ in increaseFulfillment()}) 38 | .shouldReceiveTouch(handler: {_ in increaseFulfillment()}) 39 | .shouldReceivePress(handler: {_ in increaseFulfillment()}) 40 | 41 | _ = gesture.delegate!.gestureRecognizerShouldBegin!(gesture) 42 | _ = gesture.delegate!.gestureRecognizer!(gesture, shouldRecognizeSimultaneouslyWith: gesture) 43 | _ = gesture.delegate!.gestureRecognizer!(gesture, shouldRequireFailureOf: gesture) 44 | _ = gesture.delegate!.gestureRecognizer!(gesture, shouldBeRequiredToFailBy: gesture) 45 | _ = gesture.delegate!.gestureRecognizer!(gesture, shouldReceive: UITouch()) 46 | _ = gesture.delegate!.gestureRecognizer!(gesture, shouldReceive: UIPress()) 47 | 48 | waitForExpectations(timeout: 0.2) 49 | } 50 | 51 | func testTargetActionCycle() { 52 | let exp = expectation(description: "Action handler never fired") 53 | let gesture = CustomGesture(exp.fulfill()) 54 | gesture.trigger() 55 | waitForExpectations(timeout: 0.2) 56 | } 57 | } 58 | 59 | import UIKit.UIGestureRecognizerSubclass 60 | public class CustomGesture: UIGestureRecognizer { 61 | convenience init(_ handler: @autoclosure @escaping ()->Void) { 62 | self.init() 63 | Closures.configure(gesture: self) { _ in 64 | handler() 65 | } 66 | } 67 | 68 | func trigger() { 69 | let sel = NSSelectorFromString("gestureRecognized") 70 | perform(sel) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Xcode/ClosuresTests/UIImagePickerControllerTests.swift: -------------------------------------------------------------------------------- 1 | /** 2 | The MIT License (MIT) 3 | Copyright (c) 2017 Vincent Hesener 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 6 | associated documentation files (the "Software"), to deal in the Software without restriction, 7 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 8 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 9 | is furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or 12 | substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 15 | NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 16 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | */ 20 | 21 | import XCTest 22 | @testable import Closures 23 | 24 | class UIImagePickerControllerTests: XCTestCase { 25 | func testDelegateCalls() { 26 | let picker = UIImagePickerController() 27 | let exp = expectation(description: "Not all methods called") 28 | exp.expectedFulfillmentCount = 2 29 | let increaseFulfillment: ()->Void = { 30 | exp.fulfill() 31 | } 32 | picker 33 | .didFinishPickingMedia {_ in increaseFulfillment()} 34 | .didCancel {increaseFulfillment()} 35 | 36 | picker.delegate!.imagePickerController!(picker, didFinishPickingMediaWithInfo: [:]) 37 | picker.delegate!.imagePickerControllerDidCancel!(picker) 38 | 39 | waitForExpectations(timeout: 0.2) 40 | } 41 | 42 | func testPublicRemove() { 43 | let picker = UIImagePickerController() 44 | picker.didCancel { 45 | XCTAssert(false) 46 | } 47 | picker.clearClosureDelegates() 48 | picker.delegate?.imagePickerControllerDidCancel!(picker) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Xcode/ClosuresTests/UIPickerViewTests.swift: -------------------------------------------------------------------------------- 1 | /** 2 | The MIT License (MIT) 3 | Copyright (c) 2017 Vincent Hesener 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 6 | associated documentation files (the "Software"), to deal in the Software without restriction, 7 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 8 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software 9 | is furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or 12 | substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 15 | NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 16 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | */ 20 | 21 | import XCTest 22 | @testable import Closures 23 | 24 | class UIPickerViewTests: XCTestCase { 25 | func testDelegateCalls() { 26 | let picker = UIPickerView() 27 | let exp = expectation(description: "Not all methods called") 28 | exp.expectedFulfillmentCount = 8 29 | picker 30 | .rowHeightForComponent() { _ in exp.fulfill(); return 0 } 31 | .widthForComponent() { _ in exp.fulfill(); return 0 } 32 | .titleForRow() { _,_ in exp.fulfill(); return "" } 33 | .attributedTitleForRow() { _,_ in exp.fulfill(); return nil } 34 | .viewForRow() { _,_,_ in exp.fulfill(); return UIView() } 35 | .didSelectRow() { _,_ in exp.fulfill() } 36 | .numberOfComponents() { exp.fulfill(); return 0 } 37 | .numberOfRowsInComponent() { _ in exp.fulfill(); return 0 } 38 | 39 | _ = picker.delegate!.pickerView!(picker, rowHeightForComponent: 0) 40 | _ = picker.delegate!.pickerView!(picker, widthForComponent: 0) 41 | _ = picker.delegate!.pickerView!(picker, titleForRow: 0, forComponent: 0) 42 | _ = picker.delegate!.pickerView!(picker, attributedTitleForRow: 0, forComponent: 0) 43 | _ = picker.delegate!.pickerView!(picker, viewForRow: 0, forComponent: 0, reusing: UIView()) 44 | _ = picker.delegate?.pickerView!(picker, didSelectRow: 0, inComponent: 0) 45 | _ = picker.dataSource?.numberOfComponents(in: picker) 46 | _ = picker.dataSource?.pickerView(picker, numberOfRowsInComponent: 0) 47 | 48 | waitForExpectations(timeout: 0.2) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Xcode/Playground/ClosuresDemo.playground/Pages/KVO.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Closures 3 | //: [Go back](@previous) 4 | /*: 5 | # Key-Value Observing 6 | 7 | The Foundation team at Apple has made great 8 | strides to become more closuresque, and Swift 9 | 4 has truly proved that with KVO updates. 10 | 11 | The API is almost perfect so there isn't too 12 | much to add. There is only one convenience 13 | method to improve some of the boiler plate 14 | code for a typical use case. 15 | 16 | The existing API requires you to save the observer 17 | and store it for the duration of the observation. 18 | Not bad, but with a little polish we can set and 19 | forget. Although it looks like more effort to 20 | provide a conditional remove handler, with the 21 | helper var `selfDeinits` on every NSObject, 22 | we don't have to hold onto distracting observer 23 | variables in our view controllers. 24 | 25 | In this example we'll observe the `text` 26 | property of a UITextField. 27 | */ 28 | let textField = UITextField() 29 | class MyClass: NSObject { 30 | override init() { super.init() 31 | textField.observe(\.text, until: selfDeinits) { obj,change in 32 | print("Observed the change to \"\(obj.text!)\"") 33 | } 34 | } 35 | } 36 | 37 | var myObject: MyClass? = MyClass() 38 | textField.text = "🐒" // this will be observed 39 | myObject = nil 40 | textField.text = "This will NOT be observed" 41 | 42 | //: * * * * 43 | //: [Click here to explore using **UIImagePickerController** closures](@next) 44 | //: * * * * 45 | -------------------------------------------------------------------------------- /Xcode/Playground/ClosuresDemo.playground/Pages/UIBarButtonItem.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | import UIKit;import PlaygroundSupport;PlaygroundPage.current.liveView = navController 2 | import Closures 3 | //: [Go back](@previous) 4 | /*: 5 | # UIBarButtonItem 6 | 7 | `Closures` framework adds closures for `UIBarButtonItem` tap events, usually 8 | found in a UINavigationBar. 9 | 10 | All initializers that support the target-action pattern now have an equivalent 11 | initialier that contains a `handler` parameter. 12 | */ 13 | navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Left item", style: .plain) { 14 | log("left item tapped") 15 | } 16 | /*: 17 | To add the closure handler to an existing `UIBarButtonItem`, simply call the 18 | `onTap(handler:)` method. For instance, if you created your button 19 | in a storyboard, you could call the following in your `viewDidLoad` method. 20 | */ 21 | let myRightBarButton = navigationItem.rightBarButtonItem! 22 | myRightBarButton.onTap { 23 | log("right item tapped") 24 | } 25 | -------------------------------------------------------------------------------- /Xcode/Playground/ClosuresDemo.playground/Pages/UIBarButtonItem.xcplaygroundpage/Resources/BarButtonDemoViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Courier 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Xcode/Playground/ClosuresDemo.playground/Pages/UIBarButtonItem.xcplaygroundpage/Sources/Setup.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import PlaygroundSupport 3 | 4 | public class BarButtonDemoViewController: BaseViewController, PrintableController { 5 | @IBOutlet public var printableTextView: UITextView? 6 | 7 | open override func viewDidLoad() { 8 | super.viewDidLoad() 9 | // supposed to simulate a button item created from a xib/storyboard 10 | navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Right item", style: .plain, target: nil, action: nil) 11 | } 12 | } 13 | 14 | public let viewController = BarButtonDemoViewController(nibName: "BarButtonDemoViewController", bundle: nil) 15 | public let navController = UINavigationController(rootViewController: viewController) 16 | public let navigationItem = viewController.navigationItem 17 | 18 | public func log(_ string: String) { 19 | viewController.print(text: string) 20 | print(string) 21 | } 22 | -------------------------------------------------------------------------------- /Xcode/Playground/ClosuresDemo.playground/Pages/UICollectionView.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | import UIKit;import PlaygroundSupport;PlaygroundPage.current.liveView = viewController 2 | import Closures 3 | //: [Go back](@previous) 4 | /*: 5 | # UICollectionView 6 | ## Delegate and DataSource 7 | 8 | `UICollectionView` closures make it easy to implement `UICollectionViewDelegate` and 9 | `UICollectionViewDataSource` protocol methods in an organized way. The following 10 | is an example of a simple collection view that displays strings in a basic cell. 11 | */ 12 | let collectionView = viewController.collectionView! 13 | let countries = getAllCountries() 14 | 15 | func loadCollectionView() { 16 | collectionView.register(MyCustomCollectionViewCell.self, forCellWithReuseIdentifier: "Cell") 17 | 18 | collectionView 19 | .numberOfItemsInSection { _ in 20 | countries.count 21 | }.cellForItemAt { indexPath in 22 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! MyCustomCollectionViewCell 23 | cell.textLabel.text = countries[indexPath.item] 24 | return cell 25 | }.didSelectItemAt { 26 | print("\(countries[$0.item]) selected") 27 | }.reloadData() 28 | } 29 | loadCollectionView() 30 | /*: 31 | ## Arrays 32 | These operations are common. Usually, they involve populating the `UICollectionView` 33 | with the values from an array. `Closures` framework gives you a convenient 34 | way to pass your array to the collection view, so that it can perform the boilerplate 35 | operations for you, especially the ones that are required to make the collection 36 | view perform at a basic level. 37 | */ 38 | /*: 39 | Let's setup our segmented control to load the collection view with different options. 40 | When binding to an Array it will show the same countries, but reversed, so 41 | you can visually see the change after tapping the segmented control. 42 | */ 43 | let segmentedControl = viewController.segmentedControl! 44 | segmentedControl.onChange { 45 | switch $0 { 46 | case 1: 47 | loadCollectionView(countries: Array(countries.reversed())) 48 | default: 49 | loadCollectionView() 50 | } 51 | } 52 | /*: 53 | * Important: 54 | Please remember that Swift `Array`s are value types. This means that they 55 | are copied when mutated. When the values or sequence of your array changes, you will 56 | need to call `addFlowElements` again, just before you call reloadData(). 57 | */ 58 | func loadCollectionView(countries: [String]) { 59 | collectionView 60 | .addFlowElements(countries, cell: MyCustomCollectionViewCell.self) { country, cell, index in 61 | cell.textLabel.text = country 62 | }.reloadData() 63 | 64 | /** 65 | This also allows you to override any default behavior so 66 | you aren't overly committed, even though you're initially binding everything 67 | to the `Array`. 68 | */ 69 | collectionView.didSelectItemAt { 70 | print("\(countries[$0.item]) selected from array") 71 | } 72 | } 73 | //: * * * * 74 | //: [Click here to explore using **UIGestureRecognizer** closures](@next) 75 | //: * * * * 76 | -------------------------------------------------------------------------------- /Xcode/Playground/ClosuresDemo.playground/Pages/UICollectionView.xcplaygroundpage/Resources/CollectionViewDemoViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /Xcode/Playground/ClosuresDemo.playground/Pages/UICollectionView.xcplaygroundpage/Sources/Setup.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import PlaygroundSupport 3 | 4 | public class CollectionViewDemoViewController: BaseViewController { 5 | @IBOutlet public var collectionView: UICollectionView! 6 | @IBOutlet public var segmentedControl: UISegmentedControl! 7 | } 8 | 9 | public class MyCustomCollectionViewCell: UICollectionViewCell { 10 | required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } 11 | public let textLabel = UILabel() 12 | public override init(frame: CGRect) { 13 | super.init(frame: frame) 14 | textLabel.font = .systemFont(ofSize: 8) 15 | let labelContainer = contentView 16 | textLabel.translatesAutoresizingMaskIntoConstraints = false 17 | labelContainer.addSubview(textLabel) 18 | textLabel.leadingAnchor.constraint(equalTo: labelContainer.leadingAnchor).isActive = true 19 | textLabel.trailingAnchor.constraint(equalTo: labelContainer.trailingAnchor).isActive = true 20 | textLabel.centerYAnchor.constraint(equalTo: labelContainer.centerYAnchor).isActive = true 21 | } 22 | } 23 | 24 | public let viewController = CollectionViewDemoViewController(nibName: "CollectionViewDemoViewController", bundle: nil) 25 | public let view = viewController.view! 26 | -------------------------------------------------------------------------------- /Xcode/Playground/ClosuresDemo.playground/Pages/UIControls.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | import UIKit;import PlaygroundSupport;PlaygroundPage.current.liveView = viewController 2 | import Closures 3 | /*: 4 | # UIControl 5 | 6 | `Closures` framework adds closures to many features of `UIControl` subclasses. 7 | Below are some common actions on some common controls. 8 | 9 | ## UIButton Tap 10 | 11 | A common target-action used is a button tap event. This one is 12 | really simple: 13 | */ 14 | button.onTap { 15 | log("Button tapped") 16 | } 17 | /*: 18 | ## Value Changed Events 19 | Most other `UIControl` types are only interesting for their 20 | value changes. The following are examples of how to observe 21 | value changes on other popular `UIControl`s. 22 | */ 23 | /*: 24 | * * * * 25 | ### UISlider 26 | */ 27 | slider.onChange { value in 28 | log("slider: \(value)") 29 | } 30 | /*: 31 | * * * * 32 | ### UISegmentedControl 33 | */ 34 | segmentedControl.onChange { index in 35 | log("segment: \(index)") 36 | } 37 | /*: 38 | * * * * 39 | ### UIStepper 40 | */ 41 | stepper.onChange { value in 42 | log("stepper: \(value)") 43 | } 44 | /*: 45 | * * * * 46 | ### UIPageControl 47 | */ 48 | pageControl.onChange { index in 49 | log("page: \(index)") 50 | } 51 | /*: 52 | * * * * 53 | ### UISwitch 54 | */ 55 | uiSwitch.onChange { isOn in 56 | log("swith is: \(isOn ? "on" : "off")") 57 | } 58 | /*: 59 | * * * * 60 | ### UIDatePicker 61 | */ 62 | datePicker.onChange { date in 63 | log(date) 64 | } 65 | /*: 66 | * * * * 67 | ### UITextField 68 | 69 | In addtion to text changes, `UITextField` has some other convenient wrappers 70 | around some commonly needed actions. Below are examples of some events that 71 | can you can observe. Notice the use of daisy chaining in order to keep it 72 | concise and organized. 73 | */ 74 | textfield 75 | .onChange { newText in 76 | log(newText) 77 | }.onEditingBegan { 78 | log("Editing began") 79 | }.onEditingEnded { 80 | log("Editing ended") 81 | }.onReturn { 82 | log("Return key tapped") 83 | } 84 | /*: 85 | ## Delegation 86 | `UITextField` also employs delegation to help define its behavior. Below 87 | is how you would implement `UITextFieldDelegate` methods using closures. 88 | */ 89 | textfield 90 | .didBeginEditing { 91 | log("Did begin editing delegate") 92 | }.shouldClear { 93 | log("Text clearing") 94 | return true 95 | }.shouldChangeCharacters { range, string in 96 | return true 97 | } 98 | /*: 99 | Although these convenience closures are not exhaustive, there is a way to 100 | use a closure for any `UIControlEvents`. 101 | */ 102 | button.on(.touchDragInside) { sender, event in 103 | log("Dragging inside button") 104 | } 105 | //: * * * * 106 | //: [Click here to explore using **UITableView** closures](@next) 107 | //: * * * * 108 | -------------------------------------------------------------------------------- /Xcode/Playground/ClosuresDemo.playground/Pages/UIControls.xcplaygroundpage/Sources/Setup.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import PlaygroundSupport 3 | 4 | public class ControlsDemoViewController: BaseViewController, PrintableController { 5 | 6 | @IBOutlet public var button: UIButton! 7 | @IBOutlet public var textField: UITextField! 8 | @IBOutlet public var uiSwitch: UISwitch! 9 | @IBOutlet public var slider: UISlider! 10 | @IBOutlet public var segmentedControl: UISegmentedControl! 11 | @IBOutlet public var stepper: UIStepper! 12 | @IBOutlet public var pageControl: UIPageControl! 13 | @IBOutlet public var datePicker: UIDatePicker! 14 | @IBOutlet public var scrollView: UIScrollView! 15 | @IBOutlet public var stackView: UIStackView! 16 | @IBOutlet public var printableTextView: UITextView? 17 | 18 | public override func touchesEnded(_ touches: Set, with event: UIEvent?) { 19 | view.endEditing(true) 20 | } 21 | 22 | public override func viewDidAppear(_ animated: Bool) { 23 | super.viewDidAppear(animated) 24 | scrollView.contentSize = stackView.bounds.size 25 | } 26 | 27 | @IBAction public func doneTapped() { 28 | view.endEditing(true) 29 | } 30 | 31 | public func convert(date: Date) -> String { 32 | let formatter = DateFormatter() 33 | formatter.dateFormat = "MM/dd/yyyy" 34 | return formatter.string(from: date) 35 | } 36 | } 37 | 38 | public let viewController = ControlsDemoViewController(nibName: "ControlsDemoViewController", bundle: nil) 39 | public let view = viewController.view! 40 | 41 | public func log(_ string: String) { 42 | viewController.print(text: string) 43 | print(string) 44 | } 45 | 46 | public func log(_ date: Date) { 47 | let dateString = viewController.convert(date: date) 48 | log(dateString) 49 | } 50 | 51 | public let button = viewController.button! 52 | public let textfield = viewController.textField! 53 | public let slider = viewController.slider! 54 | public let segmentedControl = viewController.segmentedControl! 55 | public let stepper = viewController.stepper! 56 | public let pageControl = viewController.pageControl! 57 | public let uiSwitch = viewController.uiSwitch! 58 | public let datePicker = viewController.datePicker! 59 | -------------------------------------------------------------------------------- /Xcode/Playground/ClosuresDemo.playground/Pages/UIGestureRecognizer.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | import UIKit;import PlaygroundSupport;PlaygroundPage.current.liveView = viewController 2 | import Closures 3 | //: [Go back](@previous) 4 | /*: 5 | # UIGestureRecognizer 6 | 7 | The `UIGestureRecognizer` initializers and delegation wrappers 8 | make it easy to add gesture recognizers to views. It also uses 9 | closures instead of target-action and delegation. 10 | 11 | ## Target-Action Initializers 12 | 13 | The following is how you would add a double tap gesture 14 | recognizer to your view using one of the custom initializers. 15 | As always, we have a closure handler to respond to the gesture's 16 | double tap action. 17 | */ 18 | let doubleTapGesture = UITapGestureRecognizer(tapsRequired: 2) { _ in 19 | log("double tapped") 20 | } 21 | view.addGestureRecognizer(doubleTapGesture) 22 | /*: 23 | These convenience initializers, delegate closures, and closure recognizers 24 | have been added to all of the existing concrete subclasses, including: 25 | 26 | * `UITapGestureRecognizer` 27 | * `UIPinchGestureRecognizer` 28 | * `UIRotationGestureRecognizer` 29 | * `UISwipeGestureRecognizer` 30 | * `UIPanGestureRecognizer` 31 | * `UIScreenEdgePanGestureRecognizer` 32 | * `UILongPressGestureRecognizer` 33 | 34 | There is also a method for you to configure a custom gesture recognizer 35 | to use closure handlers for recognition: 36 | */ 37 | let myCustomGesture = MyCustomGestureRecognizer() 38 | configure(gesture: myCustomGesture) { _ in 39 | /// a closure that's called when the gesture has ended 40 | } 41 | /*: 42 | ## Delegation 43 | With convenient extension methods on `UIGestureRecognizer` and `UIView`, 44 | we can daisy chain an entire gesture cycle, including responding 45 | to `UIGestureRecognizerDelegate` methods. 46 | */ 47 | view 48 | .addPanGesture() { pan in 49 | guard pan.state == .ended else { return } 50 | log("view panned") 51 | }.shouldBegin() { 52 | true 53 | }.shouldRecognizeSimultaneouslyWith { 54 | $0 === doubleTapGesture 55 | } 56 | //: * * * * 57 | //: [Click here to explore using **UIPickerView** closures](@next) 58 | //: * * * * 59 | -------------------------------------------------------------------------------- /Xcode/Playground/ClosuresDemo.playground/Pages/UIGestureRecognizer.xcplaygroundpage/Resources/GesturesDemoViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Courier 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /Xcode/Playground/ClosuresDemo.playground/Pages/UIGestureRecognizer.xcplaygroundpage/Sources/Setup.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import PlaygroundSupport 3 | 4 | public class GesturesDemoViewController: BaseViewController, PrintableController { 5 | @IBOutlet public var printableTextView: UITextView? 6 | } 7 | 8 | public let viewController = GesturesDemoViewController(nibName: "GesturesDemoViewController", bundle: nil) 9 | public let view = viewController.view! 10 | 11 | public func log(_ string: String) { 12 | viewController.print(text: string) 13 | print(string) 14 | } 15 | 16 | public class MyCustomGestureRecognizer: UIGestureRecognizer {} 17 | -------------------------------------------------------------------------------- /Xcode/Playground/ClosuresDemo.playground/Pages/UIImagePickerController.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Closures 3 | //: [Go back](@previous) 4 | /*: 5 | # UIImagePickerController 6 | 7 | * Callout(Meh): 8 | UIImagePickerController doesn't exactly work in playgrounds yet 9 | so this is just example code for now. 10 | 11 | The following is an example of using closures to 12 | select media from a `UIImagePickerController`. 13 | A simple picker is created which defaults to allowing 14 | images to be selected from the photos library. This 15 | initializer will call the handler when an image is selected. 16 | When preseting with the `present(from:)` method, 17 | the `UIImagePickerController` will dismiss itself on user cancel 18 | or after the picker has picked its content (after the closure 19 | is called). 20 | 21 | ```swift 22 | UIImagePickerController() { result,picker in 23 | myImageView.image = result.editedImage 24 | }.present(from: self) 25 | ``` 26 | 27 | You can also customize the picker if you need more control, including 28 | setting your own initial values and delegate callbacks. 29 | The following is a verbose example using most of the possible 30 | customization points. 31 | 32 | ```swift 33 | UIImagePickerController( 34 | source: .camera, 35 | allow: [.image, .movie], 36 | cameraOverlay: nil, 37 | showsCameraControls: true) { result,picker in 38 | myImageView.image = result.originalImage 39 | }.didCancel { [weak self] in 40 | // some custom didCancel implementation 41 | self?.dismiss(animated: animation.animate) 42 | }.present(from: self) 43 | ``` 44 | */ 45 | 46 | //: * * * * 47 | //: [Click here to explore using **UIBarButtonItem** closures](@next) 48 | //: * * * * 49 | -------------------------------------------------------------------------------- /Xcode/Playground/ClosuresDemo.playground/Pages/UIPickerView.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | import UIKit;import PlaygroundSupport;PlaygroundPage.current.liveView = viewController 2 | import Closures 3 | //: [Go back](@previous) 4 | /*: 5 | # UIPickerView 6 | ## Delegate and DataSource 7 | 8 | `UIPickerView` closures make it easy to implement `UIPickerViewDelegate` and 9 | `UIPickerViewDataSource` protocol methods in an organized way. The following 10 | is an example of a simple collection view that displays strings in each row. 11 | */ 12 | let pickerView = viewController.pickerView! 13 | let countries = getAllCountries() 14 | 15 | func loadPickerView() { 16 | pickerView 17 | .numberOfRowsInComponent() { _ in 18 | countries.count 19 | }.titleForRow() { row, component in 20 | countries[row] 21 | }.didSelectRow { row, component in 22 | log("\(countries[row]) selected") 23 | }.reloadAllComponents() 24 | } 25 | loadPickerView() 26 | /*: 27 | ## Arrays 28 | These operations are common. Usually, they involve populating the `UIPickerView` 29 | with the values from an array. `Closures` framework gives you a convenient 30 | way to pass your collection to the table view, so that it can perform the boilerplate 31 | operations for you, especially the ones that are required to make the picker 32 | view perform at a basic level. 33 | 34 | Let's setup our segmented control to load the picker view with different options. 35 | When binding to an Array it will show the same countries, but reversed, so 36 | you can visually see the change after tapping the segmented control. 37 | */ 38 | let segmentedControl = viewController.segmentedControl! 39 | segmentedControl.onChange { 40 | switch $0 { 41 | case 1: 42 | loadPickerView(countries: countries) 43 | case 2: 44 | loadPickerView(countries: getCountryDayComponents()) 45 | default: 46 | loadPickerView() 47 | } 48 | } 49 | /*: 50 | * Important: 51 | Please remember that Swift `Array`s are value types. This means that they 52 | are copied when mutaded. When the values or sequence of your array changes, you will 53 | need to call one of the `add` methods again, just before you 54 | call reloadData(). 55 | */ 56 | func loadPickerView(countries: [String]) { 57 | let reversed = Array(countries.reversed()) 58 | pickerView 59 | .addStrings(reversed) { country,component,row in 60 | log("\(country) selected from array") 61 | }.reloadAllComponents() 62 | } 63 | /*: 64 | * Note: 65 | Be sure to note that most of the closure callbacks in these array binding 66 | methods switch the order of the parameters of row and component. Most of the 67 | `UIPickerView` delegate/datasource method parameters pass `row,component`. The 68 | parameters on the `add` methods switch the order and send `component,row` 69 | 70 | ### Multiple Components 71 | And finally, you can just as easily show mutliple components by binding a 72 | 2-dimensional array. When using this method, the outer dimension of the array is the 73 | component (columns) and the inner dimension are the rows in that component. 74 | 75 | ```swift 76 | let anElement = myTwoDArray[component][row] 77 | ``` 78 | 79 | In this example, the more verbose row handler is provided just to show the 80 | different variations. Adding multiple components has a convenient method to 81 | deal with string only arrays also. 82 | */ 83 | func loadPickerView(countries: [[String]]) { 84 | pickerView.addComponents( 85 | countries, 86 | rowTitle: { country,component,row in 87 | country}, 88 | didSelect: { country,component,row in 89 | log("\(country) selected from 2D Array") 90 | }) 91 | 92 | /** 93 | This also allows you to override any default behavior so 94 | you aren't overly committed, even though you're initially binding everything 95 | to the `Array`. 96 | */ 97 | pickerView.widthForComponent { component in 98 | component == 0 ? 200 : 100 99 | }.reloadAllComponents() 100 | } 101 | //: * * * * 102 | //: [Click here to explore using **KVO** closures](@next) 103 | //: * * * * 104 | -------------------------------------------------------------------------------- /Xcode/Playground/ClosuresDemo.playground/Pages/UIPickerView.xcplaygroundpage/Resources/PickerDemoViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Courier 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /Xcode/Playground/ClosuresDemo.playground/Pages/UIPickerView.xcplaygroundpage/Sources/Setup.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import PlaygroundSupport 3 | 4 | public class PickerDemoViewController: BaseViewController, PrintableController { 5 | @IBOutlet public var segmentedControl: UISegmentedControl! 6 | @IBOutlet public var pickerView: UIPickerView! 7 | @IBOutlet public var printableTextView: UITextView? 8 | } 9 | 10 | public let viewController = PickerDemoViewController(nibName: "PickerDemoViewController", bundle: nil) 11 | public let view = viewController.view! 12 | 13 | public func log(_ string: String) { 14 | viewController.print(text: string) 15 | print(string) 16 | } 17 | 18 | public func getCountryDayComponents() -> [[String]] { 19 | let days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"] 20 | let countries = getAllCountries() 21 | return [countries, days] 22 | } 23 | -------------------------------------------------------------------------------- /Xcode/Playground/ClosuresDemo.playground/Pages/UITableView.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | import UIKit;import PlaygroundSupport;PlaygroundPage.current.liveView = viewController 2 | import Closures 3 | //: [Go back](@previous) 4 | /*: 5 | # UITableView 6 | ## Delegate and DataSource 7 | 8 | `UITableView` closures make it easy to implement `UITableViewDelegate` and 9 | `UITableViewDataSource` protocol methods in an organized way. The following 10 | is an example of a simple table view that displays strings in a basic cell. 11 | */ 12 | let tableView = viewController.tableView! 13 | let countries = getAllCountries() 14 | 15 | func loadTableView() { 16 | tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell") 17 | 18 | tableView 19 | .numberOfRows { _ in 20 | countries.count 21 | }.cellForRow { indexPath in 22 | let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) 23 | cell.textLabel!.text = countries[indexPath.row] 24 | return cell 25 | }.didSelectRowAt { 26 | print("\(countries[$0.row]) selected") 27 | }.reloadData() 28 | } 29 | loadTableView() 30 | /*: 31 | ## Arrays 32 | These operations are common. Usually, they involve populating the `UITableView` 33 | with the values from an array. `Closures` framework gives you a convenient 34 | way to pass your array to the table view, so that it can perform the boiler 35 | plate operations for you, especially the ones that are required to make the table 36 | view perform at a basic level. 37 | */ 38 | /*: 39 | Let's setup our segmented control to load the table view with different options. 40 | When binding to an Array it will show the same countries, but reversed, so 41 | you can visually see the change after tapping the segmented control. 42 | */ 43 | let segmentedControl = viewController.segmentedControl! 44 | segmentedControl.onChange { 45 | switch $0 { 46 | case 1: 47 | loadTableView(countries: Array(countries.reversed())) 48 | case 2: 49 | loadTableView(countries: countries.sectionedByFirstLetter) 50 | default: 51 | loadTableView() 52 | } 53 | } 54 | /*: 55 | * Important: 56 | Please remember that Swift `Array`s are value types. This means that they 57 | are copied when mutated. When the values or sequence of your array changes, you will 58 | need to call `addElements` again, just before you call reloadData(). 59 | */ 60 | func loadTableView(countries: [String]) { 61 | tableView 62 | .addElements(countries, cell: UITableViewCell.self) { country, cell, index in 63 | cell.textLabel!.text = country 64 | }.reloadData() 65 | } 66 | /*: 67 | ### Multiple Sections 68 | * * * * 69 | And finally, you can just as easily have a grouped table view, by binding a 70 | 2-dimensional array. Before calling this method, we grouped the countries by their 71 | first letter. 72 | */ 73 | func loadTableView(countries: [[String]]) { 74 | tableView.addSections( 75 | countries, 76 | cell: UITableViewCell.self, 77 | headerTitle: { countryArray,index in 78 | String(countryArray.first!.first!)}, 79 | row: { country, cell, index in 80 | cell.textLabel!.text = country 81 | }) 82 | 83 | /** 84 | This also allows you to override any default behavior so 85 | you aren't overly committed, even though you're initially binding everything 86 | to the `Array`. 87 | */ 88 | tableView 89 | .estimatedHeightForHeaderInSection { _ in 90 | 30 91 | }.heightForHeaderInSection { _ in 92 | 30 93 | }.reloadData() 94 | } 95 | //: * * * * 96 | //: [Click here to explore using **UICollectionView** closures](@next) 97 | //: * * * * 98 | -------------------------------------------------------------------------------- /Xcode/Playground/ClosuresDemo.playground/Pages/UITableView.xcplaygroundpage/Resources/TableViewDemoViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /Xcode/Playground/ClosuresDemo.playground/Pages/UITableView.xcplaygroundpage/Sources/Setup.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import PlaygroundSupport 3 | 4 | public class TableViewDemoViewController: BaseViewController { 5 | @IBOutlet public var tableView: UITableView! 6 | @IBOutlet public var segmentedControl: UISegmentedControl! 7 | } 8 | 9 | public let viewController = TableViewDemoViewController(nibName: "TableViewDemoViewController", bundle: nil) 10 | public let view = viewController.view! 11 | -------------------------------------------------------------------------------- /Xcode/Playground/ClosuresDemo.playground/Setup.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vhesener/Closures/d42d1cea00b05fa6778e2a0e1601028b47a32f79/Xcode/Playground/ClosuresDemo.playground/Setup.o -------------------------------------------------------------------------------- /Xcode/Playground/ClosuresDemo.playground/Sources/Setup.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import PlaygroundSupport 3 | 4 | public protocol PrintableController { 5 | var printableTextView: UITextView? {get} 6 | func print(text: String) 7 | } 8 | 9 | extension PrintableController { 10 | public func print(text: String) { 11 | guard let textView = printableTextView else { 12 | return 13 | } 14 | textView.text = textView.text + "\n" + text 15 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { 16 | guard let location = textView.text?.count, 17 | location > 0 else { 18 | return 19 | } 20 | textView.scrollRangeToVisible(NSMakeRange(location, 1)) 21 | } 22 | } 23 | } 24 | 25 | open class BaseViewController: UIViewController {} 26 | 27 | public func getAllCountries() -> [String] { 28 | let locale = Locale(identifier:"en_US") 29 | return Locale.isoRegionCodes.compactMap { 30 | locale.localizedString(forRegionCode: $0)! 31 | }.sorted() 32 | } 33 | 34 | public extension Array where Element == String { 35 | var sectionedByFirstLetter: [[String]] { 36 | let sections = Dictionary(grouping: self) { $0.first! } 37 | return sections.keys.sorted().map { 38 | sections[$0]! 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Xcode/Playground/ClosuresDemo.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/Controllers.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Controllers Reference 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |

Closures Docs

18 |

View on GitHub

19 |
20 |
21 |
22 | 27 |
28 |
29 | 142 |
143 |
144 |
145 |

Controllers

146 | 147 |
148 |
149 |
150 |
    151 |
  • 152 |
    153 | 154 | 155 | 156 | UIImagePickerController 157 | 158 |
    159 |
    160 |
    161 |
    162 |
    163 |
    164 | 165 | See more 166 |
    167 |
    168 |

    Declaration

    169 |
    170 |

    Swift

    171 |
    extension UIImagePickerController
    172 | 173 |
    174 |
    175 |
    176 |
    177 |
  • 178 |
179 |
180 |
181 |
182 | 186 |
187 |
188 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /docs/Extensions/UIPinchGestureRecognizer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | UIPinchGestureRecognizer Extension Reference 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |

Closures Docs

18 |

View on GitHub

19 |
20 |
21 |
22 | 27 |
28 |
29 | 142 |
143 |
144 |
145 |

UIPinchGestureRecognizer

146 |
147 |
148 |
extension UIPinchGestureRecognizer
149 | 150 |
151 |
152 | 153 |
154 |
155 |
156 |
    157 |
  • 158 |
    159 | 160 | 161 | 162 | init(handler:) 163 | 164 |
    165 |
    166 |
    167 |
    168 |
    169 |
    170 |

    A convenience initializer for a UIPinchGestureRecognizer so that it 171 | can be configured with a single line of code.

    172 | 173 |
    174 |
    175 |

    Declaration

    176 |
    177 |

    Swift

    178 |
    public convenience init(handler: @escaping (_ gesture: UIPinchGestureRecognizer) -> Void)
    179 | 180 |
    181 |
    182 |
    183 |

    Parameters

    184 | 185 | 186 | 187 | 192 | 197 | 198 | 199 |
    188 | 189 | handler 190 | 191 | 193 |
    194 |

    The closure that is called when the gesture is recognized

    195 |
    196 |
    200 |
    201 |
    202 |
    203 |
  • 204 |
205 |
206 |
207 |
208 | 212 |
213 |
214 | 215 | 216 | 217 | -------------------------------------------------------------------------------- /docs/Extensions/UIRotationGestureRecognizer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | UIRotationGestureRecognizer Extension Reference 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |

Closures Docs

18 |

View on GitHub

19 |
20 |
21 |
22 | 27 |
28 |
29 | 142 |
143 |
144 |
145 |

UIRotationGestureRecognizer

146 |
147 |
148 |
extension UIRotationGestureRecognizer
149 | 150 |
151 |
152 | 153 |
154 |
155 |
156 |
    157 |
  • 158 |
    159 | 160 | 161 | 162 | init(handler:) 163 | 164 |
    165 |
    166 |
    167 |
    168 |
    169 |
    170 |

    A convenience initializer for a UIRotationGestureRecognizer so that it 171 | can be configured with a single line of code.

    172 | 173 |
    174 |
    175 |

    Declaration

    176 |
    177 |

    Swift

    178 |
    public convenience init(handler: @escaping (_ gesture: UIRotationGestureRecognizer) -> Void)
    179 | 180 |
    181 |
    182 |
    183 |

    Parameters

    184 | 185 | 186 | 187 | 192 | 197 | 198 | 199 |
    188 | 189 | handler 190 | 191 | 193 |
    194 |

    The closure that is called when the gesture is recognized

    195 |
    196 |
    200 |
    201 |
    202 |
    203 |
  • 204 |
205 |
206 |
207 |
208 | 212 |
213 |
214 | 215 | 216 | 217 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | include: 2 | - "_5FKeyValueCodingAndObserving.html" 3 | 4 | -------------------------------------------------------------------------------- /docs/css/highlight.css: -------------------------------------------------------------------------------- 1 | /* Credit to https://gist.github.com/wataru420/2048287 */ 2 | .highlight { 3 | /* Comment */ 4 | /* Error */ 5 | /* Keyword */ 6 | /* Operator */ 7 | /* Comment.Multiline */ 8 | /* Comment.Preproc */ 9 | /* Comment.Single */ 10 | /* Comment.Special */ 11 | /* Generic.Deleted */ 12 | /* Generic.Deleted.Specific */ 13 | /* Generic.Emph */ 14 | /* Generic.Error */ 15 | /* Generic.Heading */ 16 | /* Generic.Inserted */ 17 | /* Generic.Inserted.Specific */ 18 | /* Generic.Output */ 19 | /* Generic.Prompt */ 20 | /* Generic.Strong */ 21 | /* Generic.Subheading */ 22 | /* Generic.Traceback */ 23 | /* Keyword.Constant */ 24 | /* Keyword.Declaration */ 25 | /* Keyword.Pseudo */ 26 | /* Keyword.Reserved */ 27 | /* Keyword.Type */ 28 | /* Literal.Number */ 29 | /* Literal.String */ 30 | /* Name.Attribute */ 31 | /* Name.Builtin */ 32 | /* Name.Class */ 33 | /* Name.Constant */ 34 | /* Name.Entity */ 35 | /* Name.Exception */ 36 | /* Name.Function */ 37 | /* Name.Namespace */ 38 | /* Name.Tag */ 39 | /* Name.Variable */ 40 | /* Operator.Word */ 41 | /* Text.Whitespace */ 42 | /* Literal.Number.Float */ 43 | /* Literal.Number.Hex */ 44 | /* Literal.Number.Integer */ 45 | /* Literal.Number.Oct */ 46 | /* Literal.String.Backtick */ 47 | /* Literal.String.Char */ 48 | /* Literal.String.Doc */ 49 | /* Literal.String.Double */ 50 | /* Literal.String.Escape */ 51 | /* Literal.String.Heredoc */ 52 | /* Literal.String.Interpol */ 53 | /* Literal.String.Other */ 54 | /* Literal.String.Regex */ 55 | /* Literal.String.Single */ 56 | /* Literal.String.Symbol */ 57 | /* Name.Builtin.Pseudo */ 58 | /* Name.Variable.Class */ 59 | /* Name.Variable.Global */ 60 | /* Name.Variable.Instance */ 61 | /* Literal.Number.Integer.Long */ } 62 | .highlight .c { 63 | color: #999988; 64 | font-style: italic; } 65 | .highlight .err { 66 | color: #a61717; 67 | background-color: #e3d2d2; } 68 | .highlight .k { 69 | color: #000000; 70 | font-weight: bold; } 71 | .highlight .o { 72 | color: #000000; 73 | font-weight: bold; } 74 | .highlight .cm { 75 | color: #999988; 76 | font-style: italic; } 77 | .highlight .cp { 78 | color: #999999; 79 | font-weight: bold; } 80 | .highlight .c1 { 81 | color: #999988; 82 | font-style: italic; } 83 | .highlight .cs { 84 | color: #999999; 85 | font-weight: bold; 86 | font-style: italic; } 87 | .highlight .gd { 88 | color: #000000; 89 | background-color: #ffdddd; } 90 | .highlight .gd .x { 91 | color: #000000; 92 | background-color: #ffaaaa; } 93 | .highlight .ge { 94 | color: #000000; 95 | font-style: italic; } 96 | .highlight .gr { 97 | color: #aa0000; } 98 | .highlight .gh { 99 | color: #999999; } 100 | .highlight .gi { 101 | color: #000000; 102 | background-color: #ddffdd; } 103 | .highlight .gi .x { 104 | color: #000000; 105 | background-color: #aaffaa; } 106 | .highlight .go { 107 | color: #888888; } 108 | .highlight .gp { 109 | color: #555555; } 110 | .highlight .gs { 111 | font-weight: bold; } 112 | .highlight .gu { 113 | color: #aaaaaa; } 114 | .highlight .gt { 115 | color: #aa0000; } 116 | .highlight .kc { 117 | color: #000000; 118 | font-weight: bold; } 119 | .highlight .kd { 120 | color: #000000; 121 | font-weight: bold; } 122 | .highlight .kp { 123 | color: #000000; 124 | font-weight: bold; } 125 | .highlight .kr { 126 | color: #000000; 127 | font-weight: bold; } 128 | .highlight .kt { 129 | color: #445588; } 130 | .highlight .m { 131 | color: #009999; } 132 | .highlight .s { 133 | color: #d14; } 134 | .highlight .na { 135 | color: #008080; } 136 | .highlight .nb { 137 | color: #0086B3; } 138 | .highlight .nc { 139 | color: #445588; 140 | font-weight: bold; } 141 | .highlight .no { 142 | color: #008080; } 143 | .highlight .ni { 144 | color: #800080; } 145 | .highlight .ne { 146 | color: #990000; 147 | font-weight: bold; } 148 | .highlight .nf { 149 | color: #990000; } 150 | .highlight .nn { 151 | color: #555555; } 152 | .highlight .nt { 153 | color: #000080; } 154 | .highlight .nv { 155 | color: #008080; } 156 | .highlight .ow { 157 | color: #000000; 158 | font-weight: bold; } 159 | .highlight .w { 160 | color: #bbbbbb; } 161 | .highlight .mf { 162 | color: #009999; } 163 | .highlight .mh { 164 | color: #009999; } 165 | .highlight .mi { 166 | color: #009999; } 167 | .highlight .mo { 168 | color: #009999; } 169 | .highlight .sb { 170 | color: #d14; } 171 | .highlight .sc { 172 | color: #d14; } 173 | .highlight .sd { 174 | color: #d14; } 175 | .highlight .s2 { 176 | color: #d14; } 177 | .highlight .se { 178 | color: #d14; } 179 | .highlight .sh { 180 | color: #d14; } 181 | .highlight .si { 182 | color: #d14; } 183 | .highlight .sx { 184 | color: #d14; } 185 | .highlight .sr { 186 | color: #009926; } 187 | .highlight .s1 { 188 | color: #d14; } 189 | .highlight .ss { 190 | color: #990073; } 191 | .highlight .bp { 192 | color: #999999; } 193 | .highlight .vc { 194 | color: #008080; } 195 | .highlight .vg { 196 | color: #008080; } 197 | .highlight .vi { 198 | color: #008080; } 199 | .highlight .il { 200 | color: #009999; } 201 | -------------------------------------------------------------------------------- /docs/css/jazzy.css: -------------------------------------------------------------------------------- 1 | html, body, div, span, h1, h3, h4, p, a, code, em, img, ul, li, table, tbody, tr, td { 2 | background: transparent; 3 | border: 0; 4 | margin: 0; 5 | outline: 0; 6 | padding: 0; 7 | vertical-align: baseline; } 8 | 9 | body { 10 | background-color: #f2f2f2; 11 | font-family: Helvetica, freesans, Arial, sans-serif; 12 | font-size: 14px; 13 | -webkit-font-smoothing: subpixel-antialiased; 14 | word-wrap: break-word; } 15 | 16 | h1, h2, h3 { 17 | margin-top: 0.8em; 18 | margin-bottom: 0.3em; 19 | font-weight: 100; 20 | color: black; } 21 | 22 | h1 { 23 | font-size: 2.5em; } 24 | 25 | h2 { 26 | font-size: 2em; 27 | border-bottom: 1px solid #e2e2e2; } 28 | 29 | h4 { 30 | font-size: 13px; 31 | line-height: 1.5; 32 | margin-top: 21px; } 33 | 34 | h5 { 35 | font-size: 1.1em; } 36 | 37 | h6 { 38 | font-size: 1.1em; 39 | color: #777; } 40 | 41 | .section-name { 42 | color: gray; 43 | display: block; 44 | font-family: Helvetica; 45 | font-size: 22px; 46 | font-weight: 100; 47 | margin-bottom: 15px; } 48 | 49 | pre, code { 50 | font: 0.95em Menlo, monospace; 51 | color: #777; 52 | word-wrap: normal; } 53 | 54 | p code, li code { 55 | background-color: #eee; 56 | padding: 2px 4px; 57 | border-radius: 4px; } 58 | 59 | a { 60 | color: #0088cc; 61 | text-decoration: none; } 62 | 63 | ul { 64 | padding-left: 15px; } 65 | 66 | li { 67 | line-height: 1.8em; } 68 | 69 | img { 70 | max-width: 100%; } 71 | 72 | blockquote { 73 | margin-left: 0; 74 | padding: 0 10px; 75 | border-left: 4px solid #ccc; } 76 | 77 | .content-wrapper { 78 | margin: 0 auto; 79 | width: 980px; } 80 | 81 | header { 82 | font-size: 0.85em; 83 | line-height: 26px; 84 | background-color: #414141; 85 | position: fixed; 86 | width: 100%; 87 | z-index: 1; } 88 | header img { 89 | padding-right: 6px; 90 | vertical-align: -4px; 91 | height: 16px; } 92 | header a { 93 | color: #fff; } 94 | header p { 95 | float: left; 96 | color: #999; } 97 | header .header-right { 98 | float: right; 99 | margin-left: 16px; } 100 | 101 | #breadcrumbs { 102 | background-color: #f2f2f2; 103 | height: 27px; 104 | padding-top: 17px; 105 | position: fixed; 106 | width: 100%; 107 | z-index: 1; 108 | margin-top: 26px; } 109 | #breadcrumbs #carat { 110 | height: 10px; 111 | margin: 0 5px; } 112 | 113 | .sidebar { 114 | background-color: #f9f9f9; 115 | border: 1px solid #e2e2e2; 116 | overflow-y: auto; 117 | overflow-x: hidden; 118 | position: fixed; 119 | top: 70px; 120 | bottom: 0; 121 | width: 230px; 122 | word-wrap: normal; } 123 | 124 | .nav-groups { 125 | list-style-type: none; 126 | background: #fff; 127 | padding-left: 0; } 128 | 129 | .nav-group-name { 130 | border-bottom: 1px solid #e2e2e2; 131 | font-size: 1.1em; 132 | font-weight: 100; 133 | padding: 15px 0 15px 20px; } 134 | .nav-group-name > a { 135 | color: #333; } 136 | 137 | .nav-group-tasks { 138 | margin-top: 5px; } 139 | 140 | .nav-group-task { 141 | font-size: 0.9em; 142 | list-style-type: none; 143 | white-space: nowrap; } 144 | .nav-group-task a { 145 | color: #888; } 146 | 147 | .main-content { 148 | background-color: #fff; 149 | border: 1px solid #e2e2e2; 150 | margin-left: 246px; 151 | position: absolute; 152 | overflow: hidden; 153 | padding-bottom: 20px; 154 | top: 70px; 155 | width: 734px; } 156 | .main-content p, .main-content a, .main-content code, .main-content em, .main-content ul, .main-content table, .main-content blockquote { 157 | margin-bottom: 1em; } 158 | .main-content p { 159 | line-height: 1.8em; } 160 | .main-content section .section:first-child { 161 | margin-top: 0; 162 | padding-top: 0; } 163 | .main-content section .task-group-section .task-group:first-of-type { 164 | padding-top: 10px; } 165 | .main-content section .task-group-section .task-group:first-of-type .section-name { 166 | padding-top: 15px; } 167 | .main-content section .heading:before { 168 | content: ""; 169 | display: block; 170 | padding-top: 70px; 171 | margin: -70px 0 0; } 172 | 173 | .section { 174 | padding: 0 25px; } 175 | 176 | .highlight { 177 | background-color: #eee; 178 | padding: 10px 12px; 179 | border: 1px solid #e2e2e2; 180 | border-radius: 4px; 181 | overflow-x: auto; } 182 | 183 | .declaration .highlight { 184 | overflow-x: initial; 185 | padding: 0 40px 40px 0; 186 | margin-bottom: -25px; 187 | background-color: transparent; 188 | border: none; } 189 | 190 | .section-name { 191 | margin: 0; 192 | margin-left: 18px; } 193 | 194 | .task-group-section { 195 | padding-left: 6px; 196 | border-top: 1px solid #e2e2e2; } 197 | 198 | .task-group { 199 | padding-top: 0px; } 200 | 201 | .task-name-container a[name]:before { 202 | content: ""; 203 | display: block; 204 | padding-top: 70px; 205 | margin: -70px 0 0; } 206 | 207 | .item { 208 | padding-top: 8px; 209 | width: 100%; 210 | list-style-type: none; } 211 | .item a[name]:before { 212 | content: ""; 213 | display: block; 214 | padding-top: 70px; 215 | margin: -70px 0 0; } 216 | .item code { 217 | background-color: transparent; 218 | padding: 0; } 219 | .item .token, .item .direct-link { 220 | padding-left: 3px; 221 | margin-left: 15px; 222 | font-size: 11.9px; 223 | transition: all 300ms; } 224 | .item .token-open { 225 | margin-left: 0px; } 226 | .item .discouraged { 227 | text-decoration: line-through; } 228 | .item .declaration-note { 229 | font-size: .85em; 230 | color: gray; 231 | font-style: italic; } 232 | 233 | .pointer-container { 234 | border-bottom: 1px solid #e2e2e2; 235 | left: -23px; 236 | padding-bottom: 13px; 237 | position: relative; 238 | width: 110%; } 239 | 240 | .pointer { 241 | background: #f9f9f9; 242 | border-left: 1px solid #e2e2e2; 243 | border-top: 1px solid #e2e2e2; 244 | height: 12px; 245 | left: 21px; 246 | top: -7px; 247 | -webkit-transform: rotate(45deg); 248 | -moz-transform: rotate(45deg); 249 | -o-transform: rotate(45deg); 250 | transform: rotate(45deg); 251 | position: absolute; 252 | width: 12px; } 253 | 254 | .height-container { 255 | display: none; 256 | left: -25px; 257 | padding: 0 25px; 258 | position: relative; 259 | width: 100%; 260 | overflow: hidden; } 261 | .height-container .section { 262 | background: #f9f9f9; 263 | border-bottom: 1px solid #e2e2e2; 264 | left: -25px; 265 | position: relative; 266 | width: 100%; 267 | padding-top: 10px; 268 | padding-bottom: 5px; } 269 | 270 | .aside, .language { 271 | padding: 6px 12px; 272 | margin: 12px 0; 273 | border-left: 5px solid #dddddd; 274 | overflow-y: hidden; } 275 | .aside .aside-title, .language .aside-title { 276 | font-size: 9px; 277 | letter-spacing: 2px; 278 | text-transform: uppercase; 279 | padding-bottom: 0; 280 | margin: 0; 281 | color: #aaa; 282 | -webkit-user-select: none; } 283 | .aside p:last-child, .language p:last-child { 284 | margin-bottom: 0; } 285 | 286 | .language { 287 | border-left: 5px solid #cde9f4; } 288 | .language .aside-title { 289 | color: #4b8afb; } 290 | 291 | .aside-warning, .aside-deprecated, .aside-unavailable { 292 | border-left: 5px solid #ff6666; } 293 | .aside-warning .aside-title, .aside-deprecated .aside-title, .aside-unavailable .aside-title { 294 | color: #ff0000; } 295 | 296 | .graybox { 297 | border-collapse: collapse; 298 | width: 100%; } 299 | .graybox p { 300 | margin: 0; 301 | word-break: break-word; 302 | min-width: 50px; } 303 | .graybox td { 304 | border: 1px solid #e2e2e2; 305 | padding: 5px 25px 5px 10px; 306 | vertical-align: middle; } 307 | .graybox tr td:first-of-type { 308 | text-align: right; 309 | padding: 7px; 310 | vertical-align: top; 311 | word-break: normal; 312 | width: 40px; } 313 | 314 | .slightly-smaller { 315 | font-size: 0.9em; } 316 | 317 | #footer { 318 | position: relative; 319 | top: 10px; 320 | bottom: 0px; 321 | margin-left: 25px; } 322 | #footer p { 323 | margin: 0; 324 | color: #aaa; 325 | font-size: 0.8em; } 326 | 327 | html.dash header, html.dash #breadcrumbs, html.dash .sidebar { 328 | display: none; } 329 | html.dash .main-content { 330 | width: 980px; 331 | margin-left: 0; 332 | border: none; 333 | width: 100%; 334 | top: 0; 335 | padding-bottom: 0; } 336 | html.dash .height-container { 337 | display: block; } 338 | html.dash .item .token { 339 | margin-left: 0; } 340 | html.dash .content-wrapper { 341 | width: auto; } 342 | html.dash #footer { 343 | position: static; } 344 | -------------------------------------------------------------------------------- /docs/docsets/Closures.docset/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.jazzy.closures 7 | CFBundleName 8 | Closures 9 | DocSetPlatformFamily 10 | closures 11 | isDashDocset 12 | 13 | dashIndexFilePath 14 | index.html 15 | isJavaScriptEnabled 16 | 17 | DashDocSetFamily 18 | dashtoc 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/docsets/Closures.docset/Contents/Resources/Documents/Controllers.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Controllers Reference 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |

Closures Docs

18 |

View on GitHub

19 |
20 |
21 |
22 | 27 |
28 |
29 | 142 |
143 |
144 |
145 |

Controllers

146 | 147 |
148 |
149 |
150 |
    151 |
  • 152 |
    153 | 154 | 155 | 156 | UIImagePickerController 157 | 158 |
    159 |
    160 |
    161 |
    162 |
    163 |
    164 | 165 | See more 166 |
    167 |
    168 |

    Declaration

    169 |
    170 |

    Swift

    171 |
    extension UIImagePickerController
    172 | 173 |
    174 |
    175 |
    176 |
    177 |
  • 178 |
179 |
180 |
181 |
182 | 186 |
187 |
188 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /docs/docsets/Closures.docset/Contents/Resources/Documents/Extensions/UIPinchGestureRecognizer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | UIPinchGestureRecognizer Extension Reference 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |

Closures Docs

18 |

View on GitHub

19 |
20 |
21 |
22 | 27 |
28 |
29 | 142 |
143 |
144 |
145 |

UIPinchGestureRecognizer

146 |
147 |
148 |
extension UIPinchGestureRecognizer
149 | 150 |
151 |
152 | 153 |
154 |
155 |
156 |
    157 |
  • 158 |
    159 | 160 | 161 | 162 | init(handler:) 163 | 164 |
    165 |
    166 |
    167 |
    168 |
    169 |
    170 |

    A convenience initializer for a UIPinchGestureRecognizer so that it 171 | can be configured with a single line of code.

    172 | 173 |
    174 |
    175 |

    Declaration

    176 |
    177 |

    Swift

    178 |
    public convenience init(handler: @escaping (_ gesture: UIPinchGestureRecognizer) -> Void)
    179 | 180 |
    181 |
    182 |
    183 |

    Parameters

    184 | 185 | 186 | 187 | 192 | 197 | 198 | 199 |
    188 | 189 | handler 190 | 191 | 193 |
    194 |

    The closure that is called when the gesture is recognized

    195 |
    196 |
    200 |
    201 |
    202 |
    203 |
  • 204 |
205 |
206 |
207 |
208 | 212 |
213 |
214 | 215 | 216 | 217 | -------------------------------------------------------------------------------- /docs/docsets/Closures.docset/Contents/Resources/Documents/css/highlight.css: -------------------------------------------------------------------------------- 1 | /* Credit to https://gist.github.com/wataru420/2048287 */ 2 | .highlight { 3 | /* Comment */ 4 | /* Error */ 5 | /* Keyword */ 6 | /* Operator */ 7 | /* Comment.Multiline */ 8 | /* Comment.Preproc */ 9 | /* Comment.Single */ 10 | /* Comment.Special */ 11 | /* Generic.Deleted */ 12 | /* Generic.Deleted.Specific */ 13 | /* Generic.Emph */ 14 | /* Generic.Error */ 15 | /* Generic.Heading */ 16 | /* Generic.Inserted */ 17 | /* Generic.Inserted.Specific */ 18 | /* Generic.Output */ 19 | /* Generic.Prompt */ 20 | /* Generic.Strong */ 21 | /* Generic.Subheading */ 22 | /* Generic.Traceback */ 23 | /* Keyword.Constant */ 24 | /* Keyword.Declaration */ 25 | /* Keyword.Pseudo */ 26 | /* Keyword.Reserved */ 27 | /* Keyword.Type */ 28 | /* Literal.Number */ 29 | /* Literal.String */ 30 | /* Name.Attribute */ 31 | /* Name.Builtin */ 32 | /* Name.Class */ 33 | /* Name.Constant */ 34 | /* Name.Entity */ 35 | /* Name.Exception */ 36 | /* Name.Function */ 37 | /* Name.Namespace */ 38 | /* Name.Tag */ 39 | /* Name.Variable */ 40 | /* Operator.Word */ 41 | /* Text.Whitespace */ 42 | /* Literal.Number.Float */ 43 | /* Literal.Number.Hex */ 44 | /* Literal.Number.Integer */ 45 | /* Literal.Number.Oct */ 46 | /* Literal.String.Backtick */ 47 | /* Literal.String.Char */ 48 | /* Literal.String.Doc */ 49 | /* Literal.String.Double */ 50 | /* Literal.String.Escape */ 51 | /* Literal.String.Heredoc */ 52 | /* Literal.String.Interpol */ 53 | /* Literal.String.Other */ 54 | /* Literal.String.Regex */ 55 | /* Literal.String.Single */ 56 | /* Literal.String.Symbol */ 57 | /* Name.Builtin.Pseudo */ 58 | /* Name.Variable.Class */ 59 | /* Name.Variable.Global */ 60 | /* Name.Variable.Instance */ 61 | /* Literal.Number.Integer.Long */ } 62 | .highlight .c { 63 | color: #999988; 64 | font-style: italic; } 65 | .highlight .err { 66 | color: #a61717; 67 | background-color: #e3d2d2; } 68 | .highlight .k { 69 | color: #000000; 70 | font-weight: bold; } 71 | .highlight .o { 72 | color: #000000; 73 | font-weight: bold; } 74 | .highlight .cm { 75 | color: #999988; 76 | font-style: italic; } 77 | .highlight .cp { 78 | color: #999999; 79 | font-weight: bold; } 80 | .highlight .c1 { 81 | color: #999988; 82 | font-style: italic; } 83 | .highlight .cs { 84 | color: #999999; 85 | font-weight: bold; 86 | font-style: italic; } 87 | .highlight .gd { 88 | color: #000000; 89 | background-color: #ffdddd; } 90 | .highlight .gd .x { 91 | color: #000000; 92 | background-color: #ffaaaa; } 93 | .highlight .ge { 94 | color: #000000; 95 | font-style: italic; } 96 | .highlight .gr { 97 | color: #aa0000; } 98 | .highlight .gh { 99 | color: #999999; } 100 | .highlight .gi { 101 | color: #000000; 102 | background-color: #ddffdd; } 103 | .highlight .gi .x { 104 | color: #000000; 105 | background-color: #aaffaa; } 106 | .highlight .go { 107 | color: #888888; } 108 | .highlight .gp { 109 | color: #555555; } 110 | .highlight .gs { 111 | font-weight: bold; } 112 | .highlight .gu { 113 | color: #aaaaaa; } 114 | .highlight .gt { 115 | color: #aa0000; } 116 | .highlight .kc { 117 | color: #000000; 118 | font-weight: bold; } 119 | .highlight .kd { 120 | color: #000000; 121 | font-weight: bold; } 122 | .highlight .kp { 123 | color: #000000; 124 | font-weight: bold; } 125 | .highlight .kr { 126 | color: #000000; 127 | font-weight: bold; } 128 | .highlight .kt { 129 | color: #445588; } 130 | .highlight .m { 131 | color: #009999; } 132 | .highlight .s { 133 | color: #d14; } 134 | .highlight .na { 135 | color: #008080; } 136 | .highlight .nb { 137 | color: #0086B3; } 138 | .highlight .nc { 139 | color: #445588; 140 | font-weight: bold; } 141 | .highlight .no { 142 | color: #008080; } 143 | .highlight .ni { 144 | color: #800080; } 145 | .highlight .ne { 146 | color: #990000; 147 | font-weight: bold; } 148 | .highlight .nf { 149 | color: #990000; } 150 | .highlight .nn { 151 | color: #555555; } 152 | .highlight .nt { 153 | color: #000080; } 154 | .highlight .nv { 155 | color: #008080; } 156 | .highlight .ow { 157 | color: #000000; 158 | font-weight: bold; } 159 | .highlight .w { 160 | color: #bbbbbb; } 161 | .highlight .mf { 162 | color: #009999; } 163 | .highlight .mh { 164 | color: #009999; } 165 | .highlight .mi { 166 | color: #009999; } 167 | .highlight .mo { 168 | color: #009999; } 169 | .highlight .sb { 170 | color: #d14; } 171 | .highlight .sc { 172 | color: #d14; } 173 | .highlight .sd { 174 | color: #d14; } 175 | .highlight .s2 { 176 | color: #d14; } 177 | .highlight .se { 178 | color: #d14; } 179 | .highlight .sh { 180 | color: #d14; } 181 | .highlight .si { 182 | color: #d14; } 183 | .highlight .sx { 184 | color: #d14; } 185 | .highlight .sr { 186 | color: #009926; } 187 | .highlight .s1 { 188 | color: #d14; } 189 | .highlight .ss { 190 | color: #990073; } 191 | .highlight .bp { 192 | color: #999999; } 193 | .highlight .vc { 194 | color: #008080; } 195 | .highlight .vg { 196 | color: #008080; } 197 | .highlight .vi { 198 | color: #008080; } 199 | .highlight .il { 200 | color: #009999; } 201 | -------------------------------------------------------------------------------- /docs/docsets/Closures.docset/Contents/Resources/Documents/css/jazzy.css: -------------------------------------------------------------------------------- 1 | html, body, div, span, h1, h3, h4, p, a, code, em, img, ul, li, table, tbody, tr, td { 2 | background: transparent; 3 | border: 0; 4 | margin: 0; 5 | outline: 0; 6 | padding: 0; 7 | vertical-align: baseline; } 8 | 9 | body { 10 | background-color: #f2f2f2; 11 | font-family: Helvetica, freesans, Arial, sans-serif; 12 | font-size: 14px; 13 | -webkit-font-smoothing: subpixel-antialiased; 14 | word-wrap: break-word; } 15 | 16 | h1, h2, h3 { 17 | margin-top: 0.8em; 18 | margin-bottom: 0.3em; 19 | font-weight: 100; 20 | color: black; } 21 | 22 | h1 { 23 | font-size: 2.5em; } 24 | 25 | h2 { 26 | font-size: 2em; 27 | border-bottom: 1px solid #e2e2e2; } 28 | 29 | h4 { 30 | font-size: 13px; 31 | line-height: 1.5; 32 | margin-top: 21px; } 33 | 34 | h5 { 35 | font-size: 1.1em; } 36 | 37 | h6 { 38 | font-size: 1.1em; 39 | color: #777; } 40 | 41 | .section-name { 42 | color: gray; 43 | display: block; 44 | font-family: Helvetica; 45 | font-size: 22px; 46 | font-weight: 100; 47 | margin-bottom: 15px; } 48 | 49 | pre, code { 50 | font: 0.95em Menlo, monospace; 51 | color: #777; 52 | word-wrap: normal; } 53 | 54 | p code, li code { 55 | background-color: #eee; 56 | padding: 2px 4px; 57 | border-radius: 4px; } 58 | 59 | a { 60 | color: #0088cc; 61 | text-decoration: none; } 62 | 63 | ul { 64 | padding-left: 15px; } 65 | 66 | li { 67 | line-height: 1.8em; } 68 | 69 | img { 70 | max-width: 100%; } 71 | 72 | blockquote { 73 | margin-left: 0; 74 | padding: 0 10px; 75 | border-left: 4px solid #ccc; } 76 | 77 | .content-wrapper { 78 | margin: 0 auto; 79 | width: 980px; } 80 | 81 | header { 82 | font-size: 0.85em; 83 | line-height: 26px; 84 | background-color: #414141; 85 | position: fixed; 86 | width: 100%; 87 | z-index: 1; } 88 | header img { 89 | padding-right: 6px; 90 | vertical-align: -4px; 91 | height: 16px; } 92 | header a { 93 | color: #fff; } 94 | header p { 95 | float: left; 96 | color: #999; } 97 | header .header-right { 98 | float: right; 99 | margin-left: 16px; } 100 | 101 | #breadcrumbs { 102 | background-color: #f2f2f2; 103 | height: 27px; 104 | padding-top: 17px; 105 | position: fixed; 106 | width: 100%; 107 | z-index: 1; 108 | margin-top: 26px; } 109 | #breadcrumbs #carat { 110 | height: 10px; 111 | margin: 0 5px; } 112 | 113 | .sidebar { 114 | background-color: #f9f9f9; 115 | border: 1px solid #e2e2e2; 116 | overflow-y: auto; 117 | overflow-x: hidden; 118 | position: fixed; 119 | top: 70px; 120 | bottom: 0; 121 | width: 230px; 122 | word-wrap: normal; } 123 | 124 | .nav-groups { 125 | list-style-type: none; 126 | background: #fff; 127 | padding-left: 0; } 128 | 129 | .nav-group-name { 130 | border-bottom: 1px solid #e2e2e2; 131 | font-size: 1.1em; 132 | font-weight: 100; 133 | padding: 15px 0 15px 20px; } 134 | .nav-group-name > a { 135 | color: #333; } 136 | 137 | .nav-group-tasks { 138 | margin-top: 5px; } 139 | 140 | .nav-group-task { 141 | font-size: 0.9em; 142 | list-style-type: none; 143 | white-space: nowrap; } 144 | .nav-group-task a { 145 | color: #888; } 146 | 147 | .main-content { 148 | background-color: #fff; 149 | border: 1px solid #e2e2e2; 150 | margin-left: 246px; 151 | position: absolute; 152 | overflow: hidden; 153 | padding-bottom: 20px; 154 | top: 70px; 155 | width: 734px; } 156 | .main-content p, .main-content a, .main-content code, .main-content em, .main-content ul, .main-content table, .main-content blockquote { 157 | margin-bottom: 1em; } 158 | .main-content p { 159 | line-height: 1.8em; } 160 | .main-content section .section:first-child { 161 | margin-top: 0; 162 | padding-top: 0; } 163 | .main-content section .task-group-section .task-group:first-of-type { 164 | padding-top: 10px; } 165 | .main-content section .task-group-section .task-group:first-of-type .section-name { 166 | padding-top: 15px; } 167 | .main-content section .heading:before { 168 | content: ""; 169 | display: block; 170 | padding-top: 70px; 171 | margin: -70px 0 0; } 172 | 173 | .section { 174 | padding: 0 25px; } 175 | 176 | .highlight { 177 | background-color: #eee; 178 | padding: 10px 12px; 179 | border: 1px solid #e2e2e2; 180 | border-radius: 4px; 181 | overflow-x: auto; } 182 | 183 | .declaration .highlight { 184 | overflow-x: initial; 185 | padding: 0 40px 40px 0; 186 | margin-bottom: -25px; 187 | background-color: transparent; 188 | border: none; } 189 | 190 | .section-name { 191 | margin: 0; 192 | margin-left: 18px; } 193 | 194 | .task-group-section { 195 | padding-left: 6px; 196 | border-top: 1px solid #e2e2e2; } 197 | 198 | .task-group { 199 | padding-top: 0px; } 200 | 201 | .task-name-container a[name]:before { 202 | content: ""; 203 | display: block; 204 | padding-top: 70px; 205 | margin: -70px 0 0; } 206 | 207 | .item { 208 | padding-top: 8px; 209 | width: 100%; 210 | list-style-type: none; } 211 | .item a[name]:before { 212 | content: ""; 213 | display: block; 214 | padding-top: 70px; 215 | margin: -70px 0 0; } 216 | .item code { 217 | background-color: transparent; 218 | padding: 0; } 219 | .item .token, .item .direct-link { 220 | padding-left: 3px; 221 | margin-left: 15px; 222 | font-size: 11.9px; 223 | transition: all 300ms; } 224 | .item .token-open { 225 | margin-left: 0px; } 226 | .item .discouraged { 227 | text-decoration: line-through; } 228 | .item .declaration-note { 229 | font-size: .85em; 230 | color: gray; 231 | font-style: italic; } 232 | 233 | .pointer-container { 234 | border-bottom: 1px solid #e2e2e2; 235 | left: -23px; 236 | padding-bottom: 13px; 237 | position: relative; 238 | width: 110%; } 239 | 240 | .pointer { 241 | background: #f9f9f9; 242 | border-left: 1px solid #e2e2e2; 243 | border-top: 1px solid #e2e2e2; 244 | height: 12px; 245 | left: 21px; 246 | top: -7px; 247 | -webkit-transform: rotate(45deg); 248 | -moz-transform: rotate(45deg); 249 | -o-transform: rotate(45deg); 250 | transform: rotate(45deg); 251 | position: absolute; 252 | width: 12px; } 253 | 254 | .height-container { 255 | display: none; 256 | left: -25px; 257 | padding: 0 25px; 258 | position: relative; 259 | width: 100%; 260 | overflow: hidden; } 261 | .height-container .section { 262 | background: #f9f9f9; 263 | border-bottom: 1px solid #e2e2e2; 264 | left: -25px; 265 | position: relative; 266 | width: 100%; 267 | padding-top: 10px; 268 | padding-bottom: 5px; } 269 | 270 | .aside, .language { 271 | padding: 6px 12px; 272 | margin: 12px 0; 273 | border-left: 5px solid #dddddd; 274 | overflow-y: hidden; } 275 | .aside .aside-title, .language .aside-title { 276 | font-size: 9px; 277 | letter-spacing: 2px; 278 | text-transform: uppercase; 279 | padding-bottom: 0; 280 | margin: 0; 281 | color: #aaa; 282 | -webkit-user-select: none; } 283 | .aside p:last-child, .language p:last-child { 284 | margin-bottom: 0; } 285 | 286 | .language { 287 | border-left: 5px solid #cde9f4; } 288 | .language .aside-title { 289 | color: #4b8afb; } 290 | 291 | .aside-warning, .aside-deprecated, .aside-unavailable { 292 | border-left: 5px solid #ff6666; } 293 | .aside-warning .aside-title, .aside-deprecated .aside-title, .aside-unavailable .aside-title { 294 | color: #ff0000; } 295 | 296 | .graybox { 297 | border-collapse: collapse; 298 | width: 100%; } 299 | .graybox p { 300 | margin: 0; 301 | word-break: break-word; 302 | min-width: 50px; } 303 | .graybox td { 304 | border: 1px solid #e2e2e2; 305 | padding: 5px 25px 5px 10px; 306 | vertical-align: middle; } 307 | .graybox tr td:first-of-type { 308 | text-align: right; 309 | padding: 7px; 310 | vertical-align: top; 311 | word-break: normal; 312 | width: 40px; } 313 | 314 | .slightly-smaller { 315 | font-size: 0.9em; } 316 | 317 | #footer { 318 | position: relative; 319 | top: 10px; 320 | bottom: 0px; 321 | margin-left: 25px; } 322 | #footer p { 323 | margin: 0; 324 | color: #aaa; 325 | font-size: 0.8em; } 326 | 327 | html.dash header, html.dash #breadcrumbs, html.dash .sidebar { 328 | display: none; } 329 | html.dash .main-content { 330 | width: 980px; 331 | margin-left: 0; 332 | border: none; 333 | width: 100%; 334 | top: 0; 335 | padding-bottom: 0; } 336 | html.dash .height-container { 337 | display: block; } 338 | html.dash .item .token { 339 | margin-left: 0; } 340 | html.dash .content-wrapper { 341 | width: auto; } 342 | html.dash #footer { 343 | position: static; } 344 | -------------------------------------------------------------------------------- /docs/docsets/Closures.docset/Contents/Resources/Documents/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vhesener/Closures/d42d1cea00b05fa6778e2a0e1601028b47a32f79/docs/docsets/Closures.docset/Contents/Resources/Documents/img/carat.png -------------------------------------------------------------------------------- /docs/docsets/Closures.docset/Contents/Resources/Documents/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vhesener/Closures/d42d1cea00b05fa6778e2a0e1601028b47a32f79/docs/docsets/Closures.docset/Contents/Resources/Documents/img/dash.png -------------------------------------------------------------------------------- /docs/docsets/Closures.docset/Contents/Resources/Documents/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vhesener/Closures/d42d1cea00b05fa6778e2a0e1601028b47a32f79/docs/docsets/Closures.docset/Contents/Resources/Documents/img/gh.png -------------------------------------------------------------------------------- /docs/docsets/Closures.docset/Contents/Resources/Documents/js/jazzy.js: -------------------------------------------------------------------------------- 1 | window.jazzy = {'docset': false} 2 | if (typeof window.dash != 'undefined') { 3 | document.documentElement.className += ' dash' 4 | window.jazzy.docset = true 5 | } 6 | if (navigator.userAgent.match(/xcode/i)) { 7 | document.documentElement.className += ' xcode' 8 | window.jazzy.docset = true 9 | } 10 | 11 | function toggleItem($link, $content) { 12 | var animationDuration = 300; 13 | $link.toggleClass('token-open'); 14 | $content.slideToggle(animationDuration); 15 | } 16 | 17 | function itemLinkToContent($link) { 18 | return $link.parent().parent().next(); 19 | } 20 | 21 | // On doc load + hash-change, open any targetted item 22 | function openCurrentItemIfClosed() { 23 | if (window.jazzy.docset) { 24 | return; 25 | } 26 | var $link = $(`.token[href="${location.hash}"]`); 27 | $content = itemLinkToContent($link); 28 | if ($content.is(':hidden')) { 29 | toggleItem($link, $content); 30 | } 31 | } 32 | 33 | $(openCurrentItemIfClosed); 34 | $(window).on('hashchange', openCurrentItemIfClosed); 35 | 36 | // On item link ('token') click, toggle its discussion 37 | $('.token').on('click', function(event) { 38 | if (window.jazzy.docset) { 39 | return; 40 | } 41 | var $link = $(this); 42 | toggleItem($link, itemLinkToContent($link)); 43 | 44 | // Keeps the document from jumping to the hash. 45 | var href = $link.attr('href'); 46 | if (history.pushState) { 47 | history.pushState({}, '', href); 48 | } else { 49 | location.hash = href; 50 | } 51 | event.preventDefault(); 52 | }); 53 | 54 | // Clicks on links to the current, closed, item need to open the item 55 | $("a:not('.token')").on('click', function() { 56 | if (location == this.href) { 57 | openCurrentItemIfClosed(); 58 | } 59 | }); 60 | -------------------------------------------------------------------------------- /docs/docsets/Closures.docset/Contents/Resources/docSet.dsidx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vhesener/Closures/d42d1cea00b05fa6778e2a0e1601028b47a32f79/docs/docsets/Closures.docset/Contents/Resources/docSet.dsidx -------------------------------------------------------------------------------- /docs/docsets/Closures.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vhesener/Closures/d42d1cea00b05fa6778e2a0e1601028b47a32f79/docs/docsets/Closures.tgz -------------------------------------------------------------------------------- /docs/img/carat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vhesener/Closures/d42d1cea00b05fa6778e2a0e1601028b47a32f79/docs/img/carat.png -------------------------------------------------------------------------------- /docs/img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vhesener/Closures/d42d1cea00b05fa6778e2a0e1601028b47a32f79/docs/img/dash.png -------------------------------------------------------------------------------- /docs/img/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vhesener/Closures/d42d1cea00b05fa6778e2a0e1601028b47a32f79/docs/img/gh.png -------------------------------------------------------------------------------- /docs/js/jazzy.js: -------------------------------------------------------------------------------- 1 | window.jazzy = {'docset': false} 2 | if (typeof window.dash != 'undefined') { 3 | document.documentElement.className += ' dash' 4 | window.jazzy.docset = true 5 | } 6 | if (navigator.userAgent.match(/xcode/i)) { 7 | document.documentElement.className += ' xcode' 8 | window.jazzy.docset = true 9 | } 10 | 11 | function toggleItem($link, $content) { 12 | var animationDuration = 300; 13 | $link.toggleClass('token-open'); 14 | $content.slideToggle(animationDuration); 15 | } 16 | 17 | function itemLinkToContent($link) { 18 | return $link.parent().parent().next(); 19 | } 20 | 21 | // On doc load + hash-change, open any targetted item 22 | function openCurrentItemIfClosed() { 23 | if (window.jazzy.docset) { 24 | return; 25 | } 26 | var $link = $(`.token[href="${location.hash}"]`); 27 | $content = itemLinkToContent($link); 28 | if ($content.is(':hidden')) { 29 | toggleItem($link, $content); 30 | } 31 | } 32 | 33 | $(openCurrentItemIfClosed); 34 | $(window).on('hashchange', openCurrentItemIfClosed); 35 | 36 | // On item link ('token') click, toggle its discussion 37 | $('.token').on('click', function(event) { 38 | if (window.jazzy.docset) { 39 | return; 40 | } 41 | var $link = $(this); 42 | toggleItem($link, itemLinkToContent($link)); 43 | 44 | // Keeps the document from jumping to the hash. 45 | var href = $link.attr('href'); 46 | if (history.pushState) { 47 | history.pushState({}, '', href); 48 | } else { 49 | location.hash = href; 50 | } 51 | event.preventDefault(); 52 | }); 53 | 54 | // Clicks on links to the current, closed, item need to open the item 55 | $("a:not('.token')").on('click', function() { 56 | if (location == this.href) { 57 | openCurrentItemIfClosed(); 58 | } 59 | }); 60 | --------------------------------------------------------------------------------