diff --git a/Podfile b/Podfile index 8c58b81..2c060ee 100644 --- a/Podfile +++ b/Podfile @@ -5,4 +5,7 @@ target 'RxTutorials' do pod 'RxCocoa' pod 'RxDataSources' pod 'ChameleonFramework/Swift' + pod 'ObjectMapper' + pod 'RxAlamofire' + pod 'AlamofireNetworkActivityIndicator' end \ No newline at end of file diff --git a/Podfile.lock b/Podfile.lock index 4cf2b7b..a6036f5 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,7 +1,16 @@ PODS: + - Alamofire (3.4.1) + - AlamofireNetworkActivityIndicator (1.0.1): + - Alamofire (~> 3.3) - ChameleonFramework/Default (2.1.0) - ChameleonFramework/Swift (2.1.0): - ChameleonFramework/Default + - ObjectMapper (1.3.0) + - RxAlamofire (2.5): + - RxAlamofire/Core (= 2.5) + - RxAlamofire/Core (2.5): + - Alamofire (~> 3.4) + - RxSwift (~> 2.5) - RxCocoa (2.6.0): - RxSwift (~> 2.5) - RxDataSources (0.9): @@ -10,17 +19,24 @@ PODS: - RxSwift (2.6.0) DEPENDENCIES: + - AlamofireNetworkActivityIndicator - ChameleonFramework/Swift + - ObjectMapper + - RxAlamofire - RxCocoa - RxDataSources - RxSwift SPEC CHECKSUMS: + Alamofire: 01a82e2f6c0f860ade35534c8dd88be61bdef40c + AlamofireNetworkActivityIndicator: 49a6e39afb947136f7b1872bae2d684957c26697 ChameleonFramework: d21a3cc247abfe5e37609a283a8238b03575cf64 + ObjectMapper: 4eb08a726e9791a93b2632049951b56a7c17e471 + RxAlamofire: b4572a077b6b2410c8732a27266299df4c00736e RxCocoa: 89ab00d5753502520227940e3a0e0a0413683c6b RxDataSources: e169bba94a7be584df7c1e1487fa56edc8753ae6 RxSwift: 77f3a0b15324baa7a1c9bfa9f199648a82424e26 -PODFILE CHECKSUM: eddc4457677bde2bc3f6299c3462d1de50e45772 +PODFILE CHECKSUM: acdb8030ddfcf2a95ba2376b3f556d7e1aa82091 COCOAPODS: 1.0.0 diff --git a/RxTutorials.xcodeproj/project.pbxproj b/RxTutorials.xcodeproj/project.pbxproj index 33304b7..6f6a4af 100644 --- a/RxTutorials.xcodeproj/project.pbxproj +++ b/RxTutorials.xcodeproj/project.pbxproj @@ -43,6 +43,281 @@ isa PBXBuildFile + 0D7041851D58D0D80061DDA0 + + children + + 0D7041861D58D1F80061DDA0 + + isa + PBXGroup + path + Models + sourceTree + <group> + + 0D7041861D58D1F80061DDA0 + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.swift + path + Repository.swift + sourceTree + <group> + + 0D7041871D58D1F80061DDA0 + + fileRef + 0D7041861D58D1F80061DDA0 + isa + PBXBuildFile + + 0D7041881D58D4890061DDA0 + + children + + 0D7041891D58D4B20061DDA0 + + isa + PBXGroup + path + Networking Search bar + sourceTree + <group> + + 0D7041891D58D4B20061DDA0 + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.swift + path + NetworkingSearchBarViewController.swift + sourceTree + <group> + + 0D70418A1D58D4B20061DDA0 + + fileRef + 0D7041891D58D4B20061DDA0 + isa + PBXBuildFile + + 0D70418B1D58D6350061DDA0 + + children + + 0D70418F1D58D9940061DDA0 + 0D70418C1D58D6530061DDA0 + + isa + PBXGroup + path + Services + sourceTree + <group> + + 0D70418C1D58D6530061DDA0 + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.swift + path + RepositoriesNetworkService.swift + sourceTree + <group> + + 0D70418D1D58D6530061DDA0 + + fileRef + 0D70418C1D58D6530061DDA0 + isa + PBXBuildFile + + 0D70418E1D58D9800061DDA0 + + children + + 0D7041971D58E2F40061DDA0 + + isa + PBXGroup + path + API + sourceTree + <group> + + 0D70418F1D58D9940061DDA0 + + children + + 0D7041901D58D9A60061DDA0 + + isa + PBXGroup + path + Networking + sourceTree + <group> + + 0D7041901D58D9A60061DDA0 + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.swift + path + Router.swift + sourceTree + <group> + + 0D7041911D58D9A60061DDA0 + + fileRef + 0D7041901D58D9A60061DDA0 + isa + PBXBuildFile + + 0D7041921D58DB250061DDA0 + + children + + 0D7041931D58DB4B0061DDA0 + 0D7041951D58E14B0061DDA0 + + isa + PBXGroup + path + Error + sourceTree + <group> + + 0D7041931D58DB4B0061DDA0 + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.swift + path + Error.swift + sourceTree + <group> + + 0D7041941D58DB4B0061DDA0 + + fileRef + 0D7041931D58DB4B0061DDA0 + isa + PBXBuildFile + + 0D7041951D58E14B0061DDA0 + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.swift + path + LocalizableString.swift + sourceTree + <group> + + 0D7041961D58E14B0061DDA0 + + fileRef + 0D7041951D58E14B0061DDA0 + isa + PBXBuildFile + + 0D7041971D58E2F40061DDA0 + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.swift + path + RepositoriesAPI.swift + sourceTree + <group> + + 0D7041981D58E2F40061DDA0 + + fileRef + 0D7041971D58E2F40061DDA0 + isa + PBXBuildFile + + 0D7041991D58F0980061DDA0 + + children + + 0D70419A1D58F0C40061DDA0 + + isa + PBXGroup + name + Elegant Networking Search bar + path + Elegant Networking Search bar + sourceTree + <group> + + 0D70419A1D58F0C40061DDA0 + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.swift + path + ElegantNetworkingSearchBarViewController.swift + sourceTree + <group> + + 0D70419B1D58F0C40061DDA0 + + fileRef + 0D70419A1D58F0C40061DDA0 + isa + PBXBuildFile + + 0D70419C1D58F4790061DDA0 + + fileEncoding + 4 + isa + PBXFileReference + lastKnownFileType + sourcecode.swift + path + RepositoriesViewModel.swift + sourceTree + <group> + + 0D70419D1D58F4790061DDA0 + + fileRef + 0D70419C1D58F4790061DDA0 + isa + PBXBuildFile + 0DBAFB001D53858900D49D70 fileEncoding @@ -121,6 +396,7 @@ children + 0D70419C1D58F4790061DDA0 0DBAFB081D539F3F00D49D70 isa @@ -292,8 +568,6 @@ isa PBXGroup - name - Basic Search bar path Basic Search bar sourceTree @@ -402,18 +676,27 @@ 2147483647 files + 0D70418A1D58D4B20061DDA0 0DBAFB0D1D53A61900D49D70 0DBAFB1B1D53E27E00D49D70 0DCFC4E81D5148B9001FD547 + 0D7041871D58D1F80061DDA0 0DBAFB141D53C79300D49D70 + 0D70418D1D58D6530061DDA0 0DBAFB091D539F3F00D49D70 + 0D7041911D58D9A60061DDA0 + 0D7041941D58DB4B0061DDA0 + 0D70419D1D58F4790061DDA0 0DBAFB171D53D11C00D49D70 0DBAFB061D539E4400D49D70 0D1675201D52509C0072EC03 + 0D7041961D58E14B0061DDA0 0DBAFB0C1D53A61900D49D70 + 0D7041981D58E2F40061DDA0 0DCFC4D21D5144E9001FD547 0DBAFB021D53858900D49D70 0DBAFB031D53858900D49D70 + 0D70419B1D58F0C40061DDA0 isa PBXSourcesBuildPhase @@ -506,8 +789,12 @@ children + 0D70418E1D58D9800061DDA0 0D16751E1D52491F0072EC03 0DCFC4E61D514858001FD547 + 0D7041921D58DB250061DDA0 + 0D7041851D58D0D80061DDA0 + 0D70418B1D58D6350061DDA0 0DCFC4E51D514853001FD547 0DBAFB071D539ED700D49D70 0DCFC4D11D5144E9001FD547 @@ -820,6 +1107,8 @@ AppIcon INFOPLIST_FILE RxTutorials/Info.plist + IPHONEOS_DEPLOYMENT_TARGET + 9.3 LD_RUNPATH_SEARCH_PATHS $(inherited) @executable_path/Frameworks PRODUCT_BUNDLE_IDENTIFIER @@ -842,6 +1131,8 @@ AppIcon INFOPLIST_FILE RxTutorials/Info.plist + IPHONEOS_DEPLOYMENT_TARGET + 9.3 LD_RUNPATH_SEARCH_PATHS $(inherited) @executable_path/Frameworks PRODUCT_BUNDLE_IDENTIFIER @@ -876,6 +1167,8 @@ 0DBAFB151D53D0EC00D49D70 0DBAFB0F1D53ADCA00D49D70 0DBAFB191D53E25600D49D70 + 0D7041991D58F0980061DDA0 + 0D7041881D58D4890061DDA0 0DBAFB101D53C70900D49D70 0DBAFB0E1D53ADBD00D49D70 0DCFC4E71D5148B9001FD547 diff --git a/RxTutorials/API/RepositoriesAPI.swift b/RxTutorials/API/RepositoriesAPI.swift new file mode 100644 index 0000000..eb4e46f --- /dev/null +++ b/RxTutorials/API/RepositoriesAPI.swift @@ -0,0 +1,23 @@ +// +// RepositoriesAPI.swift +// RxTutorials +// +// Created by Abel Osorio on 8/8/16. +// Copyright © 2016 Abel Osorio. All rights reserved. +// + +import Foundation +import RxSwift +import Alamofire + +struct RepositoriesAPI { + + private static let disposeBag = DisposeBag() + + static func searchRepositories(query query: String) -> Observable<[Repository]> { + + return RepositoriesNetworkService.searchRepositories(query: query) + } + + +} \ No newline at end of file diff --git a/RxTutorials/AppDelegate.swift b/RxTutorials/AppDelegate.swift index 45947d8..21ab3c0 100644 --- a/RxTutorials/AppDelegate.swift +++ b/RxTutorials/AppDelegate.swift @@ -8,6 +8,7 @@ import UIKit import ChameleonFramework +import AlamofireNetworkActivityIndicator @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { @@ -18,6 +19,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { // Override point for customization after application launch. customAppAppereance() + NetworkActivityIndicatorManager.sharedManager.isEnabled = true return true } diff --git a/RxTutorials/Controllers/Elegant Networking Search bar/ElegantNetworkingSearchBarViewController.swift b/RxTutorials/Controllers/Elegant Networking Search bar/ElegantNetworkingSearchBarViewController.swift new file mode 100644 index 0000000..37d3b17 --- /dev/null +++ b/RxTutorials/Controllers/Elegant Networking Search bar/ElegantNetworkingSearchBarViewController.swift @@ -0,0 +1,67 @@ +// +// ElegantNetworkingSearchBarViewController.swift +// RxTutorials +// +// Created by Abel Osorio on 8/8/16. +// Copyright © 2016 Abel Osorio. All rights reserved. +// + +import UIKit +import RxSwift +import RxCocoa +import RxDataSources + +class ElegantNetworkingSearchBarViewController: UIViewController { + + // MARK: - IBOutlets + @IBOutlet weak private var tableView:UITableView! + @IBOutlet weak private var searchBar:UISearchBar! + + // MARK: - Properties + let disposeBag = DisposeBag() + var viewModel:RepositoriesViewModel = RepositoriesViewModel() + + // MARK: - Lifecycle + + // MARK: - View Lifecycle + + override func viewDidLoad() { + super.viewDidLoad() + setUpView() + } + + // MARK: - Internal Helpers + + private func setUpView(){ + title = "Elegant GitHub Repos" + setUpNavigationBar() + setUpSearchBar() + } + + private func setUpNavigationBar(){ + + } + + private func setUpSearchBar(){ + + searchBar.placeholder = "Enter github username" + + searchBar.rx_text + .filter{$0.characters.count > 0 } + .bindTo(viewModel.searchText) + .addDisposableTo(disposeBag) + + searchBar.rx_cancelButtonClicked + .map{""} + .bindTo(viewModel.searchText) + .addDisposableTo(disposeBag) + + + viewModel.data + .drive(tableView.rx_itemsWithCellIdentifier("Cell")){ _, repository, cell in + cell.textLabel?.text = repository.name + cell.detailTextLabel?.text = repository.url + }.addDisposableTo(disposeBag) + } + +} diff --git a/RxTutorials/Controllers/Networking Search bar/NetworkingSearchBarViewController.swift b/RxTutorials/Controllers/Networking Search bar/NetworkingSearchBarViewController.swift new file mode 100644 index 0000000..9a61ed6 --- /dev/null +++ b/RxTutorials/Controllers/Networking Search bar/NetworkingSearchBarViewController.swift @@ -0,0 +1,84 @@ +// +// NetworkingSearchBarViewController.swift +// RxTutorials +// +// Created by Abel Osorio on 8/8/16. +// Copyright © 2016 Abel Osorio. All rights reserved. +// + +import UIKit +import RxSwift +import RxCocoa + +class NetworkingSearchBarViewController: UIViewController { + + // MARK: - IBOutlets + @IBOutlet weak private var tableView:UITableView! + @IBOutlet weak private var searchBar:UISearchBar! + + // MARK: - Properties + let disposeBag = DisposeBag() + var repositoryNetworkModel: RepositoriesNetworkService! + + + var searchText: Observable{ + return searchBar.rx_text + .filter {$0.characters.count > 0} + .throttle(0.3, scheduler: MainScheduler.instance) + .distinctUntilChanged() + } + + // MARK: - View Lifecycle + + override func viewDidLoad() { + super.viewDidLoad() + setUpView() + } + + // MARK: - Internal Helpers + + private func setUpView(){ + title = "GitHub Repos" + setUpNavigationBar() + setUpSearchBar() + setupRx() + } + + private func setUpNavigationBar(){ + + tableView.tableFooterView = UIView() + } + + private func setUpSearchBar(){ + + searchBar.placeholder = "Enter github username" + + } + + func setupRx() { + + repositoryNetworkModel = RepositoriesNetworkService(withNameObservable: searchText) + + repositoryNetworkModel + .rx_repositories + .drive(tableView.rx_itemsWithCellFactory) { (tv, i, repository) in + let cell = tv.dequeueReusableCellWithIdentifier("Cell", forIndexPath: NSIndexPath(forRow: i, inSection: 0)) + cell.textLabel?.text = repository.name + + return cell + } + .addDisposableTo(disposeBag) + + repositoryNetworkModel + .rx_repositories + .driveNext { repositories in + if repositories.count == 0 { + print("No repositories") + } + } + .addDisposableTo(disposeBag) + } + + + +} diff --git a/RxTutorials/Controllers/TutorialsListViewController.swift b/RxTutorials/Controllers/TutorialsListViewController.swift index fe99e4e..bd21311 100644 --- a/RxTutorials/Controllers/TutorialsListViewController.swift +++ b/RxTutorials/Controllers/TutorialsListViewController.swift @@ -32,8 +32,10 @@ class TutorialsListViewController: UIViewController { case SectionedTableViewReload = "SectionedTableViewReload" case SectionedTableViewAnimated = "SectionedTableViewAnimated" case BasicSearchBar = "BasicSearchBarViewController" + case NetworkingSearchBar = "NetworkingSearchBarViewController" + case ElegantNetworkingSearchBar = "ElegantNetworkingSearchBarViewController" - static let values :[DataSource] = [.BasicControls,.TwoWayBinding,.SectionedTableViewReload,.SectionedTableViewAnimated,.BasicSearchBar] + static let values :[DataSource] = [.BasicControls,.TwoWayBinding,.SectionedTableViewReload,.SectionedTableViewAnimated,.BasicSearchBar,.NetworkingSearchBar,.ElegantNetworkingSearchBar] } enum CellIdentifier:String { diff --git a/RxTutorials/Error/Error.swift b/RxTutorials/Error/Error.swift new file mode 100644 index 0000000..9538af1 --- /dev/null +++ b/RxTutorials/Error/Error.swift @@ -0,0 +1,93 @@ +// +// ApiError.swift +// RxTutorials +// +// Created by Abel Osorio on 8/8/16. +// Copyright © 2016 Abel Osorio. All rights reserved. +// + +import Foundation +import Alamofire + +protocol ErrorPresentable: ErrorType { + var title: String? { get } + var message: String? { get } +} + +struct ApiError : ErrorPresentable { + + // MARK: - Properties + var title: String? + var message: String? + var code: ApiErrorCode? + + // MARK: - Initializers + init(title: String? = nil, message: String? = nil, code: ApiErrorCode? = nil) { + self.title = title + self.message = message + self.code = code + } + + init?(error: NSError, data: NSData?) { + + if Int(CFNetworkErrors.CFURLErrorNotConnectedToInternet.rawValue) == error.code { + title = LocalizableString.offlineError.localizedString + message = LocalizableString.checkInternet.localizedString + } else if let errorMessage = data?.apiErrorMessage { + message = errorMessage + if let code = data?.apiErrorCode, apiErrorCode = ApiErrorCode(rawValue: code) { + self.code = apiErrorCode + } + } else { + return nil + } + } + + // MARK: - Utils + static var defaultError: ApiError { + return ApiError( + title: LocalizableString.connectionError.localizedString, + message: LocalizableString.connectionProblem.localizedString, + code: nil) + } +} + +enum ApiErrorCode: Int { + // TODO: Set error Codes + case ErrorUnknown = 0 + case sampleCase = 1 +} + +extension NSData { + func toJSONDictionary() -> [String: AnyObject]? { + + guard let json = try? NSJSONSerialization.JSONObjectWithData(self, options: []) else { + return nil + } + guard let jsonDic = json as? [String: AnyObject] else { + return nil + } + return jsonDic + } + + var apiErrorMessage: String? { + if let jsonDic = toJSONDictionary(), + let errorMessage = jsonDic["message"] as? String { + return errorMessage + } + + return nil + } + + var apiErrorCode: Int? { + if let jsonDic = toJSONDictionary(), + let errorCode = jsonDic["code"] as? Int { + return errorCode + } + + return nil + } +} + + + diff --git a/RxTutorials/Error/LocalizableString.swift b/RxTutorials/Error/LocalizableString.swift new file mode 100644 index 0000000..5c41340 --- /dev/null +++ b/RxTutorials/Error/LocalizableString.swift @@ -0,0 +1,22 @@ +// +// LocalizableString.swift +// RxTutorials +// +// Created by Abel Osorio on 8/8/16. +// Copyright © 2016 Abel Osorio. All rights reserved. +// + +import Foundation +enum LocalizableString: String { + + var localizedString: String { + return NSLocalizedString(rawValue, comment: "") + } + + //MARK: - Global + case connectionError = "Connection error" + case connectionProblem = "There was a problem, please try again." + case offlineError = "Offline error" + case checkInternet = "Check your internet connection and try again." + +} \ No newline at end of file diff --git a/RxTutorials/Models/Repository.swift b/RxTutorials/Models/Repository.swift new file mode 100644 index 0000000..3bbdd74 --- /dev/null +++ b/RxTutorials/Models/Repository.swift @@ -0,0 +1,26 @@ +// +// Repository.swift +// RxTutorials +// +// Created by Abel Osorio on 8/8/16. +// Copyright © 2016 Abel Osorio. All rights reserved. +// + +import Foundation +import ObjectMapper + +class Repository: Mappable { + var identifier: Int! + var language: String! + var url: String! + var name: String! + + required init?(_ map: Map) { } + + func mapping(map: Map) { + identifier <- map["id"] + language <- map["language"] + url <- map["url"] + name <- map["name"] + } +} diff --git a/RxTutorials/Services/Networking/Router.swift b/RxTutorials/Services/Networking/Router.swift new file mode 100644 index 0000000..92d213c --- /dev/null +++ b/RxTutorials/Services/Networking/Router.swift @@ -0,0 +1,70 @@ +// +// Router.swift +// RxTutorials +// +// Created by Abel Osorio on 8/8/16. +// Copyright © 2016 Abel Osorio. All rights reserved. +// + +import Foundation +import Alamofire + +typealias JSONDictionary = [String: AnyObject] + + +enum Router: URLRequestConvertible { + + private struct Request { + let method: Alamofire.Method + let path: String + let encoding: ParameterEncoding? + let parameters: JSONDictionary? + + init(method: Alamofire.Method, + path: String, + encoding: ParameterEncoding? = nil, + parameters: JSONDictionary? = nil){ + + self.method = method + self.path = path + self.encoding = encoding + self.parameters = parameters + } + } + + static let baseHostPath = "https://api.github.com/" + + var baseURLPath: String { + + switch self { + default: + return "\(Router.baseHostPath)" + } + } + + case Repositories(String) + + private var request: Request { + switch self { + //Repositories + case .Repositories(let query): + return Request(method: .GET, + path: "users/\(query)/repos", + encoding: .JSON) + } + } + + var URLRequest: NSMutableURLRequest { + + let URL = NSURL(string: baseURLPath)! + let mutableURLRequest = NSMutableURLRequest(URL: URL.URLByAppendingPathComponent(request.path)) + mutableURLRequest.HTTPMethod = request.method.rawValue + mutableURLRequest.timeoutInterval = NSTimeInterval(10 * 1000) + + if let encoding = request.encoding { + return encoding.encode(mutableURLRequest, parameters: request.parameters).0 + } else { + return mutableURLRequest + } + } +} \ No newline at end of file diff --git a/RxTutorials/Services/RepositoriesNetworkService.swift b/RxTutorials/Services/RepositoriesNetworkService.swift new file mode 100644 index 0000000..734676f --- /dev/null +++ b/RxTutorials/Services/RepositoriesNetworkService.swift @@ -0,0 +1,81 @@ +// +// RepositoriesNetworkService.swift +// RxTutorials +// +// Created by Abel Osorio on 8/8/16. +// Copyright © 2016 Abel Osorio. All rights reserved. +// + +import Foundation +import RxSwift +import RxCocoa +import ObjectMapper +import RxAlamofire +import Alamofire + +struct RepositoriesNetworkService { + + // driver way + lazy var rx_repositories: Driver<[Repository]> = self.fetchRepositories() + private var repositoryName: Observable + + init(withNameObservable nameObservable: Observable) { + self.repositoryName = nameObservable + } + + private func fetchRepositories() -> Driver<[Repository]> { + return repositoryName + .subscribeOn(MainScheduler.instance) // Make sure we are on MainScheduler + .doOn(onNext: { response in + UIApplication.sharedApplication().networkActivityIndicatorVisible = true + }) + .observeOn(ConcurrentDispatchQueueScheduler(globalConcurrentQueueQOS: .Background)) + .flatMapLatest { text in // .Background thread, network request + return RxAlamofire + .requestJSON(.GET, "https://api.github.com/users/\(text)/repos") + .catchError { error in + return Observable.never() + } + } + .observeOn(ConcurrentDispatchQueueScheduler(globalConcurrentQueueQOS: .Background)) + .map { (response, json) -> [Repository] in // again back to .Background, map objects + if let repos = Mapper().mapArray(json) { + return repos + } else { + return [] + } + } + .observeOn(MainScheduler.instance) // switch to MainScheduler, UI updates + .doOn(onNext: { response in + UIApplication.sharedApplication().networkActivityIndicatorVisible = false + }) + .asDriver(onErrorJustReturn: []) // This also makes sure that we are on MainScheduler + } + + + // Elegant way + static func searchRepositories(query query:String) -> Observable<[Repository]>{ + + return Observable.create{ (observer) -> Disposable in + + Alamofire.request(Router.Repositories(query)) + .validate() + .responseJSON { (response) in + switch response.result { + case .Success(let jsonData): + + guard let repos: [Repository] = Mapper().mapArray(jsonData) else { + observer.onError(ApiError.defaultError) + break + } + observer.onNext(repos) + observer.onCompleted() + case .Failure(let error): + let apiError = ApiError(error: error, data: response.data) ?? .defaultError + observer.onError(apiError) + } + } + return NopDisposable.instance + } + } +} \ No newline at end of file diff --git a/RxTutorials/View/Base.lproj/Main.storyboard b/RxTutorials/View/Base.lproj/Main.storyboard index 541d141..54cb32c 100644 --- a/RxTutorials/View/Base.lproj/Main.storyboard +++ b/RxTutorials/View/Base.lproj/Main.storyboard @@ -571,6 +571,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -744,5 +803,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RxTutorials/ViewModel/RepositoriesViewModel.swift b/RxTutorials/ViewModel/RepositoriesViewModel.swift new file mode 100644 index 0000000..d4905e2 --- /dev/null +++ b/RxTutorials/ViewModel/RepositoriesViewModel.swift @@ -0,0 +1,28 @@ + +// +// RepositoriesViewModel.swift +// RxTutorials +// +// Created by Abel Osorio on 8/8/16. +// Copyright © 2016 Abel Osorio. All rights reserved. +// + +import Foundation +import RxSwift +import RxCocoa + +struct RepositoriesViewModel { + + let searchText = Variable("") + let disposeBag = DisposeBag() + + lazy var data:Driver<[Repository]> = { + return self.searchText.asObservable() + .filter{$0.characters.count > 0} + .throttle(0.3,scheduler:MainScheduler.instance) + .flatMapLatest{ + RepositoriesAPI.searchRepositories(query: $0).catchErrorJustReturn([]) + }.asDriver(onErrorJustReturn:[]) + }() + +} \ No newline at end of file