Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions NonReactiveMVVM.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -352,9 +352,11 @@
TargetAttributes = {
32178D1A1CF8D1E300170E89 = {
CreatedOnToolsVersion = 7.3;
LastSwiftMigration = 0820;
};
32178D2E1CF8D1E300170E89 = {
CreatedOnToolsVersion = 7.3;
LastSwiftMigration = 0820;
TestTargetID = 32178D1A1CF8D1E300170E89;
};
};
Expand Down Expand Up @@ -556,6 +558,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.mustard.NonReactiveMVVM;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 3.0;
};
name = Debug;
};
Expand All @@ -568,6 +571,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.mustard.NonReactiveMVVM;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 3.0;
};
name = Release;
};
Expand All @@ -579,6 +583,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.mustard.NonReactiveMVVMTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 3.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/NonReactiveMVVM.app/NonReactiveMVVM";
};
name = Debug;
Expand All @@ -591,6 +596,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.mustard.NonReactiveMVVMTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 3.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/NonReactiveMVVM.app/NonReactiveMVVM";
};
name = Release;
Expand Down
4 changes: 2 additions & 2 deletions NonReactiveMVVM/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var app: Application?

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let window = UIWindow(frame: UIScreen.mainScreen().bounds)
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let window = UIWindow(frame: UIScreen.main.bounds)
self.app = Application(window: window)

self.window = window
Expand Down
6 changes: 3 additions & 3 deletions NonReactiveMVVM/Cells/FriendCell/FriendCell.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import UIKit

class FriendCell: UITableViewCell {
//MARK: - IBOutlets
@IBOutlet private var friendNameLabel: UILabel!
@IBOutlet private var friendImageView: UIImageView!
@IBOutlet fileprivate var friendNameLabel: UILabel!
@IBOutlet fileprivate var friendImageView: UIImageView!

//MARK: - Public
func setup(viewModel: FriendCellViewModel) {
func setup(_ viewModel: FriendCellViewModel) {
guard viewModel.allowedAccess(self) else { return }

self.friendNameLabel.text = viewModel.fullName
Expand Down
26 changes: 13 additions & 13 deletions NonReactiveMVVM/Cells/FriendCell/FriendCellViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import UIKit

class FriendCellViewModel {
//MARK: - Private
private let friend: Friend
private let imageCache: ImageCache
private var imageCacheCancellable: NetworkCancelable?
private var restrictedTo: NSIndexPath?
fileprivate let friend: Friend
fileprivate let imageCache: ImageCache
fileprivate var imageCacheCancellable: NetworkCancelable?
fileprivate var restrictedTo: IndexPath?

//MARK: - Lifecycle
init(friend: Friend, imageCache: ImageCache) {
Expand All @@ -25,22 +25,22 @@ class FriendCellViewModel {
}

//MARK: - Events
var didError: ((ErrorType) -> Void)?
var didError: ((Error) -> Void)?
var didUpdate: ((FriendCellViewModel) -> Void)?
var didSelectFriend: ((Friend) -> Void)?

//MARK: - Properties
var fullName: String { return "\(self.friend.firstName.capitalizedString) \(self.friend.lastName.capitalizedString)" }
private(set) var image: UIImage?
var fullName: String { return "\(self.friend.firstName.capitalized) \(self.friend.lastName.capitalized)" }
fileprivate(set) var image: UIImage?

//MARK: - Actions
func allowedAccess(object: CellIdentifiable) -> Bool {
func allowedAccess(_ object: CellIdentifiable) -> Bool {
guard
let restrictedTo = self.restrictedTo,
let uniqueId = object.uniqueId
else { return true }

return uniqueId == restrictedTo
return uniqueId as IndexPath == restrictedTo
}
func loadThumbnailImage() {
guard self.image == nil else { return } //ignore if we already have an image
Expand All @@ -62,11 +62,11 @@ class FriendCellViewModel {
}

extension FriendCellViewModel: CellRepresentable {
static func registerCell(tableView: UITableView) {
tableView.registerNib(UINib(nibName: String(FriendCell), bundle: nil), forCellReuseIdentifier: String(FriendCell))
static func registerCell(_ tableView: UITableView) {
tableView.register(UINib(nibName: String(describing: FriendCell.self), bundle: nil), forCellReuseIdentifier: String(describing: FriendCell.self))
}
func dequeueCell(tableView: UITableView, indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(String(FriendCell), forIndexPath: indexPath) as! FriendCell
func dequeueCell(_ tableView: UITableView, indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: FriendCell.self), for: indexPath) as! FriendCell
cell.uniqueId = indexPath
self.restrictedTo = indexPath
cell.setup(self)
Expand Down
12 changes: 6 additions & 6 deletions NonReactiveMVVM/Extensions/Dictionary+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@

import Foundation

extension Dictionary where Key: StringLiteralConvertible {
func getWithKeyPath<T>(keyPath: String, as: T.Type) -> T? {
extension Dictionary where Key: ExpressibleByStringLiteral {
func getWithKeyPath<T>(_ keyPath: String, as: T.Type) -> T? {
return self.getWithKeyPath(keyPath)
}
func getWithKeyPath<T>(keyPath: String) -> T? {
func getWithKeyPath<T>(_ keyPath: String) -> T? {
var keyPath = keyPath
let isKeyPath = keyPath.rangeOfString(".") != nil
let isKeyPath = keyPath.range(of: ".") != nil
var dictionary = self
if (isKeyPath) {
let components = keyPath.componentsSeparatedByString(".")
let components = keyPath.components(separatedBy: ".")
for i in 0..<components.count {
let keyPathComponent = components[i]
let isLast = (i == components.count - 1)
Expand All @@ -35,7 +35,7 @@ extension Dictionary where Key: StringLiteralConvertible {
return value
}

func append(other: [Key: Value]) -> [Key: Value] {
func append(_ other: [Key: Value]) -> [Key: Value] {
var result = [Key: Value]()
[self, other].forEach { dict in
dict.forEach { key, value in
Expand Down
4 changes: 2 additions & 2 deletions NonReactiveMVVM/Extensions/ErrorType+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ protocol LocalizedError {
}
extension NSError: LocalizedError { }

extension ErrorType {
extension Error {
func displayString() -> String {
if let error = self as? LocalizedError {
return error.localizedDescription
Expand All @@ -28,4 +28,4 @@ extension ErrorType {
*/
return (self as NSError).description
}
}
}
24 changes: 15 additions & 9 deletions NonReactiveMVVM/Extensions/SequenceType+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,23 @@

import Foundation

func random(min min: Int, max: Int) -> Int {
func random(min: Int, max: Int) -> Int {
return Int(arc4random_uniform(UInt32(max + 1)) + UInt32(min))
}

extension CollectionType where Self.Index == Int {
func randomElement() -> Self.Generator.Element {
let index = random(min: 0, max: self.count - 1)
extension Array {
var randomElement: Element {
let index = Int(arc4random_uniform(UInt32(count)))
return self[index]
}
subscript(safe safe: Self.Index) -> Generator.Element? {
guard self.indices.contains(safe) else { return nil }
return self[safe]
}
}
}
//extension Collection {
// func randomElement() -> Self.Iterator.Element {
// let index = random(min: 0, max: Int(self.count) - 1)
// return self[index]
// }
// subscript(safe safe: Self.Index) -> Iterator.Element? {
// guard self.indices.contains(where: safe) else { return nil }
// return self[safe]
// }
//}
10 changes: 5 additions & 5 deletions NonReactiveMVVM/Extensions/UIImage+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
import UIKit

extension UIImage {
static func fromColor(color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) -> UIImage {
let rect = CGRect(origin: CGPointZero, size: size)
static func fromColor(_ color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) -> UIImage {
let rect = CGRect(origin: CGPoint.zero, size: size)
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()
CGContextSetFillColorWithColor(context, color.CGColor)
CGContextFillRect(context, rect)
context?.setFillColor(color.cgColor)
context?.fill(rect)
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return img
return img!
}
}

6 changes: 3 additions & 3 deletions NonReactiveMVVM/Protocols/CellIdentifiable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@ import ObjectiveC
import UIKit

protocol CellIdentifiable: class {
var uniqueId: NSIndexPath? { get set }
var uniqueId: IndexPath? { get set }
}

private struct AssociatedKeys {
static var UniqueID = "UniqueID"
}

extension UITableViewCell: CellIdentifiable {
var uniqueId: NSIndexPath? {
get { return objc_getAssociatedObject(self, &AssociatedKeys.UniqueID) as? NSIndexPath }
var uniqueId: IndexPath? {
get { return objc_getAssociatedObject(self, &AssociatedKeys.UniqueID) as? IndexPath }
set {
objc_setAssociatedObject(self, &AssociatedKeys.UniqueID, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
if let newValue = newValue {
Expand Down
4 changes: 2 additions & 2 deletions NonReactiveMVVM/Protocols/CellRepresentable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import UIKit

protocol CellRepresentable {
static func registerCell(tableView: UITableView)
func dequeueCell(tableView: UITableView, indexPath: NSIndexPath) -> UITableViewCell
static func registerCell(_ tableView: UITableView)
func dequeueCell(_ tableView: UITableView, indexPath: IndexPath) -> UITableViewCell
func cellSelected()
}
20 changes: 10 additions & 10 deletions NonReactiveMVVM/Services/API/API.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,29 @@

import Foundation

enum APIError: ErrorType, CustomStringConvertible {
case InvalidResponse
case NotFound
enum APIError: Error, CustomStringConvertible {
case invalidResponse
case notFound

var description: String {
switch self {
case .InvalidResponse: return "Received an invalid response"
case .NotFound: return "Requested item was not found"
case .invalidResponse: return "Received an invalid response"
case .notFound: return "Requested item was not found"
}
}
}

class API {
//MARK: - Private
private let network: Network
fileprivate let network: Network

//MARK: - Lifecycle
init(network: Network) {
self.network = network
}

//MARK: - Public
func getFriends(success success: ([Friend]) -> Void, failure: (ErrorType) -> Void) {
func getFriends(success: @escaping ([Friend]) -> Void, failure: @escaping (Error) -> Void) {
let request = NetworkRequest(
method: .GET,
url: "http://api.randomuser.me/?results=20"
Expand All @@ -39,7 +39,7 @@ class API {
request,
success: { (json: [String: AnyObject]) in
guard let items = json.getWithKeyPath("results", as: [[String: AnyObject]].self) else {
failure(APIError.InvalidResponse)
failure(APIError.invalidResponse)
return
}

Expand All @@ -51,14 +51,14 @@ class API {
}

//MARK: - Helpers
private func jsonWithInjectedAbout(json: [String: AnyObject]) -> [String: AnyObject] {
fileprivate func jsonWithInjectedAbout(_ json: [String: AnyObject]) -> [String: AnyObject] {
let lorems = [
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut vel ante ac odio gravida convallis. Ut faucibus eu nulla eget sollicitudin. Quisque ac erat ut tortor fringilla euismod id non nisl. Maecenas diam elit, ornare accumsan libero ac, hendrerit ullamcorper quam. Aenean in nisi malesuada odio faucibus fermentum. Aliquam erat volutpat. Etiam ut sapien iaculis, venenatis diam at, vehicula mauris. Donec arcu nulla, lobortis eu posuere eu, tincidunt non tortor. Nunc facilisis ex at tristique mattis. Aenean luctus orci felis, non ullamcorper ipsum placerat vulputate. Proin eleifend neque a odio egestas sagittis. Pellentesque malesuada nulla a enim ultricies blandit. Pellentesque commodo nulla et lacus malesuada, at rutrum nunc fermentum.",
"Praesent bibendum nunc dolor, et iaculis arcu vestibulum nec. Praesent vestibulum eros in metus suscipit, eget sagittis orci bibendum. Duis auctor convallis dignissim. Maecenas sapien nisi, feugiat eu eros lacinia, mattis laoreet ligula. Phasellus porta efficitur sapien sit amet commodo. Suspendisse dictum tincidunt tortor quis bibendum. Curabitur congue viverra vehicula. Curabitur eget diam nec nunc dapibus tincidunt. In at lectus aliquam, dictum ipsum at, convallis sem. Donec volutpat lorem eu mauris semper, viverra iaculis lorem posuere. Maecenas iaculis egestas ante, nec vulputate eros varius sit amet. Nam dapibus massa nunc, viverra pretium metus dignissim in. Nulla nec facilisis nunc. Morbi dictum velit in mi tincidunt pharetra. Aliquam erat volutpat.",
"Nam consectetur eget est sit amet ornare. Vivamus leo mauris, ornare et ornare eu, aliquam a sapien. Donec ultrices ante nec sapien varius maximus. In hac habitasse platea dictumst. Mauris vitae nisl at purus vestibulum maximus. Proin finibus aliquam nibh id tristique. Quisque hendrerit sit amet erat vel accumsan. Sed ac mi sit amet quam suscipit fringilla. Cras mattis, nulla sed lobortis finibus, ipsum dolor porttitor ipsum, id accumsan eros leo in metus. Sed id magna ut diam viverra viverra. Donec porta gravida fermentum. Vivamus id massa interdum, dapibus augue sit amet, dictum tortor.",
"Morbi accumsan tortor justo, sit amet pharetra tellus elementum ut. Quisque aliquet tempus congue. Sed ut elementum odio, at porta enim. Suspendisse et mi molestie, viverra nisl non, accumsan tellus. Cras semper, lectus at consequat mollis, enim libero rutrum ipsum, vel pulvinar nulla risus lacinia eros. Cras efficitur, libero ut molestie vulputate, lorem justo commodo est, nec cursus ipsum sem ut augue. Quisque elementum sollicitudin lacus et eleifend. Quisque ac vulputate diam, sollicitudin faucibus odio. Sed sodales porttitor pellentesque. Proin et sapien efficitur, maximus velit ac, auctor ex. Donec iaculis ultricies auctor. Donec tempus tempor sapien, sit amet viverra tortor sodales ut. Ut faucibus magna eu nisi varius, quis maximus velit sodales. Vivamus maximus velit ipsum, eget accumsan lectus auctor non. Nulla sollicitudin tristique auctor. Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
"Vestibulum tincidunt tincidunt erat, sit amet dictum magna venenatis quis. Nullam quis tristique magna, at pharetra dolor. Quisque ullamcorper iaculis sapien, vitae interdum magna sollicitudin sit amet. Quisque diam nisi, malesuada et nisl vel, vulputate vestibulum ante. Curabitur sit amet neque nunc. Duis euismod mattis tortor, eget lacinia arcu vulputate ut. Nunc quis leo risus. In sollicitudin odio vel neque efficitur varius. Aliquam et convallis risus. Vivamus id tristique nulla. Nulla dolor nulla, cursus ut nisl at, imperdiet dapibus nisi. Mauris luctus ac dolor sit amet sagittis. Vivamus mollis lacinia convallis. Phasellus scelerisque faucibus diam, non auctor risus imperdiet id. Etiam fringilla aliquam nulla, interdum dapibus arcu laoreet et. Cras in eleifend quam."
]
return json.append(["about": lorems.randomElement()])
return json.append(["about": lorems.randomElement as AnyObject])
}
}
4 changes: 2 additions & 2 deletions NonReactiveMVVM/Services/Application/Application.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ import UIKit

class Application {
//MARK: - Dependencies
private let window: UIWindow
fileprivate let window: UIWindow
lazy var navigation: Navigation = Navigation(
window: self.window,
navigationController: NavigationController(),
application: self
)
lazy var network = NetworkProvider(session: NSURLSession.sharedSession())
lazy var network = NetworkProvider(session: URLSession.shared)
lazy var api: API = API(network: self.network)
lazy var imageCache: ImageCache = ImageCacheProvider(network: self.network)

Expand Down
4 changes: 2 additions & 2 deletions NonReactiveMVVM/Services/ImageCache/ImageCache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import UIKit

enum ImageCacheError: ErrorType, CustomStringConvertible {
enum ImageCacheError: Error, CustomStringConvertible {
case InvalidResponse

var description: String {
Expand All @@ -21,7 +21,7 @@ enum ImageCacheError: ErrorType, CustomStringConvertible {
protocol ImageCache {
init(network: Network)

func image(url url: String, success: (UIImage) -> Void, failure: (ErrorType) -> Void) -> NetworkCancelable?
func image(url url: String, success: @escaping (UIImage) -> Void, failure: @escaping (Error) -> Void) -> NetworkCancelable?
func hasImageFor(url: String) -> Bool
func cachedImage(url url: String, or: UIImage?) -> UIImage?
func clearCache()
Expand Down
10 changes: 5 additions & 5 deletions NonReactiveMVVM/Services/ImageCache/ImageCacheProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@ import UIKit

class ImageCacheProvider: ImageCache {
//MARK: - Private
private var cache = [String: UIImage]()
private let network: Network
fileprivate var cache = [String: UIImage]()
fileprivate let network: Network

//MARK: - Lifecycle
required init(network: Network) {
self.network = network
}

//MARK: - Public
func image(url url: String, success: (UIImage) -> Void, failure: (ErrorType) -> Void) -> NetworkCancelable? {
func image(url: String, success: @escaping (UIImage) -> Void, failure: @escaping (Error) -> Void) -> NetworkCancelable? {
if let existing = self.cache[url] {
success(existing)
print("cached")
Expand All @@ -29,7 +29,7 @@ class ImageCacheProvider: ImageCache {
let request = NetworkRequest(method: .GET, url: url)
return self.network.makeRequest(
request,
success: { [weak self] (data: NSData) in
success: { [weak self] (data: Data) in
guard let `self` = self else { return }

guard let image = UIImage(data: data) else {
Expand All @@ -44,7 +44,7 @@ class ImageCacheProvider: ImageCache {
func hasImageFor(url: String) -> Bool {
return (self.cache[url] != nil)
}
func cachedImage(url url: String, or: UIImage?) -> UIImage? {
func cachedImage(url: String, or: UIImage?) -> UIImage? {
return self.cache[url] ?? or
}
func clearCache() { self.cache = [String: UIImage]() }
Expand Down
Loading