//: Playground - noun: a place where people can play
import UIKit
import XCTest
protocol DataService {}
protocol APIService {}
protocol Cache {}
protocol HomeViewModel {}
protocol Action {}
protocol State {}
protocol Result {}
enum MVC {
// Code Sample for Conductor
final class HomeViewController: UIViewController {
@IBOutlet var headerView: UIView!
@IBOutlet var signInButton: UIButton!
@IBOutlet var signOutButton: UIButton!
// ...
var dataService: DataService!
var apiService: APIService!
var cache: Cache!
// ...
@IBAction func signIn() {
// ...
}
// ...
}
}
enum MVVM {
// Code Sample for Bridge
final class HomeViewController: UIViewController {
@IBOutlet var headerView: UIView!
@IBOutlet var signInButton: UIButton!
@IBOutlet var signOutButton: UIButton!
// ...
var viewModel: HomeViewModel!
}
}
enum Flux {
struct Subscriber {
func newState(_ state: State) {}
}
struct State {
var currentUser: User?
}
struct User {
let name: String
}
enum Action {
case signIn(User)
case signOut
}
final class Store {
var state: State
var subscribers: [Subscriber] = []
init(initialState: State) {
state = initialState
}
func dispatch(action: Action) {
// update state ...
subscribers.forEach { $0.newState(state) }
}
func subscribe(view: Subscriber) {
subscribers.append(view)
}
}
static func reduce(_ state: State, _ action: Action) -> State {
var state = state
switch action {
case .signOut:
state.currentUser = nil
case .signIn(let user):
state.currentUser = user
}
return state
}
class ReduceTests: XCTestCase {
func testSignIn() {
let state = State(currentUser: nil)
let user = User(name: "Bob")
let expected = State(currentUser: user)
let newState = Flux.reduce(state, Action.signIn(user))
XCTAssertEqual(expected, newState)
}
func testSignOut() {
let user = User(name: "Bob")
let state = State(currentUser: user)
let expected = State(currentUser: nil)
let newState = Flux.reduce(state, Action.signOut)
XCTAssertEqual(expected, newState)
}
}
}
extension Flux.State: Equatable {
static func ==(lhs: Flux.State, rhs: Flux.State) -> Bool {
return true
}
}
var actionStream: [Action]
extension Sequence {
func reduce<Result>(
_ initialResult: Result,
_ nextPartialResult: (Result, Self.Iterator.Element) -> Result
) -> Result
{
return initialResult
}
func reduce<Result>(
_ initialResult: Result,
_ nextPartialResult: (Result, Action) -> Result
) -> Result
{
return initialResult
}
typealias State = Result
func reduce(
_ initialResult: State,
_ nextPartialResult: (State, Action) -> State
) -> State
{
return initialResult
}
}
let a = "hi there"
let b = 1
#if DEBUG
#endif
/// RESWIFT SLIDES
protocol StateType {}
protocol Reducer {}
struct Store<T> {
func subscribe(_ any: Any) {
}
func unsubscribe(_ any: Any) {
}
func dispatch(_ any: Any) {
}
}
protocol StoreSubscriber {}
enum ReSwift {
struct User {
let name: String
}
struct AppState: StateType {
var user: User?
}
static let store = Store<AppState>()
enum AuthenticationAction: Action {
case signIn(User)
case signOut
}
struct AppReducer: Reducer {
func handleAction(action: Action, state: AppState?) -> AppState {
let state = state ?? AppState()
return AppState(
user: userReducer(action: action, state: state)
)
}
func userReducer(action: Action, state: AppState) -> User? {
guard let action = action as? AuthenticationAction else {
return state.user
}
switch action {
case .signIn(let user):
return user
case .signOut:
return nil
}
}
}
final class HomeViewController: UIViewController, StoreSubscriber {
var store: Store<AppState>!
override func viewWillAppear(_ animated: Bool) {
store.subscribe(self)
}
override func viewWillDisappear(_ animated: Bool) {
store.unsubscribe(self)
}
func newState(state: AppState) {
// render state to view
}
@IBAction func signOut(sender: AnyObject) {
store.dispatch(AuthenticationAction.signOut)
}
}
}
class ReduceTests: XCTestCase {
let user = User(name: "Bob")
let signedOut = State(currentUser: nil)
let signedIn = State(currentUser: User(name: "Bob"))
func testSignIn() {
let newState = Flux.reduce(signedOut, Action.signIn(user))
XCTAssertEqual(signedIn, newState)
}
func testSignOut() {
let newState = Flux.reduce(signedIn, Action.signOut)
XCTAssertEqual(signedOut, newState)
}
}
static let store = Store<AppState>(reducer: AppReducer(), state: nil)
// FIND ME
func signIn(credential: Credential) {
store.dispatch(SignInFormAction.request)
api.signIn(with: credential) { [store] result in
DispatchQueue.main.async {
switch result {
case .success(let user):
store.dispatch(SignInFormAction.handleSuccess)
store.dispatch(AuthenticationAction.signIn(user))
case .error(let error):
store.dispatch(SignInFormAction.handleError(error))
}
}
}
}
// EXAMPLE SIGN IN SLIDES
enum SignInExample {
struct AppState: StateType {
var user: User?
var signInForm = SignInState()
}
struct User {
let name: String
}
struct SignInState {
var isSigningIn = false
var serverError: String?
var email: String?
var emailEditedOnce = false
var password: String?
var passwordEditedOnce = false
}
}
extension SignInExample.SignInState {
var isEmailValid: Bool {
return Validation.isValid(email: email ?? "")
}
var isPasswordValid: Bool {
return Validation.isValid(password: password ?? "")
}
var isFormValid: Bool {
return isEmailValid && isPasswordValid
}
}
class SignInViewController: UIViewController, StoreSubscriber {
@IBOutlet var serverErrorView: UIView!
@IBOutlet var serverErrorLabel: UILabel!
@IBOutlet var emailField: UITextField!
@IBOutlet var emailErrorLabel: UILabel!
@IBOutlet var passwordField: UITextField!
@IBOutlet var passwordErrorLabel: UILabel!
@IBOutlet var signInButton: UIButton!
@IBOutlet var activityIndicator: UIActivityIndicatorView!
func newState(state: SignInExample.AppState) {
let state = state.signInForm
serverErrorLabel.text = state.serverError
serverErrorView.isHidden = state.serverError == nil
emailField.text = state.email
emailField.isEnabled = !state.isSigningIn
emailErrorLabel.isHidden = !state.emailEditedOnce || state.isEmailValid
passwordField.text = state.password
passwordField.isEnabled = !state.isSigningIn
passwordErrorLabel.isHidden = !state.passwordEditedOnce || state.isPasswordValid
signInButton.isEnabled = state.isFormValid
signInButton.isHidden = state.isSigningIn
activityIndicator.isHidden = !state.isSigningIn
}
}
enum SignInFormAction: Action {
case requested
case success
case error(Error)
case reset
case emailEdited
case emailUpdated(String)
case passwordEdited
case passwordUpdated(String)
}
enum SignInFormAction: Action {
case request
case handleSuccess
case handleError(Error)
case reset
case touchEmail
case updateEmail(String)
case touchPassword
case updatePassword(String)
}
func signInReducer(action: Action, state: SignInState) -> SignInState {
guard let action = action as? SignInFormAction else {
return state
}
var state = state
switch action {
case .request:
state.isSigningIn = true
case .handleSuccess:
state.isSigningIn = false
case .handleError(let error):
state.isSigningIn = false
state.serverError = error.localizedDescription
case .touchEmail:
state.emailEditedOnce = true
case .updateEmail(let email):
state.email = email
case .touchPassword:
state.passwordEditedOnce = true
case .updatePassword(let password):
state.password = password
}
return state
}
func createActions() {
store.dispatch(StoplightAction.turnGreen)
}
func createActionsAsync() {
store.dispatch(StoplightAction.turnYellow)
let when = DispatchTime.now() + 4
DispatchQueue.main.asyncAfter(deadline: when) {
self.store.dispatch(StoplightAction.turnRed)
}
}
enum Managed {
class Contact: NSManagedObject {
var name: String!
var phone: String!
}
}
struct Contact {
let id: URL
let name: String
let phone: String
init(contact: Managed.Contact) {
id = contact.objectID.uriRepresentation()
name = contact.name
phone = contact.phone
}
}
struct AppState {}
enum ContactAction: Action {
case load([Contact])
case error(Error)
}
class CoreDataExample {
var store: Store<AppState>!
var context: NSManagedObjectContext!
func loadContacts() {
let fetchRequest = NSFetchRequest<Managed.Contact>(entityName: "Contact")
do {
let contacts = try fetchRequest.execute()
store.dispatch(ContactAction.load(contacts.map(Contact.init)))
} catch {
store.dispatch(ContactAction.error(error))
}
}
}
contacts.count
// => 10_000_000
enum Loading<T> {
case loading
case loaded(T)
case error(Error)
}
enum TypeSafeExample {
struct SignInForm {}
struct User {}
enum AppState {
case unauthenticated(SignInForm)
case authenticated(AuthenticatedState)
}
struct AuthenticatedState {
let user: User
let contacts: Loading<[Contact]>
}
override func viewWillAppear(animated: Bool) {
store.subscribe(self, selector: SignInViewModel.init)
}
override func viewWillDisappear(animated: Bool) {
store.dispatch(SignInFormAction.reset)
store.unsubscribe(self)
signInRequest?.cancel()
}
override func viewDidLoad() {
emailField.addTarget(self, action: .emailUpdated, forControlEvents: .EditingChanged)
passwordField.addTarget(self, action: .passwordUpdated, forControlEvents: .EditingChanged)
}
struct SignInViewModel {
let email: String
let password: String
let serverError: String?
let isSigningIn: Bool
let isFormValid: Bool
let hideServerError: Bool
let hideEmailError: Bool
let hidePasswordError: Bool
init?(state: AppState) {
// ...
fatalError()
}
}
func newState(viewModel: SignInViewModel?) {
guard let viewModel = viewModel else { return }
serverErrorLabel.text = viewModel.serverError
serverErrorView.hidden = viewModel.hideServerError
emailField.text = viewModel.email
emailField.enabled = !viewModel.isSigningIn
emailErrorLabel.hidden = viewModel.hideEmailError
passwordField.text = viewModel.password
passwordField.enabled = !viewModel.isSigningIn
passwordErrorLabel.hidden = viewModel.hidePasswordError
signInButton.enabled = viewModel.isFormValid
signInButton.hidden = viewModel.isSigningIn
activityIndicator.hidden = !viewModel.isSigningIn
}
UIView.animateWithDuration(0.3) {
self.emailErrorLabel.hidden = hideEmailError
self.passwordErrorLabel.hidden = hidePasswordError
}
extension UITableView {
func iDontKnowHowToFixThis() {
reloadData()
}
}
func newState(state: AppState) {
let (diff, updates) = lastState.rows.tableDiff(state.rows)
lastState = state
tableView.indexPathsForVisibleRows
for indexPath in updates.intersection(Set(tableView.indexPathsForVisibleRows ?? [])) {
let cell = tableView.cellForRow(at: indexPath) as! CustomCell
cell.update(state.rows[indexPath.row])
}
// automatically insert, delete, and move:
tableView.applyDiff(diff)
}
func newState(state: AppState) {
let (diff, updates) = lastState.rows.tableDiff(state.rows)
lastState = state
let visible = Set(tableView.indexPathsForVisibleRows ?? [])
for indexPath in updates.intersection(visible) {
let cell = tableView.cellForRow(at: indexPath) as! CustomCell
cell.update(state.rows[indexPath.row])
}
// automatically insert, delete, and move:
tableView.applyDiff(diff)
}
@IBAction func linkTapped() {
let vc = NextViewController()
navigationController?.pushViewController(vc, animated: true)
}
@IBAction func linkTapped() {
let vc = NextViewController()
navigationController?.pushViewController(vc, animated: true)
}
enum RouteExample {
struct AppState {
var route: [String]
}
func asdf() {
let state = AppState(route: ["auth", "signIn"])
UIViewController.goTo(route: ["home", "movies", "kubo"])
}
}
func application(_ app: UIApplication,
open url: URL,
options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool
{
// build the whole stack!
return true
}
func application(_ app: UIApplication, open url: URL,
options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
// build the whole stack!
return true
}
protocol Routable {
func changeRouteSegment(from: RouteElementIdentifier,
to: RouteElementIdentifier,
completionHandler: RoutingCompletionHandler) -> Routable
func pushRouteSegment(routeElementIdentifier: RouteElementIdentifier,
completionHandler: RoutingCompletionHandler) -> Routable
func popRouteSegment(routeElementIdentifier: RouteElementIdentifier,
completionHandler: RoutingCompletionHandler)
}
protocol Routable {
func changeRouteSegment(from: ..., to: ..., completion: ...) -> Routable
func pushRouteSegment(identifier: ..., completion: ... ) -> Routable
func popRouteSegment(identifier: ... , completionI: ... )
}
final class Store {
var state: State
var subscriber: Subscriber?
init(initialState: State) {
state = initialState
}
func dispatch(action: Action) {
// state = update state ...
subscriber?.newState(state)
}
func subscribe(view: Subscriber) {
subscriber = view
}
}
func testInvalidEmailAndPassword() {
state.signInForm.email = "mark@facebook"
state.signInForm.emailEditedOnce = true
state.signInForm.password = "pas"
state.signInForm.passwordEditedOnce = true
controller.newState(state.signInForm)
FBSnapshotVerifyView(window)
}
override func setUp() {
super.setUp()
recordMode = false
state = AppState()
}
func testInvalidEmailAndPassword() {
let state = AppState()
state.signInForm.email = "mark@facebook"
state.signInForm.emailEditedOnce = true
state.signInForm.password = "pas"
state.signInForm.passwordEditedOnce = true
controller.newState(state.signInForm)
FBSnapshotVerifyView(window)
}
func testInvalidEmailAndPassword() {
let state = AppState()
state.signInForm.email = "mark@facebook"
state.signInForm.emailEditedOnce = true
state.signInForm.password = "pas"
state.signInForm.passwordEditedOnce = true
controller.newState(state.signInForm)
FBSnapshotVerifyView(window)
}
protocol SignInHandler: class {
func signIn(credential: Credential)
func forgotPassword()
}
struct Credential {
}
enum Coordinator {
final class SignInViewController: UIViewController {
weak var handler: SignInHandler!
@IBAction func signInButtonPressed(sender: AnyObject) {
let credential = Credential()
handler.signIn(credential: credential)
}
@IBAction func forgotPasswordButtonPressed(sender: AnyObject) {
handler.forgotPassword()
}
}
func signIn(credential: Credential) {
store.dispatch(SignInFormAction.request)
api.signIn(with: credential) { [store] result in
DispatchQueue.main.async {
switch result {
case .success(let user):
store.dispatch(SignInFormAction.handleSuccess)
store.dispatch(AuthenticationAction.signIn(user))
case .error(let error):
store.dispatch(SignInFormAction.handleError(error))
}
}
}
}