Skip to content

Commit

Permalink
Version 0.3
Browse files Browse the repository at this point in the history
- UIBarButtonItem
- Documentation typos
  • Loading branch information
vhesener authored Oct 24, 2017
1 parent 69b0930 commit 107b6e3
Show file tree
Hide file tree
Showing 86 changed files with 1,762 additions and 100 deletions.
30 changes: 30 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
The following rules generally apply for PRs and code changes:

**Submit Pull Requests to the `develop` branch**

This is where all experimental and other beta-ish features will go.
`develop` will collect many new features until a release is planned. At a point where a stable release is ready,
`develop` branch will then be merged into `master` and a release tag will be generated for the general public.

**Markdown documentation and header-level comment changes need to run Jazzy**

If making a change to the documentation or changes inside of method/property quick look comments,
[Jazzy](https://github.com/realm/jazzy) needs to be run. Please install Jazzy and run the following
Terminal command from the Supporting/jazzy directory:

`./generate_docs.sh`

**Blend with existing patterns**

If, for instance, you are contributing by adding another
[Closure API](https://github.com/vhesener/Closures/issues?q=is%3Aissue+is%3Aopen+label%3A%22Closure+API+Request%22)
and that API has a precedent for implementation, it is best to mimic the existing precedent's pattern.
If however, you think both the new API and it's counterparts could use improvements, let's definitely
discuss how to update all of the existing APIs as well.

Let's take a simple example of adding a new API for a delegate protocol. The following is almost universal:

- [x] Use the Delegate/Delegator wrapper mechanism used by other delegate APIs
- [x] Unit tests to make sure all delegate methods are covered
- [x] Documentation on any public initializers, methods, or properties
- [x] Playground example showing how to use it, along with explanations
26 changes: 13 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,17 +64,17 @@ pickerView.addStrings(myStrings) { title, component, row in
***
### **Daisy Chaining**

Almost all convenience methods allow for the use of [daisy chaining](https://en.wikipedia.org/wiki/Method_chaining). This allows us to have some nice syntax sugar while implementing optional delegate methods in a consise way. Using [UITextField](https://vhesener.github.io/Closures/Extensions/UITextField.html) as an example, we can organize and visualize all of the `UITextFieldDelegate` behavior.
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.

```swift
textField
.didBeginEditing {
// UITextField did begin editing code
}.shouldClear {
.didBeginEditing {
// UITextField did begin editing code
}.shouldClear {
true
}.shouldChangeCharacters { range, string in
// some custom character change code
return false
}.shouldChangeCharacters { range, string in
// some custom character change code
return false
}
```
***
Expand Down Expand Up @@ -113,18 +113,18 @@ These two [UIImagePickerController](https://vhesener.github.io/Closures/Extensio

```swift
UIImagePickerController(source: .camera, allow: .image) { result, picker in
myImageView.image = result.editedImage
myImageView.image = result.editedImage
}.present(from: self)
```
```swift
let pickerController = UIImagePickerController()
pickerController.sourceType = .camera
pickerController.mediaTypes = [kUTTypeImage]
pickerController.didFinishPickingMedia { [weak self] info in
myImageView.image = info[UIImagePickerControllerEditedImage] as? UIImage
self?.dismiss(animated: true)
myImageView.image = info[UIImagePickerControllerEditedImage] as? UIImage
self?.dismiss(animated: true)
}.didCancel { [weak self] in
self?.dismiss(animated: true)
self?.dismiss(animated: true)
}
self.present(pickerController, animated: true)
```
Expand Down Expand Up @@ -184,7 +184,7 @@ constraints:
There are many reasons for this, but mostly because
* It was arbitrarily challenging.
* It was in the spirit of Swift.
* Create a scalable mechanism to easily add addtional closure wrappers in the future.
* Create a scalable mechanism to easily add additional closure wrappers in the future.

It is our goal to become irrelevant via [sherlock](http://www.urbandictionary.com/define.php?term=sherlocked).
In addition to not having to support this library anymore, it would actually be flattering
Expand Down Expand Up @@ -219,4 +219,4 @@ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPO
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
```
```
1 change: 1 addition & 0 deletions Supporting/cocoapods/.swift_version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
4.0
23 changes: 23 additions & 0 deletions Supporting/cocoapods/Closures.podspec.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "Closures",
"version": "0.3",
"summary": "Swifty closures for UIKit and Foundation",
"homepage": "https://github.com/vhesener/Closures",
"screenshots": [
"https://raw.githubusercontent.com/vhesener/Closures/assets/assets/playground_general.gif",
"https://raw.githubusercontent.com/vhesener/Closures/assets/assets/reference_large.png"
],
"license": "MIT",
"authors": "Vinnie Hesener",
"platforms": {
"ios": "9.0"
},
"source": {
"git": "https://github.com/vhesener/Closures.git",
"tag": "v0.3"
},
"swift_version": "4.0",
"source_files": "Xcode/Closures/Source",
"documentation_url": "https://vhesener.github.io/Closures/",
"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."
}
23 changes: 23 additions & 0 deletions Supporting/jazzy/abstracts/UIBarButtonItem.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
`Closures` framework adds closures for `UIBarButtonItem` tap events, usually
found in a UINavigationBar.

All initializers that support the target-action pattern now have an equivalent
initialier that contains a `handler` parameter.

```swift
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Left item", style: .plain) {
// left bar button item tapped
}
```

To add the closure handler to an existing `UIBarButtonItem`, simply call the
`onTap(handler:)` method. For instance, if you created your button
in a storyboard, you could call the following in your `viewDidLoad` method.

```swift
let myRightBarButton = navigationItem.rightBarButtonItem!
myRightBarButton.onTap {
// right bar button item tapped
}
```

7 changes: 7 additions & 0 deletions Supporting/jazzy/generate_docs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
jazzy --config jazzy.yml;

## Have to create this exception in order to have underscores in a class name
## display in the github documentation generator
echo "include:
- \"_5FKeyValueCodingAndObserving.html\"
" > ../../docs/_config.yml
4 changes: 3 additions & 1 deletion Supporting/jazzy/jazzy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ hide_documentation_coverage: true
use_safe_filenames: true
readme: "../../README.md"
module: Closures
abstract: "../Supporting/jazzy/abstracts/*.md"
abstract: "abstracts/*.md"
source_directory: "../../Xcode"
custom_categories:
- name: Controls
children:
Expand All @@ -24,6 +25,7 @@ custom_categories:
- UIStepper
- UISlider
- UIControl
- UIBarButtonItem
- name: Scrolling Views
children:
- UITableView
Expand Down
8 changes: 8 additions & 0 deletions Xcode/Closures.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
3F7620EF1D849B5000E17BF5 /* Closures.h in Headers */ = {isa = PBXBuildFile; fileRef = 3F7620E11D849B5000E17BF5 /* Closures.h */; settings = {ATTRIBUTES = (Public, ); }; };
3FB42D941F01A97700740CB0 /* UIImagePickerControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FB42D931F01A97700740CB0 /* UIImagePickerControllerTests.swift */; };
3FB77F311EE4DB5200C60F5C /* UITableViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FB77F301EE4DB5200C60F5C /* UITableViewTests.swift */; };
3FD393C61F9E583900DA155D /* UIBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FD393C51F9E583900DA155D /* UIBarButtonItem.swift */; };
3FD393C81F9E61A900DA155D /* UIBarButtonItemTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FD393C71F9E61A900DA155D /* UIBarButtonItemTests.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -59,6 +61,8 @@
3F7620EE1D849B5000E17BF5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
3FB42D931F01A97700740CB0 /* UIImagePickerControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImagePickerControllerTests.swift; sourceTree = "<group>"; };
3FB77F301EE4DB5200C60F5C /* UITableViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITableViewTests.swift; sourceTree = "<group>"; };
3FD393C51F9E583900DA155D /* UIBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIBarButtonItem.swift; sourceTree = "<group>"; };
3FD393C71F9E61A900DA155D /* UIBarButtonItemTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIBarButtonItemTests.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -88,6 +92,7 @@
3F59C56A1F1C32AA00945666 /* UICollectionView.swift */,
3F59C56B1F1C32AA00945666 /* UIControl.swift */,
3F59C56C1F1C32AA00945666 /* UIGestureRecognizer.swift */,
3FD393C51F9E583900DA155D /* UIBarButtonItem.swift */,
3F59C56D1F1C32AA00945666 /* UIImagePickerController.swift */,
3F59C56E1F1C32AA00945666 /* UIPickerView.swift */,
3F59C56F1F1C32AA00945666 /* UIScrollView.swift */,
Expand Down Expand Up @@ -127,6 +132,7 @@
3F7620EB1D849B5000E17BF5 /* ClosuresTests */ = {
isa = PBXGroup;
children = (
3FD393C71F9E61A900DA155D /* UIBarButtonItemTests.swift */,
3FB42D931F01A97700740CB0 /* UIImagePickerControllerTests.swift */,
3F55FC3C1EFD6D5E001ED560 /* UIPickerViewTests.swift */,
3F1F3D211EF8E54300A62BF2 /* UIGestureRecognizerTests.swift */,
Expand Down Expand Up @@ -257,6 +263,7 @@
3F59C5751F1C32BF00945666 /* UIGestureRecognizer.swift in Sources */,
3F59C5711F1C32BF00945666 /* Core.swift in Sources */,
3F59C5791F1C32BF00945666 /* UITableView.swift in Sources */,
3FD393C61F9E583900DA155D /* UIBarButtonItem.swift in Sources */,
3F59C5781F1C32BF00945666 /* UIScrollView.swift in Sources */,
3F59C5761F1C32BF00945666 /* UIImagePickerController.swift in Sources */,
3F59C5771F1C32BF00945666 /* UIPickerView.swift in Sources */,
Expand All @@ -273,6 +280,7 @@
3F1F3D221EF8E54300A62BF2 /* UIGestureRecognizerTests.swift in Sources */,
3F7620ED1D849B5000E17BF5 /* UIControlTests.swift in Sources */,
3FB77F311EE4DB5200C60F5C /* UITableViewTests.swift in Sources */,
3FD393C81F9E61A900DA155D /* UIBarButtonItemTests.swift in Sources */,
3F5B9E411F0003C500B8555C /* KVOTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
18 changes: 17 additions & 1 deletion Xcode/Closures/Source/Core.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,28 @@ extension NotificationCenter {
func selfObserve<T>(name: Notification.Name,
target: T,
closure: @escaping (_ target: T, _ userInfo: [AnyHashable : Any]?) -> Void) where T: AnyObject {

// Post a cleanup notification to remove any duplicates
let cleanupKey = "com.vhesener.notificationkey.selfobserved.cleanup"
post(name: name, object: target, userInfo: [cleanupKey: target])

var observer: NSObjectProtocol?
observer = addObserver(
forName: name,
// Can't use the object for this parameter. Since the object
// is the one sending the post, it will never clean up. The observer
// will always stay in the notification center and I'm not sure of
// the concequences of that yet.
object: nil,
queue: nil) { [weak target, weak self] in
// Cleanup opportunity for this notification name.
// Cleanup any notification with this name;target combo
if let cleanupTarget = $0.userInfo?[cleanupKey] as? T {
if cleanupTarget === target,
$0.name == name {
self?.removeObserver(observer!)
}
return
}
// Remove if target is nil (target-action on self is fruitless)
guard let target = target else {
self?.removeObserver(observer!)
Expand Down
118 changes: 118 additions & 0 deletions Xcode/Closures/Source/UIBarButtonItem.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/**
The MIT License (MIT)
Copyright (c) 2017 Vincent Hesener
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute,
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

import UIKit

/// :nodoc:
private let jzyBug = 0 // Prevent the license header from showing up in Jazzy Docs for UICollectionView

public extension UIBarButtonItem {
/**
A convenience initializer for a UIBarButtonItem so that the tap event can
be handled with a closure. This is equivalent of using the init(image:style:target:action:)
initializer.
* parameter image: The image to use for the button
* parameter style: The `UIBarButtonItemStyle` of the button
* parameter handler: The closure that is called when the button is tapped
*/
public convenience init(image: UIImage?, style: UIBarButtonItemStyle, handler: @escaping () -> Void) {
self.init(image: image, style: style, target: nil, action: nil)
onTap(handler: handler)
}

/**
A convenience initializer for a UIBarButtonItem so that the tap event can
be handled with a closure. This is equivalent of using the init(image:landscapeImagePhone:style:target:action:)
initializer.
* parameter image: The image to use for the button
* parameter landscapeImagePhone: The image to use for the compressed landscape bar item
* parameter style: The `UIBarButtonItemStyle` of the button
* parameter handler: The closure that is called when the button is tapped
*/
@available(iOS 5.0, *)
public convenience init(image: UIImage?, landscapeImagePhone: UIImage?, style: UIBarButtonItemStyle, handler: @escaping () -> Void) {
self.init(image: image, landscapeImagePhone: landscapeImagePhone, style: style, target: nil, action: nil)
onTap(handler: handler)
}

/**
A convenience initializer for a UIBarButtonItem so that the tap event can
be handled with a closure. This is equivalent of using the init(title:style:target:action:)
initializer.
* parameter title: The text to use for the button
* parameter style: The `UIBarButtonItemStyle` of the button
* parameter handler: The closure that is called when the button is tapped
*/
public convenience init(title: String?, style: UIBarButtonItemStyle, handler: @escaping () -> Void) {
self.init(title: title, style: style, target: nil, action: nil)
onTap(handler: handler)
}

/**
A convenience initializer for a UIBarButtonItem so that the tap event can
be handled with a closure. This is equivalent of using the init(barButtonSystemItem:target:action:)
initializer.
* parameter barButtonSystemItem: The `UIBarButtonSystemItem` to be used for the button
* parameter handler: The closure that is called when the button is tapped
*/
public convenience init(barButtonSystemItem systemItem: UIBarButtonSystemItem, handler: @escaping () -> Void) {
self.init(barButtonSystemItem: systemItem, target: nil, action: nil)
onTap(handler: handler)
}

/**
This method is a convenience method to add a closure handler to a `UIBarButtonItem`.
Use this method if you are creating a `UIBarButtonItem` using an initializer
other than the convience ones provide, or if the item was created by a
Storyboard or xib.
* * * *
#### An example that adds a closure handler to an existing `UIBarButtonItem`:
```swift
myBarButtonItem.onTap {
// bar button tapped code
}
```
* parameter handler: The closure that will be called when the tap is recognized.
*/
public func onTap(handler: @escaping () -> Void) {
target = self
action = #selector(UIBarButtonItem.buttonTapped)
NotificationCenter.selfObserve(name: .barButtonItemTapped, target: self) { button, userInfo in
handler()
}
}
}

fileprivate extension UIBarButtonItem {
@objc func buttonTapped() {
NotificationCenter.closures.post(name: .barButtonItemTapped, object: self)
}
}

fileprivate extension Notification.Name {
static let barButtonItemTapped = Notification.Name("UIBarButtonItem.tapped")
}
Loading

0 comments on commit 107b6e3

Please sign in to comment.