-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit b5eb0fa
Showing
12 changed files
with
520 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
.DS_Store | ||
sourcekitten-output.json | ||
docs/ | ||
/.build | ||
/Packages | ||
/*.xcodeproj | ||
**/xcuserdata | ||
**/xcshareddata | ||
Pods/ | ||
Carthage/ | ||
Examples/**/Podfile.lock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
Pod::Spec.new do |s| | ||
s.name = "AlertReactor" | ||
s.version = "0.1.0" | ||
s.summary = "ReactorKit extension for UIAlertController" | ||
s.homepage = "https://github.com/devxoul/AlertReactor" | ||
s.license = { :type => "MIT", :file => "LICENSE" } | ||
s.author = { "Suyeol Jeon" => "devxoul@gmail.com" } | ||
s.source = { :git => "https://github.com/devxoul/AlertReactor.git", | ||
:tag => s.version.to_s } | ||
s.source_files = "Sources/**/*.{swift,h,m}" | ||
s.frameworks = "Foundation" | ||
s.dependency "ReactorKit" | ||
|
||
s.ios.deployment_target = "8.0" | ||
s.osx.deployment_target = "10.11" | ||
s.tvos.deployment_target = "9.0" | ||
s.watchos.deployment_target = "2.0" | ||
|
||
s.pod_target_xcconfig = { | ||
"SWIFT_VERSION" => "3.1" | ||
} | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
The MIT License (MIT) | ||
|
||
Copyright (c) 2017 Suyeol Jeon (xoul.kr) | ||
|
||
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
// swift-tools-version:3.1 | ||
|
||
import Foundation | ||
import PackageDescription | ||
|
||
var dependencies: [Package.Dependency] = [ | ||
.Package(url: "https://github.com/ReactiveX/RxSwift.git", majorVersion: 3), | ||
.Package(url: "https://github.com/ReactorKit/ReactorKit.git", majorVersion: 0), | ||
] | ||
|
||
let isTest = ProcessInfo.processInfo.environment["TEST"] == "1" | ||
if isTest { | ||
dependencies.append( | ||
.Package(url: "https://github.com/devxoul/RxExpect.git", majorVersion: 0) | ||
) | ||
} | ||
|
||
let package = Package( | ||
name: "AlertReactor", | ||
dependencies: dependencies | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
# AlertReactor | ||
|
||
![Swift](https://img.shields.io/badge/Swift-3.1-orange.svg) | ||
[![CocoaPods](http://img.shields.io/cocoapods/v/AlertReactor.svg)](https://cocoapods.org/pods/AlertReactor) | ||
[![Build Status](https://travis-ci.org/devxoul/AlertReactor.svg?branch=master)](https://travis-ci.org/devxoul/AlertReactor) | ||
[![Codecov](https://img.shields.io/codecov/c/github/devxoul/AlertReactor.svg)](https://codecov.io/gh/devxoul/AlertReactor) | ||
|
||
ReactorKit extension for UIAlertController. It provides an elegant way to deal with an UIAlertController. Best fits for lazy-loaded alert actions. | ||
|
||
## Features | ||
|
||
* Statically typed alert actions | ||
* Reactive and dynamic action bindings | ||
|
||
## At a Glance | ||
|
||
With AlertReactor, you can write a reactive code for alert controller. The code below displays an action sheet when `menuButton` is tapped. When an user selects an item in the action sheet, the selected menu item is converted into an action which is binded to a reactor. | ||
|
||
```swift | ||
// Menu Button -> Action Sheet -> Reactor Action | ||
menuButton.rx.tap | ||
.flatMap { [weak self] _ -> Observable<UserAlertAction> in | ||
let reactor = UserAlertReactor() | ||
let controller = AlertController<UserAlertAction>(reactor: reactor, preferredStyle: .actionSheet) | ||
self?.present(controller, animated: true, completion: nil) | ||
return controller.rx.actionSelected.asObservable() | ||
} | ||
.map { alertAction -> Reactor.Action? in | ||
switch action { | ||
case .follow: return .followUser | ||
case .unfollow: return .unfollowUser | ||
case .block: return .blockUser | ||
case .cancel: return nil | ||
} | ||
} | ||
.filterNil() | ||
.bind(to: reactor.action) | ||
``` | ||
|
||
## Getting Started | ||
|
||
### 1. Defining an Alert Action | ||
|
||
AlertReactor provides a `AlertActionType ` protocol. This is an abstraction model of `UIAlertAction`. Create a new type conforming this protocol. This protocol requires a `title` and `style` property. | ||
|
||
```swift | ||
enum UserAlertAction: AlertActionType { | ||
case follow | ||
case unfollow | ||
case block | ||
case cancel | ||
|
||
// required | ||
var title: String { | ||
case follow: return "Follow" | ||
case unfollow: return "Unfollow" | ||
case block: return "Block" | ||
case cancel: return "Cancel" | ||
} | ||
|
||
// optional | ||
var style: UIAlertActionStyle { | ||
case follow: return .default | ||
case unfollow: return .default | ||
case block: return .destructive | ||
case cancel: return .cancel | ||
} | ||
} | ||
``` | ||
|
||
|
||
### 2. Creating an Alert Reactor | ||
|
||
`AlertReactor` is a reactor class. It has an action, mutation and state: | ||
|
||
```swift | ||
enum Action { | ||
case prepare | ||
} | ||
|
||
enum Mutation { | ||
case setTitle(String?) | ||
case setMessage(String?) | ||
case setActions([AlertAction]) | ||
} | ||
|
||
struct State { | ||
public var title: String? | ||
public var message: String? | ||
public var actions: [AlertAction] | ||
} | ||
``` | ||
|
||
Override this class to implement `mutate(action:)` method so that the reactor can emit mutations to change the state. Here is an example of lazy-loaded actions. | ||
|
||
```swift | ||
final class UserAlertReactor: AlertReactor<UserAlertAction> { | ||
let userID: Int | ||
|
||
init(userID: Int) { | ||
self.userID = userID | ||
} | ||
|
||
override func mutate(action: Action) -> Observable<Mutation> { | ||
return Observable.concat([ | ||
// Initial actions | ||
Observable.just(Mutation.setTitle("Loading...")), | ||
Observable.just(Mutation.setActions([.block, .cancel])), | ||
|
||
// Call API to choose follow or unfollow | ||
api.isFollowing(userID: userID) | ||
.map { isFollowing -> Mutation in | ||
if isFollowing { | ||
return Mutation.setActions([.unfollow, .block, .cancel]) | ||
} else { | ||
return Mutation.setActions([.follow, .block, .cancel]) | ||
} | ||
} | ||
Observable.just(Mutation.setTitle(nil)), | ||
]) | ||
} | ||
} | ||
``` | ||
|
||
### 3. Using with Alert Controller | ||
|
||
A generic class `AlertController` is provided. You can create it with some parameters: `reactor` and `preferredStyle`. There's also a `actionSelected` control event property in a reactive extension. | ||
|
||
```swift | ||
let reactor = UserAlertReactor(userID: 12345) | ||
let controller = AlertController<UserAlertAction>(reactor: reactor, preferredStyle: .actionSheet) | ||
controller.rx.actionSelected | ||
.subscribe(onNext: { action in | ||
switch action { | ||
case .follow: print("Follow user") | ||
case .unfollow: print("Unfollow user") | ||
case .block: print("Block user") | ||
case .cancel: print("Cancel") | ||
} | ||
}) | ||
``` | ||
|
||
## Installation | ||
|
||
```ruby | ||
pod 'AlertReactor' | ||
``` | ||
|
||
## License | ||
|
||
AlertReactor is under MIT license. See the [LICENSE](LICENSE) for more info. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import UIKit | ||
|
||
public protocol AlertActionType: Equatable { | ||
var title: String { get } | ||
var style: UIAlertActionStyle { get } | ||
} | ||
|
||
public extension AlertActionType { | ||
var style: UIAlertActionStyle { | ||
return .default | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import UIKit | ||
|
||
import ReactorKit | ||
import RxCocoa | ||
import RxSwift | ||
|
||
|
||
// MARK: - AlertControllerType | ||
|
||
public protocol AlertControllerType: class { | ||
associatedtype AlertAction: AlertActionType | ||
var _actionSelectedSubject: PublishSubject<AlertAction> { get } | ||
func setValue(_ value: Any?, forKey key: String) | ||
} | ||
|
||
|
||
// MARK: - AlertController | ||
|
||
open class AlertController<A: AlertActionType>: UIAlertController, AlertControllerType, View { | ||
public typealias AlertAction = A | ||
|
||
|
||
// MARK: Properties | ||
|
||
open var disposeBag = DisposeBag() | ||
public private(set) lazy var _actionSelectedSubject: PublishSubject<AlertAction> = .init() | ||
|
||
private var _preferredStyle: UIAlertControllerStyle | ||
open override var preferredStyle: UIAlertControllerStyle { | ||
get { return self._preferredStyle } | ||
set { self._preferredStyle = newValue } | ||
} | ||
|
||
|
||
// MARK: Initializing | ||
|
||
public init(reactor: AlertReactor<AlertAction>? = nil, preferredStyle: UIAlertControllerStyle = .alert) { | ||
self._preferredStyle = preferredStyle | ||
super.init(nibName: nil, bundle: nil) | ||
self.title = "" | ||
self.reactor = reactor | ||
} | ||
|
||
public required init?(coder aDecoder: NSCoder) { | ||
fatalError("init(coder:) has not been implemented") | ||
} | ||
|
||
|
||
// MARK: Binding | ||
|
||
open func bind(reactor: AlertReactor<AlertAction>) { | ||
// Action | ||
self.rx.methodInvoked(#selector(UIViewController.viewDidLoad)) | ||
.map { _ in Reactor.Action.prepare } | ||
.bind(to: reactor.action) | ||
.disposed(by: self.disposeBag) | ||
|
||
// State | ||
reactor.state.map { $0.title } | ||
.distinctUntilChanged { $0 == $1 } | ||
.subscribe(onNext: { [weak self] title in | ||
self?.title = title | ||
}) | ||
.disposed(by: self.disposeBag) | ||
|
||
reactor.state.map { $0.message } | ||
.distinctUntilChanged { $0 == $1 } | ||
.subscribe(onNext: { [weak self] message in | ||
self?.message = message | ||
}) | ||
.disposed(by: self.disposeBag) | ||
|
||
reactor.state.map { $0.actions } | ||
.distinctUntilChanged { old, new in | ||
old.elementsEqual(new) { $0.title == $1.title && $0.style == $1.style } | ||
} | ||
.bind(to: self.rx.actions) | ||
.disposed(by: self.disposeBag) | ||
} | ||
} | ||
|
||
|
||
// MARK: - Reactive Extension | ||
|
||
extension Reactive where Base: AlertControllerType { | ||
var actions: UIBindingObserver<Base, [Base.AlertAction]> { | ||
return UIBindingObserver(UIElement: self.base) { alertController, actions in | ||
let alertActions = actions.map { action in | ||
UIAlertAction(title: action.title, style: action.style) { [weak base = self.base] _ in | ||
base?._actionSelectedSubject.onNext(action) | ||
} | ||
} | ||
alertController.setValue(alertActions, forKey: "actions") | ||
} | ||
} | ||
|
||
var actionSelected: ControlEvent<Base.AlertAction> { | ||
let source = self.base._actionSelectedSubject.asObservable() | ||
return ControlEvent(events: source) | ||
} | ||
} |
Oops, something went wrong.