Skip to content

Instantly share code, notes, and snippets.

@justinmakaila
Last active August 29, 2015 14:14
Show Gist options
  • Save justinmakaila/74494df57c237b7ce559 to your computer and use it in GitHub Desktop.
Save justinmakaila/74494df57c237b7ce559 to your computer and use it in GitHub Desktop.
Uses ReactiveCocoa to provide a view controller, protocol, and view model to present a simple table-driven feed.
//
// FeedViewController.swift
// ProjectDemand
//
// Created by Justin Makaila on 1/13/15.
// Copyright (c) 2015 Present. All rights reserved.
//
import UIKit
import TableAdapter
import ReactiveCocoa
protocol FeedViewModelProtocol {
var isEmpty: Bool { get }
func refreshSignal() -> RACSignal
func loadMoreSignal() -> RACSignal
}
class FeedViewModel: NSObject, FeedViewModelProtocol {
var isEmpty: Bool {
return true
}
func refreshSignal() -> RACSignal {
return RACSignal.empty()
}
func loadMoreSignal() -> RACSignal {
return RACSignal.empty()
}
}
class FeedViewController: UIViewController {
// MARK: - View Model
var viewModel: FeedViewModelProtocol!
// MARK: - General Signals
var networkingComplete: RACSignal!
var networkingError: RACSignal!
var executingNetworkRequest: RACSignal!
// MARK: - Empty Signals
var isEmptySignal: RACSignal!
// MARK: - Refresh
private(set) var refreshCommand: RACCommand!
var refreshExecuting: RACSignal!
var refreshSuccess: RACSignal!
var refreshError: RACSignal!
// MARK: - Load More
private(set) var loadMoreCommand: RACCommand!
var loadMoreExecuting: RACSignal!
var loadMoreSuccess: RACSignal!
var loadMoreError: RACSignal!
// MARK: - Subviews
private let refreshControl = UIRefreshControl()
// MARK: IBOutlets
@IBOutlet internal var emptyStateView: UIView?
@IBOutlet internal var tableView: UITableView!
// MARK: - Table View Adapter
// MARK: Table View Data Source
let tableDataSource = TableViewDataSource()
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if viewModel.isEmpty {
refresh()
}
}
func refresh() {
refreshCommand.execute(nil)
}
func loadMore() {
loadMoreCommand.execute(nil)
}
}
// MARK: - Setup Extension
private extension FeedViewController {
func setup() {
setupTableView()
setupSignals()
}
func setupTableView() {
// Insert the refresh control
tableView.insertSubview(refreshControl, atIndex: 0)
// Set `tableDataSource` table view to `tableView`
tableDataSource.tableView = tableView
}
func setupSignals() {
setupNetworkingSignals()
refreshControl.rac_command = refreshCommand
}
func setupRefreshSignals() {
// Create a refresh command for handling refresh
refreshCommand = RACCommand { _ in
return self.viewModel.refreshSignal()
}
// Refresh command should not allow concurrent execution
refreshCommand.allowsConcurrentExecution = false
// Configure the refresh signals
refreshExecuting = refreshCommand.executing
refreshSuccess = refreshCommand.executionSignals.flattenMap { $0 as RACSignal }
refreshError = refreshCommand.errors
}
func setupLoadMoreSignals() {
// Create a load more command for triggering a load more event
loadMoreCommand = RACCommand { _ in
return self.viewModel.loadMoreSignal()
}
// Load more command should not allow concurrent execution
loadMoreCommand.allowsConcurrentExecution = false
// Configure the load more signals
loadMoreExecuting = loadMoreCommand.executing
loadMoreSuccess = loadMoreCommand.executionSignals.flattenMap { $0 as RACSignal }
loadMoreError = loadMoreCommand.errors
}
func setupNetworkingSignals() {
// Setup the refresh command and signals
setupRefreshSignals()
// Setup the load more command and signals
setupLoadMoreSignals()
// Create a merged signal of `refreshExecuting` and `loadMoreExecuting`
executingNetworkRequest = RACSignal.merge([refreshExecuting, loadMoreExecuting])
// Create a merged signal of `refreshSuccess` and `loadMoreSuccess`
networkingComplete = RACSignal.merge([refreshSuccess, loadMoreSuccess])
// Create a merged signal of `refreshError` and `loadMoreError`
networkingError = RACSignal.merge([refreshError, loadMoreError])
// Create a signal for when networking completes
// or errors, mapping it to the value of `viewModel.isEmpty`
isEmptySignal = RACSignal
.merge([networkingComplete, networkingError])
.map { _ in
return self.viewModel.isEmpty
}
// Automatically hide the table view and show `emptyStateView`
// if `emptyStateView` != nil
isEmptySignal.subscribeNextAs { (isEmpty: Bool) in
if let emptyStateView = self.emptyStateView {
self.tableView.hidden = isEmpty
emptyStateView.hidden = !isEmpty
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment