├── Screenshots ├── rc-movie-list.png └── rc-movie-detail.png ├── Pods ├── Target Support Files │ ├── Unbox │ │ ├── Unbox.modulemap │ │ ├── Unbox-dummy.m │ │ ├── Unbox-prefix.pch │ │ ├── Unbox-umbrella.h │ │ ├── Unbox.xcconfig │ │ └── Info.plist │ ├── Alamofire │ │ ├── Alamofire.modulemap │ │ ├── Alamofire-dummy.m │ │ ├── Alamofire-prefix.pch │ │ ├── Alamofire-umbrella.h │ │ ├── Alamofire.xcconfig │ │ └── Info.plist │ ├── Pods-Commons │ │ ├── Pods-Commons.modulemap │ │ ├── Pods-Commons-dummy.m │ │ ├── Pods-Commons-umbrella.h │ │ ├── Pods-Commons.mock.xcconfig │ │ ├── Pods-Commons.debug.xcconfig │ │ ├── Pods-Commons.release.xcconfig │ │ ├── Info.plist │ │ └── Pods-Commons-acknowledgements.markdown │ ├── Pods-RedCarpetMVC │ │ ├── Pods-RedCarpetMVC.modulemap │ │ ├── Pods-RedCarpetMVC-dummy.m │ │ ├── Pods-RedCarpetMVC-umbrella.h │ │ ├── Pods-RedCarpetMVC.debug.xcconfig │ │ ├── Pods-RedCarpetMVC.mock.xcconfig │ │ ├── Pods-RedCarpetMVC.release.xcconfig │ │ ├── Info.plist │ │ └── Pods-RedCarpetMVC-acknowledgements.markdown │ ├── Pods-RedCarpetMVVM │ │ ├── Pods-RedCarpetMVVM.modulemap │ │ ├── Pods-RedCarpetMVVM-dummy.m │ │ ├── Pods-RedCarpetMVVM-umbrella.h │ │ ├── Pods-RedCarpetMVVM.mock.xcconfig │ │ ├── Pods-RedCarpetMVVM.debug.xcconfig │ │ ├── Pods-RedCarpetMVVM.release.xcconfig │ │ ├── Info.plist │ │ └── Pods-RedCarpetMVVM-acknowledgements.markdown │ ├── Pods-RedCarpetRedux │ │ ├── Pods-RedCarpetRedux.modulemap │ │ ├── Pods-RedCarpetRedux-dummy.m │ │ ├── Pods-RedCarpetRedux-umbrella.h │ │ ├── Pods-RedCarpetRedux.debug.xcconfig │ │ ├── Pods-RedCarpetRedux.mock.xcconfig │ │ ├── Pods-RedCarpetRedux.release.xcconfig │ │ ├── Info.plist │ │ └── Pods-RedCarpetRedux-acknowledgements.markdown │ ├── Pods-RedCarpetVIPER │ │ ├── Pods-RedCarpetVIPER.modulemap │ │ ├── Pods-RedCarpetVIPER-dummy.m │ │ ├── Pods-RedCarpetVIPER-umbrella.h │ │ ├── Pods-RedCarpetVIPER.debug.xcconfig │ │ ├── Pods-RedCarpetVIPER.mock.xcconfig │ │ ├── Pods-RedCarpetVIPER.release.xcconfig │ │ ├── Info.plist │ │ └── Pods-RedCarpetVIPER-acknowledgements.markdown │ └── Pods-RedCarpetAppleMVC │ │ ├── Pods-RedCarpetAppleMVC.modulemap │ │ ├── Pods-RedCarpetAppleMVC-dummy.m │ │ ├── Pods-RedCarpetAppleMVC-umbrella.h │ │ ├── Pods-RedCarpetAppleMVC.debug.xcconfig │ │ ├── Pods-RedCarpetAppleMVC.mock.xcconfig │ │ ├── Pods-RedCarpetAppleMVC.release.xcconfig │ │ ├── Info.plist │ │ └── Pods-RedCarpetAppleMVC-acknowledgements.markdown ├── Unbox │ ├── Sources │ │ ├── UnboxPathNode.swift │ │ ├── NSDictionary+Unbox.swift │ │ ├── Typealiases.swift │ │ ├── DateFormatter+Unbox.swift │ │ ├── NSArray+Unbox.swift │ │ ├── URL+Unbox.swift │ │ ├── UnboxableKey.swift │ │ ├── Int+Unbox.swift │ │ ├── CGFloat+Unbox.swift │ │ ├── UInt+Unbox.swift │ │ ├── Sequence+Unbox.swift │ │ ├── Float+Unbox.swift │ │ ├── Int32+Unbox.swift │ │ ├── Int64+Unbox.swift │ │ ├── String+Unbox.swift │ │ ├── Double+Unbox.swift │ │ ├── UInt32+Unbox.swift │ │ ├── UInt64+Unbox.swift │ │ ├── UnboxPath.swift │ │ ├── JSONSerialization+Unbox.swift │ │ ├── Decimal+Unbox.swift │ │ ├── UnboxableEnum.swift │ │ ├── UnboxContainer.swift │ │ ├── Unboxable.swift │ │ ├── Optional+Unbox.swift │ │ ├── Set+Unbox.swift │ │ ├── Bool+Unbox.swift │ │ ├── UnboxArrayContainer.swift │ │ ├── UnboxCollectionElementTransformer.swift │ │ ├── UnboxableByTransform.swift │ │ ├── UnboxCompatible.swift │ │ ├── UnboxableRawType.swift │ │ ├── UnboxError.swift │ │ ├── Array+Unbox.swift │ │ ├── Data+Unbox.swift │ │ ├── UnboxFormatter.swift │ │ ├── UnboxableWithContext.swift │ │ ├── UnboxPathError.swift │ │ ├── UnboxableCollection.swift │ │ └── Dictionary+Unbox.swift │ └── LICENSE ├── Manifest.lock └── Alamofire │ ├── LICENSE │ └── Source │ ├── DispatchQueue+Alamofire.swift │ └── Notifications.swift ├── RedCarpet.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ ├── Commons.xcscheme │ └── RedCarpetMVC.xcscheme ├── RedCarpet.xcworkspace └── contents.xcworkspacedata ├── Commons ├── Services │ ├── MovieServiceProtocol.swift │ ├── MovieResponse.swift │ ├── MovieSerializer.swift │ └── MovieService.swift ├── Helpers │ ├── Date+Helpers.swift │ ├── Array+Movie.swift │ └── Result.swift ├── Commons.h ├── Info.plist ├── DataModels │ ├── Movie.swift │ └── MoviePresentation.swift └── Mocking │ └── MockMovieService.swift ├── RedCarpetVIPER ├── MovieDetail │ ├── MovieDetailContracts.swift │ ├── MovieDetailPresenter.swift │ └── MovieDetailViewController.swift ├── MovieList │ ├── MovieListContracts.swift │ ├── MovieListRouter.swift │ ├── MovieListInteractor.swift │ ├── MovieListPresenter.swift │ └── MovieListViewController.swift ├── AppRouter.swift ├── Info.plist ├── Base.lproj │ └── LaunchScreen.storyboard ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json └── AppDelegate.swift ├── RedCarpetRedux ├── App │ ├── LoggerMiddleware.swift │ ├── MovieDetailLogic.swift │ ├── AppLogic.swift │ ├── NavigationLogic.swift │ └── MovieListLogic.swift ├── AppLauncher.swift ├── MovieDetail │ ├── MovieDetailPresenter.swift │ └── MovieDetailViewController.swift ├── Info.plist ├── Base.lproj │ └── LaunchScreen.storyboard ├── Navigation │ └── NavigationEngine.swift ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── MovieList │ ├── MovieListPresenter.swift │ └── MovieListViewController.swift └── AppDelegate.swift ├── RedCarpetMVVM ├── MovieDetail │ ├── MovieDetailViewModelContract.swift │ ├── MovieDetailViewModel.swift │ └── MovieDetailViewController.swift ├── MovieList │ ├── MovieListViewModelContract.swift │ └── MovieListViewModel.swift ├── AppRouter.swift ├── Info.plist ├── Base.lproj │ └── LaunchScreen.storyboard ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json └── AppDelegate.swift ├── Podfile.lock ├── RedCarpetMVC ├── AppLauncher.swift ├── MovieDetail │ ├── MovieDetailViewController.swift │ ├── MovieDetailView.swift │ └── MovieDetailView.xib ├── Info.plist ├── Base.lproj │ └── LaunchScreen.storyboard ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── MovieList │ ├── MovieListViewController.swift │ ├── MovieListView.swift │ └── MovieListView.xib └── AppDelegate.swift ├── Podfile ├── LICENSE.txt ├── .gitignore ├── RedCarpetAppleMVC ├── Info.plist ├── Base.lproj │ └── LaunchScreen.storyboard ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── MovieDetailViewController.swift └── AppDelegate.swift └── README.md /Screenshots/rc-movie-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gokselkoksal/RedCarpet/HEAD/Screenshots/rc-movie-list.png -------------------------------------------------------------------------------- /Screenshots/rc-movie-detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gokselkoksal/RedCarpet/HEAD/Screenshots/rc-movie-detail.png -------------------------------------------------------------------------------- /Pods/Target Support Files/Unbox/Unbox.modulemap: -------------------------------------------------------------------------------- 1 | framework module Unbox { 2 | umbrella header "Unbox-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Unbox/Unbox-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Unbox : NSObject 3 | @end 4 | @implementation PodsDummy_Unbox 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Alamofire/Alamofire.modulemap: -------------------------------------------------------------------------------- 1 | framework module Alamofire { 2 | umbrella header "Alamofire-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Alamofire/Alamofire-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Alamofire : NSObject 3 | @end 4 | @implementation PodsDummy_Alamofire 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-Commons/Pods-Commons.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_Commons { 2 | umbrella header "Pods-Commons-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-Commons/Pods-Commons-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_Commons : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_Commons 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetMVC/Pods-RedCarpetMVC.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_RedCarpetMVC { 2 | umbrella header "Pods-RedCarpetMVC-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetMVVM/Pods-RedCarpetMVVM.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_RedCarpetMVVM { 2 | umbrella header "Pods-RedCarpetMVVM-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetMVC/Pods-RedCarpetMVC-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_RedCarpetMVC : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_RedCarpetMVC 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetRedux/Pods-RedCarpetRedux.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_RedCarpetRedux { 2 | umbrella header "Pods-RedCarpetRedux-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetVIPER/Pods-RedCarpetVIPER.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_RedCarpetVIPER { 2 | umbrella header "Pods-RedCarpetVIPER-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetMVVM/Pods-RedCarpetMVVM-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_RedCarpetMVVM : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_RedCarpetMVVM 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetRedux/Pods-RedCarpetRedux-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_RedCarpetRedux : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_RedCarpetRedux 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetVIPER/Pods-RedCarpetVIPER-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_RedCarpetVIPER : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_RedCarpetVIPER 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetAppleMVC/Pods-RedCarpetAppleMVC.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_RedCarpetAppleMVC { 2 | umbrella header "Pods-RedCarpetAppleMVC-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetAppleMVC/Pods-RedCarpetAppleMVC-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_RedCarpetAppleMVC : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_RedCarpetAppleMVC 5 | @end 6 | -------------------------------------------------------------------------------- /RedCarpet.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Unbox/Unbox-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Alamofire/Alamofire-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/UnboxPathNode.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | internal protocol UnboxPathNode { 10 | func unboxPathValue(forKey key: String) -> Any? 11 | } 12 | -------------------------------------------------------------------------------- /RedCarpet.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Commons/Services/MovieServiceProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovieService.swift 3 | // Commons 4 | // 5 | // Created by Göksel Köksal on 7.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol MovieServiceProtocol { 12 | func fetchMovies(_ completion: @escaping (Result<[Movie]>) -> Void) 13 | } 14 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/NSDictionary+Unbox.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | #if !os(Linux) 10 | extension NSDictionary: UnboxPathNode { 11 | func unboxPathValue(forKey key: String) -> Any? { 12 | return self[key] 13 | } 14 | } 15 | #endif 16 | -------------------------------------------------------------------------------- /RedCarpetVIPER/MovieDetail/MovieDetailContracts.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovieDetailContracts.swift 3 | // RedCarpetVIPER 4 | // 5 | // Created by Göksel Köksal on 20.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Commons 11 | 12 | protocol MovieDetailPresenterProtocol { 13 | var moviePresentation: MovieDetailPresentation { get } 14 | } 15 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/Typealiases.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | /// Type alias defining what type of Dictionary that is Unboxable (valid JSON) 10 | public typealias UnboxableDictionary = [String : Any] 11 | 12 | internal typealias UnboxTransform = (Any) throws -> T? 13 | -------------------------------------------------------------------------------- /RedCarpetRedux/App/LoggerMiddleware.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoggerMiddleware.swift 3 | // RedCarpetRedux 4 | // 5 | // Created by Göksel Köksal on 29.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class LoggerMiddleware: Middleware { 12 | 13 | func process(event: Event, state: AppState) { 14 | print("EVENT:", event) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /RedCarpetMVVM/MovieDetail/MovieDetailViewModelContract.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovieDetailViewModelContract.swift 3 | // RedCarpetMVVM 4 | // 5 | // Created by Göksel Köksal on 16.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Commons 11 | 12 | protocol MovieDetailViewModelProtocol { 13 | var movie: Movie { get } 14 | init(movie: Movie) 15 | } 16 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Unbox/Unbox-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double UnboxVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char UnboxVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Alamofire/Alamofire-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double AlamofireVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char AlamofireVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-Commons/Pods-Commons-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_CommonsVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_CommonsVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/DateFormatter+Unbox.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | 8 | import Foundation 9 | 10 | /// Extension making `DateFormatter` usable as an UnboxFormatter 11 | extension DateFormatter: UnboxFormatter { 12 | public func format(unboxedValue: String) -> Date? { 13 | return self.date(from: unboxedValue) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/NSArray+Unbox.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSArray+UnboxPathNode.swift 3 | // Unbox 4 | // 5 | // Created by John Sundell on 2017-03-27. 6 | // Copyright © 2017 John Sundell. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | #if !os(Linux) 12 | extension NSArray: UnboxPathNode { 13 | func unboxPathValue(forKey key: String) -> Any? { 14 | return (self as Array).unboxPathValue(forKey: key) 15 | } 16 | } 17 | #endif 18 | -------------------------------------------------------------------------------- /Commons/Helpers/Date+Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Date+Helpers.swift 3 | // Commons 4 | // 5 | // Created by Göksel Köksal on 7.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Date { 12 | 13 | public func extract(_ component: Calendar.Component) -> Int { 14 | let calendar = Calendar.current 15 | return calendar.component(component, from: self) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetMVC/Pods-RedCarpetMVC-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_RedCarpetMVCVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_RedCarpetMVCVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetMVVM/Pods-RedCarpetMVVM-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_RedCarpetMVVMVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_RedCarpetMVVMVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetRedux/Pods-RedCarpetRedux-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_RedCarpetReduxVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_RedCarpetReduxVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetVIPER/Pods-RedCarpetVIPER-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_RedCarpetVIPERVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_RedCarpetVIPERVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /RedCarpetMVVM/MovieDetail/MovieDetailViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovieDetailViewModel.swift 3 | // RedCarpetMVVM 4 | // 5 | // Created by Göksel Köksal on 16.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Commons 11 | 12 | final class MovieDetailViewModel: MovieDetailViewModelProtocol { 13 | 14 | let movie: Movie 15 | 16 | init(movie: Movie) { 17 | self.movie = movie 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (4.7.3) 3 | - Unbox (3.0.0) 4 | 5 | DEPENDENCIES: 6 | - Alamofire (~> 4.7) 7 | - Unbox (~> 3.0) 8 | 9 | SPEC REPOS: 10 | https://github.com/cocoapods/specs.git: 11 | - Alamofire 12 | - Unbox 13 | 14 | SPEC CHECKSUMS: 15 | Alamofire: c7287b6e5d7da964a70935e5db17046b7fde6568 16 | Unbox: 9ed33b2a31f7a8a049f54b94d40c4a52a28c4d9d 17 | 18 | PODFILE CHECKSUM: a5bf787b870685a75933e897a73cf70fc41ebb3e 19 | 20 | COCOAPODS: 1.5.3 21 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/URL+Unbox.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | /// Extension making `URL` Unboxable by transform 10 | extension URL: UnboxableByTransform { 11 | public typealias UnboxRawValue = String 12 | 13 | public static func transform(unboxedValue: String) -> URL? { 14 | return URL(string: unboxedValue) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetAppleMVC/Pods-RedCarpetAppleMVC-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_RedCarpetAppleMVCVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_RedCarpetAppleMVCVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/UnboxableKey.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | /// Protocol used to enable any type to be transformed from a JSON key into a dictionary key 10 | public protocol UnboxableKey { 11 | /// Transform an unboxed key into a key that will be used in an unboxed dictionary 12 | static func transform(unboxedKey: String) -> Self? 13 | } 14 | -------------------------------------------------------------------------------- /Pods/Manifest.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (4.7.3) 3 | - Unbox (3.0.0) 4 | 5 | DEPENDENCIES: 6 | - Alamofire (~> 4.7) 7 | - Unbox (~> 3.0) 8 | 9 | SPEC REPOS: 10 | https://github.com/cocoapods/specs.git: 11 | - Alamofire 12 | - Unbox 13 | 14 | SPEC CHECKSUMS: 15 | Alamofire: c7287b6e5d7da964a70935e5db17046b7fde6568 16 | Unbox: 9ed33b2a31f7a8a049f54b94d40c4a52a28c4d9d 17 | 18 | PODFILE CHECKSUM: a5bf787b870685a75933e897a73cf70fc41ebb3e 19 | 20 | COCOAPODS: 1.5.3 21 | -------------------------------------------------------------------------------- /Commons/Services/MovieResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovieResponse.swift 3 | // Commons 4 | // 5 | // Created by Göksel Köksal on 7.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Unbox 11 | 12 | struct MovieResponse { 13 | let results: [Movie] 14 | } 15 | 16 | extension MovieResponse: Unboxable { 17 | 18 | init(unboxer: Unboxer) throws { 19 | self.results = try unboxer.unbox(keyPath: "feed.results") 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Alamofire/Alamofire.xcconfig: -------------------------------------------------------------------------------- 1 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Alamofire 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 4 | PODS_BUILD_DIR = ${BUILD_DIR} 5 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 6 | PODS_ROOT = ${SRCROOT} 7 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/Alamofire 8 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 9 | SKIP_INSTALL = YES 10 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/Int+Unbox.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | /// Extension making `Int` an Unboxable raw type 10 | extension Int: UnboxableRawType { 11 | public static func transform(unboxedNumber: NSNumber) -> Int? { 12 | return unboxedNumber.intValue 13 | } 14 | 15 | public static func transform(unboxedString: String) -> Int? { 16 | return Int(unboxedString) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /RedCarpetRedux/App/MovieDetailLogic.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovieDetailLogic.swift 3 | // RedCarpetRedux 4 | // 5 | // Created by Göksel Köksal on 4.11.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Commons 11 | 12 | // MARK: State 13 | 14 | struct MovieDetailState: State { 15 | var movie: Movie 16 | } 17 | 18 | // MARK: Reducer 19 | 20 | extension MovieDetailState { 21 | 22 | mutating func react(to event: Event) { 23 | // No events to handle. 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/CGFloat+Unbox.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | #if !os(Linux) 10 | import CoreGraphics 11 | 12 | /// Extension making `CGFloat` an Unboxable raw type 13 | extension CGFloat: UnboxableByTransform { 14 | public typealias UnboxRawValue = Double 15 | 16 | public static func transform(unboxedValue: Double) -> CGFloat? { 17 | return CGFloat(unboxedValue) 18 | } 19 | } 20 | #endif 21 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/UInt+Unbox.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | /// Extension making UInt an Unboxable raw type 10 | extension UInt: UnboxableRawType { 11 | public static func transform(unboxedNumber: NSNumber) -> UInt? { 12 | return unboxedNumber.uintValue 13 | } 14 | 15 | public static func transform(unboxedString: String) -> UInt? { 16 | return UInt(unboxedString) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/Sequence+Unbox.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | internal extension Sequence { 10 | func map(allowInvalidElements: Bool, transform: (Iterator.Element) throws -> T) throws -> [T] { 11 | if !allowInvalidElements { 12 | return try self.map(transform) 13 | } 14 | 15 | return compactMap { 16 | return try? transform($0) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /RedCarpetVIPER/MovieDetail/MovieDetailPresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovieDetailPresenter.swift 3 | // RedCarpetVIPER 4 | // 5 | // Created by Göksel Köksal on 20.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Commons 11 | 12 | class MovieDetailPresenter: MovieDetailPresenterProtocol { 13 | 14 | let moviePresentation: MovieDetailPresentation 15 | 16 | init(moviePresentation: MovieDetailPresentation) { 17 | self.moviePresentation = moviePresentation 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/Float+Unbox.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | /// Extension making `Float` an Unboxable raw type 10 | extension Float: UnboxableRawType { 11 | public static func transform(unboxedNumber: NSNumber) -> Float? { 12 | return unboxedNumber.floatValue 13 | } 14 | 15 | public static func transform(unboxedString: String) -> Float? { 16 | return Float(unboxedString) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/Int32+Unbox.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | /// Extension making `Int32` an Unboxable raw type 10 | extension Int32: UnboxableRawType { 11 | public static func transform(unboxedNumber: NSNumber) -> Int32? { 12 | return unboxedNumber.int32Value 13 | } 14 | 15 | public static func transform(unboxedString: String) -> Int32? { 16 | return Int32(unboxedString) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/Int64+Unbox.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | /// Extension making `Int64` an Unboxable raw type 10 | extension Int64: UnboxableRawType { 11 | public static func transform(unboxedNumber: NSNumber) -> Int64? { 12 | return unboxedNumber.int64Value 13 | } 14 | 15 | public static func transform(unboxedString: String) -> Int64? { 16 | return Int64(unboxedString) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/String+Unbox.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | /// Extension making `String` an Unboxable raw type 10 | extension String: UnboxableRawType { 11 | public static func transform(unboxedNumber: NSNumber) -> String? { 12 | return unboxedNumber.stringValue 13 | } 14 | 15 | public static func transform(unboxedString: String) -> String? { 16 | return unboxedString 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/Double+Unbox.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | /// Extension making `Double` an Unboxable raw type 10 | extension Double: UnboxableRawType { 11 | public static func transform(unboxedNumber: NSNumber) -> Double? { 12 | return unboxedNumber.doubleValue 13 | } 14 | 15 | public static func transform(unboxedString: String) -> Double? { 16 | return Double(unboxedString) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/UInt32+Unbox.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | /// Extension making `UInt32` an Unboxable raw type 10 | extension UInt32: UnboxableRawType { 11 | public static func transform(unboxedNumber: NSNumber) -> UInt32? { 12 | return unboxedNumber.uint32Value 13 | } 14 | 15 | public static func transform(unboxedString: String) -> UInt32? { 16 | return UInt32(unboxedString) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/UInt64+Unbox.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | /// Extension making `UInt64` an Unboxable raw type 10 | extension UInt64: UnboxableRawType { 11 | public static func transform(unboxedNumber: NSNumber) -> UInt64? { 12 | return unboxedNumber.uint64Value 13 | } 14 | 15 | public static func transform(unboxedString: String) -> UInt64? { 16 | return UInt64(unboxedString) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/UnboxPath.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | internal enum UnboxPath { 10 | case key(String) 11 | case keyPath(String) 12 | } 13 | 14 | extension UnboxPath: CustomStringConvertible { 15 | var description: String { 16 | switch self { 17 | case .key(let key): 18 | return key 19 | case .keyPath(let keyPath): 20 | return keyPath 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Commons/Commons.h: -------------------------------------------------------------------------------- 1 | // 2 | // Commons.h 3 | // Commons 4 | // 5 | // Created by Göksel Köksal on 7.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for Commons. 12 | FOUNDATION_EXPORT double CommonsVersionNumber; 13 | 14 | //! Project version string for Commons. 15 | FOUNDATION_EXPORT const unsigned char CommonsVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/JSONSerialization+Unbox.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | internal extension JSONSerialization { 10 | static func unbox(data: Data, options: ReadingOptions = []) throws -> T { 11 | do { 12 | return try (self.jsonObject(with: data, options: options) as? T).orThrow(UnboxError.invalidData) 13 | } catch { 14 | throw UnboxError.invalidData 15 | } 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Unbox/Unbox.xcconfig: -------------------------------------------------------------------------------- 1 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Unbox 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | OTHER_LDFLAGS = -framework "CoreGraphics" -framework "Foundation" 4 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 5 | PODS_BUILD_DIR = ${BUILD_DIR} 6 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 7 | PODS_ROOT = ${SRCROOT} 8 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/Unbox 9 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 10 | SKIP_INSTALL = YES 11 | -------------------------------------------------------------------------------- /RedCarpetRedux/AppLauncher.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppLauncher.swift 3 | // RedCarpetRedux 4 | // 5 | // Created by Göksel Köksal on 29.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class AppLauncher { 12 | 13 | static let shared = AppLauncher() 14 | 15 | var navigationEngine: NavigationEngine! 16 | 17 | func initialViewController() -> UIViewController { 18 | let nc = UINavigationController() 19 | navigationEngine = NavigationEngine(rootNavigationController: nc) 20 | return nc 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/Decimal+Unbox.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | /// Extension making `Decimal` an Unboxable raw type 10 | extension Decimal: UnboxableRawType { 11 | public static func transform(unboxedNumber: NSNumber) -> Decimal? { 12 | return Decimal(string: unboxedNumber.stringValue) 13 | } 14 | 15 | public static func transform(unboxedString unboxedValue: String) -> Decimal? { 16 | return Decimal(string: unboxedValue) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/UnboxableEnum.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | /// Protocol used to enable an enum to be directly unboxable 10 | public protocol UnboxableEnum: RawRepresentable, UnboxCompatible {} 11 | 12 | /// Default implementation of `UnboxCompatible` for enums 13 | public extension UnboxableEnum { 14 | static func unbox(value: Any, allowInvalidCollectionElements: Bool) throws -> Self? { 15 | return (value as? RawValue).map(self.init) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/UnboxContainer.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | internal struct UnboxContainer: UnboxableWithContext { 10 | let model: T 11 | 12 | init(unboxer: Unboxer, context: UnboxPath) throws { 13 | switch context { 14 | case .key(let key): 15 | self.model = try unboxer.unbox(key: key) 16 | case .keyPath(let keyPath): 17 | self.model = try unboxer.unbox(keyPath: keyPath) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/Unboxable.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | /// Protocol used to declare a model as being Unboxable, for use with the unbox() function 10 | public protocol Unboxable { 11 | /// Initialize an instance of this model by unboxing a dictionary using an Unboxer 12 | init(unboxer: Unboxer) throws 13 | } 14 | 15 | internal extension Unboxable { 16 | static func makeTransform() -> UnboxTransform { 17 | return { try ($0 as? UnboxableDictionary).map(unbox) } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /RedCarpetMVC/AppLauncher.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppLauncher.swift 3 | // RedCarpetMVC 4 | // 5 | // Created by Göksel Köksal on 7.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Commons 11 | 12 | final class AppLauncher { 13 | 14 | static func initialViewController() -> UIViewController { 15 | let vc = MovieListViewController() 16 | #if MOCK 17 | vc.service = MockMovieService() 18 | #else 19 | vc.service = MovieService() 20 | #endif 21 | let nc = UINavigationController(rootViewController: vc) 22 | return nc 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /RedCarpetMVVM/MovieList/MovieListViewModelContract.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovieListViewModelContract.swift 3 | // RedCarpetMVVM 4 | // 5 | // Created by Göksel Köksal on 16.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Commons 11 | 12 | enum MovieListViewModelChange { 13 | case didUpdateMovies 14 | case didEncounterError(Error) 15 | case didChangeNetworkActivityStatus(Bool) 16 | } 17 | 18 | protocol MovieListViewModelProtocol { 19 | 20 | var movies: [Movie] { get } 21 | var changeHandler: ((MovieListViewModelChange) -> Void)? { get set } 22 | 23 | func fetchMovies() 24 | } 25 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/Optional+Unbox.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | internal extension Optional { 10 | func map(_ transform: (Wrapped) throws -> T?) rethrows -> T? { 11 | guard let value = self else { 12 | return nil 13 | } 14 | 15 | return try transform(value) 16 | } 17 | 18 | func orThrow(_ errorClosure: @autoclosure () -> E) throws -> Wrapped { 19 | guard let value = self else { 20 | throw errorClosure() 21 | } 22 | 23 | return value 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/Set+Unbox.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | /// Extension making `Set` an unboxable collection 10 | extension Set: UnboxableCollection { 11 | public typealias UnboxValue = Element 12 | 13 | public static func unbox(value: Any, allowInvalidElements: Bool, transformer: T) throws -> Set? where T.UnboxedElement == UnboxValue { 14 | guard let array = try [UnboxValue].unbox(value: value, allowInvalidElements: allowInvalidElements, transformer: transformer) else { 15 | return nil 16 | } 17 | 18 | return Set(array) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/Bool+Unbox.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | /// Extension making `Bool` an Unboxable raw type 10 | extension Bool: UnboxableRawType { 11 | public static func transform(unboxedNumber: NSNumber) -> Bool? { 12 | return unboxedNumber.boolValue 13 | } 14 | 15 | public static func transform(unboxedString: String) -> Bool? { 16 | switch unboxedString.lowercased() { 17 | case "true", "t", "y", "yes": 18 | return true 19 | case "false", "f" , "n", "no": 20 | return false 21 | default: 22 | return nil 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | target 'Commons' do 5 | use_frameworks! 6 | platform :ios, '11.0' 7 | 8 | pod 'Alamofire', '~> 4.7' 9 | pod 'Unbox', '~> 3.0' 10 | end 11 | 12 | target 'RedCarpetMVC' do 13 | use_frameworks! 14 | platform :ios, '11.0' 15 | end 16 | 17 | target 'RedCarpetAppleMVC' do 18 | use_frameworks! 19 | platform :ios, '11.0' 20 | end 21 | 22 | target 'RedCarpetMVVM' do 23 | use_frameworks! 24 | platform :ios, '11.0' 25 | end 26 | 27 | target 'RedCarpetVIPER' do 28 | use_frameworks! 29 | platform :ios, '11.0' 30 | end 31 | 32 | target 'RedCarpetRedux' do 33 | use_frameworks! 34 | platform :ios, '11.0' 35 | end 36 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/UnboxArrayContainer.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | internal struct UnboxArrayContainer: UnboxableWithContext { 10 | let models: [T] 11 | 12 | init(unboxer: Unboxer, context: (path: UnboxPath, allowInvalidElements: Bool)) throws { 13 | switch context.path { 14 | case .key(let key): 15 | self.models = try unboxer.unbox(key: key, allowInvalidElements: context.allowInvalidElements) 16 | case .keyPath(let keyPath): 17 | self.models = try unboxer.unbox(keyPath: keyPath, allowInvalidElements: context.allowInvalidElements) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /RedCarpetMVC/MovieDetail/MovieDetailViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovieDetailViewController.swift 3 | // RedCarpetMVC 4 | // 5 | // Created by Göksel Köksal on 7.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Commons 11 | 12 | final class MovieDetailViewController: UIViewController { 13 | 14 | var movie: Movie! 15 | 16 | override func loadView() { 17 | let myView = MovieDetailView.instantiate() 18 | self.view = myView 19 | } 20 | 21 | var myView: MovieDetailView { 22 | return view as! MovieDetailView 23 | } 24 | 25 | override func viewDidLoad() { 26 | super.viewDidLoad() 27 | title = "Movie Detail" 28 | myView.movie = movie 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Commons/Helpers/Array+Movie.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Array+Movie.swift 3 | // Commons 4 | // 5 | // Created by Göksel Köksal on 7.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Array where Element == Movie { 12 | 13 | func filter(withKeyword keyword: String) -> [Element] { 14 | let key = keyword.lowercased() 15 | 16 | func check(_ word: String) -> Bool { 17 | return word.lowercased().contains(key) 18 | } 19 | 20 | return self.filter { (movie) -> Bool in 21 | let nameFlag = check(movie.name) 22 | let artistNameFlag = check(movie.artistName) 23 | 24 | return nameFlag || artistNameFlag 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/UnboxCollectionElementTransformer.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | /// Protocol used to unbox an element in a collection. Unbox provides default implementations of this protocol. 10 | public protocol UnboxCollectionElementTransformer { 11 | /// The raw element type that this transformer expects as input 12 | associatedtype UnboxRawElement 13 | /// The unboxed element type that this transformer outputs 14 | associatedtype UnboxedElement 15 | 16 | /// Unbox an element from a collection, optionally allowing invalid elements for nested collections 17 | func unbox(element: UnboxRawElement, allowInvalidCollectionElements: Bool) throws -> UnboxedElement? 18 | } 19 | -------------------------------------------------------------------------------- /Commons/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-Commons/Pods-Commons.mock.xcconfig: -------------------------------------------------------------------------------- 1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/Unbox" 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' '@executable_path/../../Frameworks' 4 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Unbox/Unbox.framework/Headers" 5 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "Unbox" 6 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 7 | PODS_BUILD_DIR = ${BUILD_DIR} 8 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 9 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 10 | PODS_ROOT = ${SRCROOT}/Pods 11 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-Commons/Pods-Commons.debug.xcconfig: -------------------------------------------------------------------------------- 1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/Unbox" 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' '@executable_path/../../Frameworks' 4 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Unbox/Unbox.framework/Headers" 5 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "Unbox" 6 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 7 | PODS_BUILD_DIR = ${BUILD_DIR} 8 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 9 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 10 | PODS_ROOT = ${SRCROOT}/Pods 11 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-Commons/Pods-Commons.release.xcconfig: -------------------------------------------------------------------------------- 1 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/Unbox" 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' '@executable_path/../../Frameworks' 4 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Unbox/Unbox.framework/Headers" 5 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "Unbox" 6 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 7 | PODS_BUILD_DIR = ${BUILD_DIR} 8 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 9 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 10 | PODS_ROOT = ${SRCROOT}/Pods 11 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetMVC/Pods-RedCarpetMVC.debug.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/Unbox" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Unbox/Unbox.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "Unbox" 7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetMVC/Pods-RedCarpetMVC.mock.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/Unbox" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Unbox/Unbox.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "Unbox" 7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetMVVM/Pods-RedCarpetMVVM.mock.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/Unbox" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Unbox/Unbox.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "Unbox" 7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetMVC/Pods-RedCarpetMVC.release.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/Unbox" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Unbox/Unbox.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "Unbox" 7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetMVVM/Pods-RedCarpetMVVM.debug.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/Unbox" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Unbox/Unbox.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "Unbox" 7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetMVVM/Pods-RedCarpetMVVM.release.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/Unbox" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Unbox/Unbox.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "Unbox" 7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetRedux/Pods-RedCarpetRedux.debug.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/Unbox" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Unbox/Unbox.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "Unbox" 7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetRedux/Pods-RedCarpetRedux.mock.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/Unbox" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Unbox/Unbox.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "Unbox" 7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetRedux/Pods-RedCarpetRedux.release.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/Unbox" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Unbox/Unbox.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "Unbox" 7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetVIPER/Pods-RedCarpetVIPER.debug.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/Unbox" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Unbox/Unbox.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "Unbox" 7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetVIPER/Pods-RedCarpetVIPER.mock.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/Unbox" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Unbox/Unbox.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "Unbox" 7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetVIPER/Pods-RedCarpetVIPER.release.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/Unbox" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Unbox/Unbox.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "Unbox" 7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetAppleMVC/Pods-RedCarpetAppleMVC.debug.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/Unbox" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Unbox/Unbox.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "Unbox" 7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetAppleMVC/Pods-RedCarpetAppleMVC.mock.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/Unbox" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Unbox/Unbox.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "Unbox" 7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetAppleMVC/Pods-RedCarpetAppleMVC.release.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire" "${PODS_CONFIGURATION_BUILD_DIR}/Unbox" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Alamofire/Alamofire.framework/Headers" -iquote "${PODS_CONFIGURATION_BUILD_DIR}/Unbox/Unbox.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "Alamofire" -framework "Unbox" 7 | OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" 8 | PODS_BUILD_DIR = ${BUILD_DIR} 9 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 10 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 11 | PODS_ROOT = ${SRCROOT}/Pods 12 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/UnboxableByTransform.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | /// Protocol used to enable any type as being unboxable, by transforming a raw value 10 | public protocol UnboxableByTransform: UnboxCompatible { 11 | /// The type of raw value that this type can be transformed from. Must be a valid JSON type. 12 | associatedtype UnboxRawValue 13 | 14 | /// Attempt to transform a raw unboxed value into an instance of this type 15 | static func transform(unboxedValue: UnboxRawValue) -> Self? 16 | } 17 | 18 | /// Default implementation of `UnboxCompatible` for transformable types 19 | public extension UnboxableByTransform { 20 | static func unbox(value: Any, allowInvalidCollectionElements: Bool) throws -> Self? { 21 | return (value as? UnboxRawValue).map(self.transform) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Unbox/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 3.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Alamofire/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 4.7.3 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-Commons/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetMVC/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetAppleMVC/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetMVVM/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetRedux/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetVIPER/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | ${PRODUCT_BUNDLE_IDENTIFIER} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Commons/Services/MovieSerializer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovieSerializer.swift 3 | // Commons 4 | // 5 | // Created by Göksel Köksal on 7.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Unbox 11 | 12 | class MovieSerializer { 13 | 14 | enum Error: Swift.Error { 15 | case invalidResponse 16 | } 17 | 18 | static let shared = MovieSerializer() 19 | 20 | func serialize(json: Any) -> Result<[Movie]> { 21 | do { 22 | guard let json = json as? [String: Any] else { 23 | let result = Result<[Movie]>.failure(Error.invalidResponse) 24 | return result 25 | } 26 | let response: MovieResponse = try unbox(dictionary: json) 27 | let movies = response.results 28 | let result = Result<[Movie]>.success(movies) 29 | return result 30 | } catch { 31 | let result = Result<[Movie]>.failure(Error.invalidResponse) 32 | return result 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /RedCarpetVIPER/MovieList/MovieListContracts.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovieListContracts.swift 3 | // RedCarpetVIPER 4 | // 5 | // Created by Göksel Köksal on 16.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Commons 11 | 12 | protocol MovieListInteractorProtocol: class { 13 | var output: MovieListInteractorOutput? { get set } 14 | func fetchMovies() 15 | } 16 | 17 | protocol MovieListInteractorOutput: class { 18 | func receiveMovies(_ movies: [Movie]) 19 | func receiveNetworkActivityStatus(isActive: Bool) 20 | func receiveError(_ error: Error) 21 | } 22 | 23 | protocol MovieListPresenterProtocol: class { 24 | func didLoad() 25 | func didTapOnMovie(at index: Int) 26 | } 27 | 28 | protocol MovieListViewProtocol: class { 29 | func updateMoviePresentations(_ presentations: [MovieListPresentation]) 30 | func handleError(_ error: Error) 31 | func setLoading(_ flag: Bool) 32 | } 33 | 34 | protocol MovieListRouterProtocol: class { 35 | func showMovieDetail(with presentation: MovieDetailPresentation) 36 | } 37 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/UnboxCompatible.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | /// Protocol that types that can be used in an unboxing process must conform to. You don't conform to this protocol yourself. 10 | public protocol UnboxCompatible { 11 | /// Unbox a value, or either throw or return nil if unboxing couldn't be performed 12 | static func unbox(value: Any, allowInvalidCollectionElements: Bool) throws -> Self? 13 | } 14 | 15 | // MARK: - Internal extensions 16 | 17 | internal extension UnboxCompatible { 18 | static func unbox(value: Any) throws -> Self? { 19 | return try self.unbox(value: value, allowInvalidCollectionElements: false) 20 | } 21 | } 22 | 23 | internal extension UnboxCompatible where Self: Collection { 24 | static func makeTransform(allowInvalidElements: Bool) -> UnboxTransform { 25 | return { 26 | try self.unbox(value: $0, allowInvalidCollectionElements: allowInvalidElements) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Commons/Services/MovieService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovieService.swift 3 | // Commons 4 | // 5 | // Created by Göksel Köksal on 7.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Alamofire 11 | import Unbox 12 | 13 | public class MovieService: MovieServiceProtocol { 14 | 15 | private let session: SessionManager 16 | 17 | public init() { 18 | self.session = SessionManager.default 19 | } 20 | 21 | public func fetchMovies(_ completion: @escaping (Result<[Movie]>) -> Void) { 22 | let urlString = "https://rss.itunes.apple.com/api/v1/us/movies/top-movies/all/25/explicit.json" 23 | 24 | session.request(urlString).responseJSON { (response) in 25 | switch response.result { 26 | case .success(let rawJSON): 27 | let result = MovieSerializer.shared.serialize(json: rawJSON) 28 | completion(result) 29 | case .failure(let error): 30 | let result = Result<[Movie]>.failure(error) 31 | completion(result) 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Göksel Köksal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /RedCarpetVIPER/MovieList/MovieListRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovieListRouter.swift 3 | // RedCarpetVIPER 4 | // 5 | // Created by Göksel Köksal on 16.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Commons 11 | 12 | final class MovieListRouter: MovieListRouterProtocol { 13 | 14 | private weak var view: (UIViewController & MovieListViewProtocol)? 15 | 16 | init(view: (UIViewController & MovieListViewProtocol)) { 17 | self.view = view 18 | } 19 | 20 | func showMovieDetail(with presentation: MovieDetailPresentation) { 21 | let storyboard = UIStoryboard(name: "Main", bundle: nil) 22 | let id = String(describing: MovieDetailViewController.self) 23 | 24 | guard let vc = storyboard.instantiateViewController(withIdentifier: id) as? MovieDetailViewController else { 25 | return 26 | } 27 | 28 | let presenter = MovieDetailPresenter(moviePresentation: presentation) 29 | vc.presenter = presenter 30 | 31 | view?.navigationController?.pushViewController(vc, animated: true) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Pods/Unbox/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 John Sundell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Pods/Alamofire/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /RedCarpetVIPER/MovieList/MovieListInteractor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovieListInteractor.swift 3 | // RedCarpetVIPER 4 | // 5 | // Created by Göksel Köksal on 16.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Commons 11 | 12 | final class MovieListInteractor: MovieListInteractorProtocol { 13 | 14 | let service: MovieServiceProtocol 15 | weak var output: MovieListInteractorOutput? 16 | 17 | init(service: MovieServiceProtocol) { 18 | self.service = service 19 | } 20 | 21 | func fetchMovies() { 22 | output?.receiveNetworkActivityStatus(isActive: true) 23 | 24 | service.fetchMovies { [weak self] (result) in 25 | guard let strongSelf = self else { return } 26 | 27 | strongSelf.output?.receiveNetworkActivityStatus(isActive: false) 28 | 29 | switch result { 30 | case .success(let movies): 31 | strongSelf.output?.receiveMovies(movies) 32 | case .failure(let error): 33 | strongSelf.output?.receiveError(error) 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/UnboxableRawType.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | /// Protocol used to enable a raw type (such as `Int` or `String`) for Unboxing 10 | public protocol UnboxableRawType: UnboxCompatible { 11 | /// Transform an instance of this type from an unboxed number 12 | static func transform(unboxedNumber: NSNumber) -> Self? 13 | /// Transform an instance of this type from an unboxed string 14 | static func transform(unboxedString: String) -> Self? 15 | } 16 | 17 | // Default implementation of `UnboxCompatible` for raw types 18 | public extension UnboxableRawType { 19 | static func unbox(value: Any, allowInvalidCollectionElements: Bool) throws -> Self? { 20 | if let matchedValue = value as? Self { 21 | return matchedValue 22 | } 23 | 24 | if let string = value as? String { 25 | return self.transform(unboxedString: string) 26 | } 27 | 28 | if let number = value as? NSNumber { 29 | return self.transform(unboxedNumber: number) 30 | } 31 | 32 | return nil 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/UnboxError.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | /// Error type that Unbox throws in case an unrecoverable error was encountered 10 | public enum UnboxError: Error { 11 | /// Invalid data was provided when calling unbox(data:...) 12 | case invalidData 13 | /// Custom unboxing failed, either by throwing or returning `nil` 14 | case customUnboxingFailed 15 | /// An error occurred while unboxing a value for a path (contains the underlying path error, and the path) 16 | case pathError(UnboxPathError, String) 17 | } 18 | 19 | /// Extension making `UnboxError` conform to `CustomStringConvertible` 20 | extension UnboxError: CustomStringConvertible { 21 | public var description: String { 22 | switch self { 23 | case .invalidData: 24 | return "[UnboxError] Invalid data." 25 | case .customUnboxingFailed: 26 | return "[UnboxError] Custom unboxing failed." 27 | case .pathError(let error, let path): 28 | return "[UnboxError] An error occurred while unboxing path \"\(path)\": \(error)" 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /RedCarpetMVVM/AppRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppRouter.swift 3 | // RedCarpetMVVM 4 | // 5 | // Created by Göksel Köksal on 18.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Commons 11 | 12 | final class AppRouter { 13 | 14 | static let shared = AppRouter() 15 | 16 | func configureInitialViewController(_ viewController: UIViewController) { 17 | let initialVC: UIViewController 18 | 19 | if let nc = viewController as? UINavigationController { 20 | initialVC = nc.viewControllers.first! // Can't recover if nil. 21 | } else { 22 | initialVC = viewController 23 | } 24 | 25 | guard let vc = initialVC as? MovieListViewController else { 26 | fatalError() 27 | } 28 | 29 | configureMovieListViewController(vc) 30 | } 31 | 32 | private func configureMovieListViewController(_ vc: MovieListViewController) { 33 | #if MOCK 34 | let service = MockMovieService() 35 | #else 36 | let service = MovieService() 37 | #endif 38 | 39 | let viewModel = MovieListViewModel(service: service) 40 | vc.viewModel = viewModel 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /RedCarpetRedux/MovieDetail/MovieDetailPresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovieDetailPresenter.swift 3 | // RedCarpetRedux 4 | // 5 | // Created by Göksel Köksal on 29.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Commons 11 | 12 | protocol MovieDetailPresenterProtocol { 13 | var moviePresentation: MovieDetailPresentation { get } 14 | func willAppear() 15 | func didDisappear() 16 | func didClose() 17 | } 18 | 19 | class MovieDetailPresenter: MovieDetailPresenterProtocol { 20 | 21 | let moviePresentation: MovieDetailPresentation 22 | 23 | init(movie: Movie) { 24 | self.moviePresentation = MovieDetailPresentation(movie: movie) 25 | } 26 | 27 | func willAppear() { 28 | core.add(subscriber: self, notifyOnQueue: .main, selector: { $0.movieDetailState as Any }) 29 | } 30 | 31 | func didDisappear() { 32 | core.remove(subscriber: self) 33 | } 34 | 35 | func didClose() { 36 | core.fire(event: NavigationEvent.didCloseDetailScreen) 37 | } 38 | } 39 | 40 | extension MovieDetailPresenter: Subscriber { 41 | 42 | func update(with state: MovieDetailState) { 43 | // No movie detail state changes to handle for now. 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Commons/DataModels/Movie.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Movie.swift 3 | // Commons 4 | // 5 | // Created by Göksel Köksal on 7.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Unbox 11 | 12 | public struct Movie { 13 | 14 | public let id: String 15 | public let artistName: String 16 | public let artworkURL: URL 17 | public let name: String 18 | public let releaseDate: Date 19 | public let copyright: String? 20 | 21 | static let releaseDateFormatter: DateFormatter = { 22 | let formatter = DateFormatter() 23 | formatter.dateFormat = "YYYY-MM-dd" 24 | return formatter 25 | }() 26 | } 27 | 28 | extension Movie: CustomStringConvertible { 29 | 30 | public var description: String { 31 | return name 32 | } 33 | } 34 | 35 | extension Movie: Unboxable { 36 | 37 | public init(unboxer: Unboxer) throws { 38 | self.id = try unboxer.unbox(key: "id") 39 | self.artistName = try unboxer.unbox(key: "artistName") 40 | self.artworkURL = try unboxer.unbox(key: "artworkUrl100") 41 | self.name = try unboxer.unbox(key: "name") 42 | self.releaseDate = try unboxer.unbox(key: "releaseDate", formatter: Movie.releaseDateFormatter) 43 | self.copyright = unboxer.unbox(key: "copyright") 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Commons/Mocking/MockMovieService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MockMovieService.swift 3 | // Commons 4 | // 5 | // Created by Göksel Köksal on 7.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class MockMovieService: MovieServiceProtocol { 12 | 13 | public enum Error: Swift.Error { 14 | case loadFailed 15 | } 16 | 17 | public init() { 18 | // Noop. 19 | } 20 | 21 | public func fetchMovies(_ completion: @escaping (Result<[Movie]>) -> Void) { 22 | guard let json = loadMockJSON() else { 23 | completion(Result<[Movie]>.failure(Error.loadFailed)) 24 | return 25 | } 26 | 27 | let result = MovieSerializer.shared.serialize(json: json) 28 | completion(result) 29 | } 30 | 31 | private func loadMockJSON() -> Any? { 32 | let bundle = Bundle(for: type(of: self)) 33 | 34 | guard let url = bundle.url(forResource: "movies-response", withExtension: "json") else { 35 | return nil 36 | } 37 | 38 | do { 39 | let data = try Data(contentsOf: url) 40 | let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments) 41 | return json 42 | } catch { 43 | return nil 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/Array+Unbox.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | /// Extension making `Array` an unboxable collection 10 | extension Array: UnboxableCollection { 11 | public typealias UnboxValue = Element 12 | 13 | public static func unbox(value: Any, allowInvalidElements: Bool, transformer: T) throws -> Array? where T.UnboxedElement == UnboxValue { 14 | guard let array = value as? [T.UnboxRawElement] else { 15 | return nil 16 | } 17 | 18 | return try array.enumerated().map(allowInvalidElements: allowInvalidElements) { index, element in 19 | let unboxedElement = try transformer.unbox(element: element, allowInvalidCollectionElements: allowInvalidElements) 20 | return try unboxedElement.orThrow(UnboxPathError.invalidArrayElement(element, index)) 21 | } 22 | } 23 | } 24 | 25 | /// Extension making `Array` an unbox path node 26 | extension Array: UnboxPathNode { 27 | func unboxPathValue(forKey key: String) -> Any? { 28 | guard let index = Int(key) else { 29 | return nil 30 | } 31 | 32 | if index >= self.count { 33 | return nil 34 | } 35 | 36 | return self[index] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /RedCarpetMVVM/MovieList/MovieListViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovieListViewModel.swift 3 | // RedCarpetMVVM 4 | // 5 | // Created by Göksel Köksal on 16.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Commons 11 | 12 | final class MovieListViewModel: MovieListViewModelProtocol { 13 | 14 | private(set) var movies: [Movie] = [] 15 | private let service: MovieServiceProtocol 16 | var changeHandler: ((MovieListViewModelChange) -> Void)? 17 | 18 | init(service: MovieServiceProtocol) { 19 | self.service = service 20 | } 21 | 22 | func fetchMovies() { 23 | emit(.didChangeNetworkActivityStatus(true)) 24 | 25 | service.fetchMovies { [weak self] (result) in 26 | guard let strongSelf = self else { return } 27 | 28 | strongSelf.emit(.didChangeNetworkActivityStatus(false)) 29 | 30 | switch result { 31 | case .success(let movies): 32 | strongSelf.movies = movies 33 | strongSelf.emit(.didUpdateMovies) 34 | case .failure(let error): 35 | strongSelf.emit(.didEncounterError(error)) 36 | } 37 | } 38 | } 39 | 40 | private func emit(_ change: MovieListViewModelChange) { 41 | changeHandler?(change) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /RedCarpetRedux/App/AppLogic.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppLogic.swift 3 | // RedCarpetRedux 4 | // 5 | // Created by Göksel Köksal on 29.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | #if DEBUG 12 | private let middlewares: [AnyMiddleware] = [LoggerMiddleware()] 13 | #else 14 | private let middlewares: [AnyMiddleware] = [] 15 | #endif 16 | 17 | let core = Core( 18 | state: AppState(), 19 | middlewares: middlewares 20 | ) 21 | 22 | struct AppState: State { 23 | var navigationState: NavigationState = NavigationState(event: NavigationEvent.showMovieList) 24 | var movieListState: MovieListState = MovieListState() 25 | var movieDetailState: MovieDetailState? 26 | } 27 | 28 | extension AppState { 29 | 30 | mutating func react(to event: Event) { 31 | if let event = event as? NavigationEvent { 32 | switch event { 33 | case .showMovieList: 34 | break // Noop. 35 | case .showDetailScreen(movie: let movie): 36 | movieDetailState = MovieDetailState(movie: movie) 37 | case .didCloseDetailScreen: 38 | movieDetailState = nil 39 | } 40 | navigationState.react(to: event) 41 | } 42 | 43 | movieListState.react(to: event) 44 | movieDetailState?.react(to: event) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Commons/DataModels/MoviePresentation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MoviePresentation.swift 3 | // Commons 4 | // 5 | // Created by Göksel Köksal on 7.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct MovieListPresentation { 12 | 13 | public let title: String 14 | public let detail: String 15 | 16 | public init(movie: Movie) { 17 | let year = movie.releaseDate.extract(.year) 18 | 19 | self.title = movie.name 20 | self.detail = "\(year) | \(movie.artistName)" 21 | } 22 | } 23 | 24 | public struct MovieDetailPresentation { 25 | 26 | public struct Info { 27 | public let title: String 28 | public let detail: String 29 | } 30 | 31 | public let infoList: [Info] 32 | 33 | public init(movie: Movie) { 34 | var infoList: [Info] = [] 35 | 36 | infoList.append(Info(title: "Name", detail: movie.name)) 37 | infoList.append(Info(title: "Starring", detail: movie.artistName)) 38 | 39 | let year = "\(movie.releaseDate.extract(.year))" 40 | infoList.append(Info(title: "Year", detail: year)) 41 | 42 | if let copyright = movie.copyright { 43 | infoList.append(Info(title: "Copyright", detail: copyright)) 44 | } 45 | 46 | self.infoList = infoList 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /RedCarpetRedux/App/NavigationLogic.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NavigationLogic.swift 3 | // RedCarpetRedux 4 | // 5 | // Created by Göksel Köksal on 29.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Commons 11 | 12 | enum NavigationEvent: Event, Equatable { 13 | 14 | case showMovieList 15 | case showDetailScreen(movie: Movie) 16 | case didCloseDetailScreen 17 | 18 | static func ==(a: NavigationEvent, b: NavigationEvent) -> Bool { 19 | switch (a, b) { 20 | case (.showMovieList, .showMovieList): 21 | return true 22 | case (.showDetailScreen(movie: _), .showDetailScreen(movie: _)): 23 | return true 24 | case (.didCloseDetailScreen, .didCloseDetailScreen): 25 | return true 26 | default: 27 | return false 28 | } 29 | } 30 | } 31 | 32 | struct NavigationState: State { 33 | 34 | var history: [NavigationEvent] 35 | var lastEvent: NavigationEvent 36 | 37 | init(event: NavigationEvent) { 38 | self.lastEvent = event 39 | self.history = [event] 40 | } 41 | } 42 | 43 | extension NavigationState { 44 | 45 | mutating func react(to event: Event) { 46 | guard let event = event as? NavigationEvent else { return } 47 | history.append(event) 48 | lastEvent = event 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /RedCarpetMVC/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | 33 | UISupportedInterfaceOrientations~ipad 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationPortraitUpsideDown 37 | UIInterfaceOrientationLandscapeLeft 38 | UIInterfaceOrientationLandscapeRight 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /RedCarpetRedux/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | 33 | UISupportedInterfaceOrientations~ipad 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationPortraitUpsideDown 37 | UIInterfaceOrientationLandscapeLeft 38 | UIInterfaceOrientationLandscapeRight 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/swift 3 | 4 | ### Swift ### 5 | # Xcode 6 | # 7 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 8 | 9 | ## Build generated 10 | build/ 11 | DerivedData/ 12 | 13 | ## Various settings 14 | *.pbxuser 15 | !default.pbxuser 16 | *.mode1v3 17 | !default.mode1v3 18 | *.mode2v3 19 | !default.mode2v3 20 | *.perspectivev3 21 | !default.perspectivev3 22 | xcuserdata/ 23 | 24 | ## Other 25 | *.moved-aside 26 | *.xccheckout 27 | *.xcscmblueprint 28 | 29 | ## Obj-C/Swift specific 30 | *.hmap 31 | *.ipa 32 | *.dSYM.zip 33 | *.dSYM 34 | 35 | ## Playgrounds 36 | timeline.xctimeline 37 | playground.xcworkspace 38 | 39 | # Swift Package Manager 40 | # 41 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 42 | # Packages/ 43 | # Package.pins 44 | .build/ 45 | 46 | # CocoaPods - Refactored to standalone file 47 | 48 | # Carthage - Refactored to standalone file 49 | 50 | # fastlane 51 | # 52 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 53 | # screenshots whenever they are needed. 54 | # For more information about the recommended setup visit: 55 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 56 | 57 | fastlane/report.xml 58 | fastlane/Preview.html 59 | fastlane/screenshots 60 | fastlane/test_output 61 | 62 | # End of https://www.gitignore.io/api/swift -------------------------------------------------------------------------------- /RedCarpetVIPER/AppRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppRouter.swift 3 | // RedCarpetVIPER 4 | // 5 | // Created by Göksel Köksal on 16.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Commons 11 | 12 | final class AppRouter { 13 | 14 | static let shared = AppRouter() 15 | 16 | func configureInitialViewController(_ viewController: UIViewController) { 17 | let initialVC: UIViewController 18 | 19 | if let nc = viewController as? UINavigationController { 20 | initialVC = nc.viewControllers.first! // Can't recover if nil. 21 | } else { 22 | initialVC = viewController 23 | } 24 | 25 | guard let vc = initialVC as? MovieListViewController else { 26 | fatalError() 27 | } 28 | 29 | configureMovieListViewController(vc) 30 | } 31 | 32 | private func configureMovieListViewController(_ vc: MovieListViewController) { 33 | #if MOCK 34 | let service = MockMovieService() 35 | #else 36 | let service = MovieService() 37 | #endif 38 | 39 | let interactor = MovieListInteractor(service: service) 40 | let router = MovieListRouter(view: vc) 41 | let presenter = MovieListPresenter( 42 | interactor: interactor, 43 | router: router, 44 | view: vc 45 | ) 46 | 47 | vc.presenter = presenter 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/Data+Unbox.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | internal extension Data { 10 | func unbox() throws -> T { 11 | return try Unboxer(data: self).performUnboxing() 12 | } 13 | 14 | func unbox(context: T.UnboxContext) throws -> T { 15 | return try Unboxer(data: self).performUnboxing(context: context) 16 | } 17 | 18 | func unbox(closure: (Unboxer) throws -> T?) throws -> T { 19 | return try closure(Unboxer(data: self)).orThrow(UnboxError.customUnboxingFailed) 20 | } 21 | 22 | func unbox(allowInvalidElements: Bool) throws -> [T] { 23 | let array: [UnboxableDictionary] = try JSONSerialization.unbox(data: self, options: [.allowFragments]) 24 | return try array.map(allowInvalidElements: allowInvalidElements) { dictionary in 25 | return try Unboxer(dictionary: dictionary).performUnboxing() 26 | } 27 | } 28 | 29 | func unbox(context: T.UnboxContext, allowInvalidElements: Bool) throws -> [T] { 30 | let array: [UnboxableDictionary] = try JSONSerialization.unbox(data: self, options: [.allowFragments]) 31 | 32 | return try array.map(allowInvalidElements: allowInvalidElements) { dictionary in 33 | return try Unboxer(dictionary: dictionary).performUnboxing(context: context) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /RedCarpetMVVM/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /RedCarpetVIPER/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /RedCarpetAppleMVC/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Professional Swift Courseware 2 | 3 | This project is a part of [Professional Swift Courseware](https://www.packtpub.com/) from [Packt Publishing](https://www.packtpub.com/). 4 | 5 | Content for 3-day training: 6 | 7 | * [Day 1](https://github.com/kareman/Packt-Courseware---Frameworks) 8 | * [Day 2](https://github.com/robkerr/PacktProSwiftDay2) 9 | * **Day 3** 10 | 11 | # Red Carpet 12 | 13 | This project demonstrates Original MVC, Apple MVC, MVVM, VIPER and Redux architectures all in a simple movie app. Upon launch, you will see top 25 movies from iTunes listed in a table view. You can also tap on a movie to show details. 14 | 15 | * **Original MVC**: Demonstrated in `RedCarpetMVC` target. 16 | * **Apple's MVC**: Demonstrated in `RedCarpetAppleMVC` target. 17 | * **MVVM**: Demonstrated in `RedCarpetMVVM` target. 18 | * **VIPER**: Demonstrated in `RedCarpetVIPER` target. 19 | * **Redux**: Demonstrated in `RedCarpetRedux` target. 20 | 21 | All targets above implements the same app using a different approach. However, they share resources like data models, service APIs, mocked data, etc. These shared resources can be found in `Commons` target. 22 | 23 | Data is provided by [iTunes](https://rss.itunes.apple.com/en-us). 24 | 25 | ## Requirements 26 | 27 | * Xcode 10 28 | * Swift 4.2 29 | 30 | **Note**: Pods are included in repo but you may also need [CocoaPods](https://github.com/CocoaPods/CocoaPods) to manage dependencies. 31 | 32 | ## License 33 | 34 | Red Carpet is available under the [MIT license](https://github.com/gokselkoksal/RedCarpet/blob/master/LICENSE.txt). 35 | -------------------------------------------------------------------------------- /RedCarpetRedux/App/MovieListLogic.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovieListLogic.swift 3 | // RedCarpetRedux 4 | // 5 | // Created by Göksel Köksal on 29.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Commons 11 | 12 | // MARK: Actions 13 | 14 | enum MovieListEvent: Event { 15 | case setLoading(Bool) 16 | case setMovieFetchResult(Result<[Movie]>) 17 | } 18 | 19 | // MARK: Async Actions 20 | 21 | class MovieFetchCommand: Command { 22 | 23 | let service: MovieServiceProtocol 24 | 25 | init(service: MovieServiceProtocol) { 26 | self.service = service 27 | } 28 | 29 | func execute(state: AppState, core: Core) { 30 | core.fire(event: MovieListEvent.setLoading(true)) 31 | service.fetchMovies { (result) in 32 | core.fire(event: MovieListEvent.setLoading(false)) 33 | core.fire(event: MovieListEvent.setMovieFetchResult(result)) 34 | } 35 | } 36 | } 37 | 38 | // MARK: State 39 | 40 | struct MovieListState: State { 41 | var movieFetchResult: Result<[Movie]> = Result.success([]) 42 | var isLoading: Bool = false 43 | } 44 | 45 | // MARK: Reducer 46 | 47 | extension MovieListState { 48 | 49 | mutating func react(to event: Event) { 50 | guard let event = event as? MovieListEvent else { return } 51 | 52 | switch event { 53 | case .setLoading(let isLoading): 54 | self.isLoading = isLoading 55 | case .setMovieFetchResult(let result): 56 | self.movieFetchResult = result 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Commons/Helpers/Result.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Result.swift 3 | // Commons 4 | // 5 | // Created by Göksel Köksal on 7.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum Result { 12 | case success(Value) 13 | case failure(Error) 14 | } 15 | 16 | public extension Result { 17 | 18 | public var isSuccess: Bool { 19 | switch self { 20 | case .success(_): 21 | return true 22 | case .failure(_): 23 | return false 24 | } 25 | } 26 | 27 | public var value: Value? { 28 | switch self { 29 | case .success(let value): 30 | return value 31 | case .failure(_): 32 | return nil 33 | } 34 | } 35 | 36 | public var error: Error? { 37 | switch self { 38 | case .success(_): 39 | return nil 40 | case .failure(let error): 41 | return error 42 | } 43 | } 44 | } 45 | 46 | public extension Result { 47 | 48 | public func map(_ transform: (Value) -> T) -> Result { 49 | switch self { 50 | case .success(let value): 51 | let newValue = transform(value) 52 | return Result.success(newValue) 53 | case .failure(let error): 54 | return Result.failure(error) 55 | } 56 | } 57 | 58 | public func flatMap(_ transform: (Value) -> Result) -> Result { 59 | switch self { 60 | case .success(let value): 61 | return transform(value) 62 | case .failure(let error): 63 | return Result.failure(error) 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /RedCarpetVIPER/MovieList/MovieListPresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovieListPresenter.swift 3 | // RedCarpetVIPER 4 | // 5 | // Created by Göksel Köksal on 16.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Commons 11 | 12 | final class MovieListPresenter { 13 | 14 | private let interactor: MovieListInteractorProtocol 15 | private let router: MovieListRouterProtocol 16 | 17 | weak var view: MovieListViewProtocol? 18 | 19 | private var movies: [Movie] = [] 20 | 21 | init(interactor: MovieListInteractorProtocol, 22 | router: MovieListRouter, 23 | view: MovieListViewProtocol) { 24 | 25 | self.interactor = interactor 26 | self.router = router 27 | self.view = view 28 | 29 | self.interactor.output = self 30 | } 31 | } 32 | 33 | extension MovieListPresenter: MovieListPresenterProtocol { 34 | 35 | func didLoad() { 36 | interactor.fetchMovies() 37 | } 38 | 39 | func didTapOnMovie(at index: Int) { 40 | let movie = movies[index] 41 | let presentation = MovieDetailPresentation(movie: movie) 42 | router.showMovieDetail(with: presentation) 43 | } 44 | } 45 | 46 | extension MovieListPresenter: MovieListInteractorOutput { 47 | 48 | func receiveError(_ error: Error) { 49 | view?.handleError(error) 50 | } 51 | 52 | func receiveMovies(_ movies: [Movie]) { 53 | self.movies = movies 54 | let presentations = movies.map { MovieListPresentation(movie: $0) } 55 | view?.updateMoviePresentations(presentations) 56 | } 57 | 58 | func receiveNetworkActivityStatus(isActive: Bool) { 59 | view?.setLoading(isActive) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /RedCarpetMVC/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /RedCarpetMVVM/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /RedCarpetRedux/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /RedCarpetVIPER/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /RedCarpetAppleMVC/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/UnboxFormatter.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | /// Protocol used by objects that may format raw values into some other value 10 | public protocol UnboxFormatter { 11 | /// The type of raw value that this formatter accepts as input 12 | associatedtype UnboxRawValue: UnboxableRawType 13 | /// The type of value that this formatter produces as output 14 | associatedtype UnboxFormattedType 15 | 16 | /// Format an unboxed value into another value (or nil if the formatting failed) 17 | func format(unboxedValue: UnboxRawValue) -> UnboxFormattedType? 18 | } 19 | 20 | // MARK: - Internal extensions 21 | 22 | internal extension UnboxFormatter { 23 | func makeTransform() -> UnboxTransform { 24 | return { ($0 as? UnboxRawValue).map(self.format) } 25 | } 26 | 27 | func makeCollectionTransform(allowInvalidElements: Bool) -> UnboxTransform where C.UnboxValue == UnboxFormattedType { 28 | return { 29 | let transformer = UnboxFormatterCollectionElementTransformer(formatter: self) 30 | return try C.unbox(value: $0, allowInvalidElements: allowInvalidElements, transformer: transformer) 31 | } 32 | } 33 | } 34 | 35 | // MARK: - Utilities 36 | 37 | private class UnboxFormatterCollectionElementTransformer: UnboxCollectionElementTransformer { 38 | private let formatter: T 39 | 40 | init(formatter: T) { 41 | self.formatter = formatter 42 | } 43 | 44 | func unbox(element: T.UnboxRawValue, allowInvalidCollectionElements: Bool) throws -> T.UnboxFormattedType? { 45 | return self.formatter.format(unboxedValue: element) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /RedCarpetRedux/Navigation/NavigationEngine.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NavigationEngine.swift 3 | // RedCarpetRedux 4 | // 5 | // Created by Göksel Köksal on 29.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Commons 11 | 12 | class NavigationEngine: Subscriber { 13 | 14 | private(set) var oldState: NavigationState? 15 | let rootNavigationController: UINavigationController 16 | 17 | init(rootNavigationController: UINavigationController) { 18 | self.rootNavigationController = rootNavigationController 19 | core.add(subscriber: self, notifyOnQueue: .main, selector: { $0.navigationState }) 20 | } 21 | 22 | func update(with state: NavigationState) { 23 | if let oldState = oldState, state.lastEvent == oldState.lastEvent { 24 | return 25 | } 26 | 27 | oldState = state 28 | 29 | switch state.lastEvent { 30 | case .showMovieList: 31 | let vc = MovieListViewController() 32 | #if MOCK 33 | let service: MovieServiceProtocol = MockMovieService() 34 | #else 35 | let service: MovieServiceProtocol = MovieService() 36 | #endif 37 | let presenter = MovieListPresenter(view: vc, service: service) 38 | vc.presenter = presenter 39 | rootNavigationController.setViewControllers([vc], animated: false) 40 | 41 | case .showDetailScreen(movie: let movie): 42 | let vc = MovieDetailViewController() 43 | let presenter = MovieDetailPresenter(movie: movie) 44 | vc.presenter = presenter 45 | rootNavigationController.pushViewController(vc, animated: true) 46 | 47 | case .didCloseDetailScreen: 48 | // UIKit handles. 49 | break 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /RedCarpetMVVM/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /RedCarpetAppleMVC/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /RedCarpetRedux/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /RedCarpetVIPER/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /Pods/Alamofire/Source/DispatchQueue+Alamofire.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DispatchQueue+Alamofire.swift 3 | // 4 | // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Dispatch 26 | import Foundation 27 | 28 | extension DispatchQueue { 29 | static var userInteractive: DispatchQueue { return DispatchQueue.global(qos: .userInteractive) } 30 | static var userInitiated: DispatchQueue { return DispatchQueue.global(qos: .userInitiated) } 31 | static var utility: DispatchQueue { return DispatchQueue.global(qos: .utility) } 32 | static var background: DispatchQueue { return DispatchQueue.global(qos: .background) } 33 | 34 | func after(_ delay: TimeInterval, execute closure: @escaping () -> Void) { 35 | asyncAfter(deadline: .now() + delay, execute: closure) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/UnboxableWithContext.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | /// Protocol used to declare a model as being Unboxable with a certain context, for use with the unbox(context:) function 10 | public protocol UnboxableWithContext { 11 | /// The type of the contextual object that this model requires when unboxed 12 | associatedtype UnboxContext 13 | 14 | /// Initialize an instance of this model by unboxing a dictionary & using a context 15 | init(unboxer: Unboxer, context: UnboxContext) throws 16 | } 17 | 18 | // MARK: - Internal extensions 19 | 20 | internal extension UnboxableWithContext { 21 | static func makeTransform(context: UnboxContext) -> UnboxTransform { 22 | return { 23 | try ($0 as? UnboxableDictionary).map { 24 | try unbox(dictionary: $0, context: context) 25 | } 26 | } 27 | } 28 | 29 | static func makeCollectionTransform(context: UnboxContext, allowInvalidElements: Bool) -> UnboxTransform where C.UnboxValue == Self { 30 | return { 31 | let transformer = UnboxableWithContextCollectionElementTransformer(context: context) 32 | return try C.unbox(value: $0, allowInvalidElements: allowInvalidElements, transformer: transformer) 33 | } 34 | } 35 | } 36 | 37 | // MARK: - Utilities 38 | 39 | private class UnboxableWithContextCollectionElementTransformer: UnboxCollectionElementTransformer { 40 | private let context: T.UnboxContext 41 | 42 | init(context: T.UnboxContext) { 43 | self.context = context 44 | } 45 | 46 | func unbox(element: UnboxableDictionary, allowInvalidCollectionElements: Bool) throws -> T? { 47 | let unboxer = Unboxer(dictionary: element) 48 | return try T(unboxer: unboxer, context: self.context) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /RedCarpetAppleMVC/MovieDetailViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovieDetailViewController.swift 3 | // RedCarpetAppleMVC 4 | // 5 | // Created by Göksel Köksal on 9.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Commons 11 | 12 | final class MovieDetailViewController: UITableViewController { 13 | 14 | var movie: Movie! 15 | private var presentation: MovieDetailPresentation? 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | title = "Movie Detail" 20 | configureTableView() 21 | presentation = MovieDetailPresentation(movie: movie) 22 | tableView.reloadData() 23 | } 24 | } 25 | 26 | private extension MovieDetailViewController { 27 | 28 | func configureTableView() { 29 | tableView.allowsSelection = false 30 | tableView.alwaysBounceVertical = false 31 | tableView.tableFooterView = UIView() 32 | } 33 | } 34 | 35 | // MARK: - UITableViewDataSource 36 | 37 | extension MovieDetailViewController { 38 | 39 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 40 | return presentation?.infoList.count ?? 0 41 | } 42 | 43 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 44 | let cell: UITableViewCell 45 | let cellId = "defaultCell" 46 | 47 | if let someCell = tableView.dequeueReusableCell(withIdentifier: cellId) { 48 | cell = someCell 49 | } else { 50 | cell = UITableViewCell(style: .value2, reuseIdentifier: cellId) 51 | } 52 | 53 | // Presentation can't possibly be nil at this point. 54 | // If it is nil, there's a programmer error. 55 | let row = presentation!.infoList[indexPath.row] 56 | 57 | cell.textLabel?.text = row.title 58 | cell.detailTextLabel?.text = row.detail 59 | 60 | return cell 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /RedCarpetVIPER/MovieDetail/MovieDetailViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovieDetailViewController.swift 3 | // RedCarpetVIPER 4 | // 5 | // Created by Göksel Köksal on 16.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Commons 11 | 12 | final class MovieDetailViewController: UITableViewController { 13 | 14 | var presenter: MovieDetailPresenterProtocol! 15 | 16 | var presentation: MovieDetailPresentation { 17 | return presenter.moviePresentation 18 | } 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | title = "Movie Detail" 23 | configureTableView() 24 | tableView.reloadData() 25 | } 26 | } 27 | 28 | private extension MovieDetailViewController { 29 | 30 | func configureTableView() { 31 | tableView.allowsSelection = false 32 | tableView.alwaysBounceVertical = false 33 | tableView.tableFooterView = UIView() 34 | } 35 | } 36 | 37 | // MARK: - UITableViewDataSource 38 | 39 | extension MovieDetailViewController { 40 | 41 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 42 | return presentation.infoList.count 43 | } 44 | 45 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 46 | let cell: UITableViewCell 47 | let cellId = "defaultCell" 48 | 49 | if let someCell = tableView.dequeueReusableCell(withIdentifier: cellId) { 50 | cell = someCell 51 | } else { 52 | cell = UITableViewCell(style: .value2, reuseIdentifier: cellId) 53 | } 54 | 55 | // Presentation can't possibly be nil at this point. 56 | // If it is nil, there's a programmer error. 57 | let row = presentation.infoList[indexPath.row] 58 | 59 | cell.textLabel?.text = row.title 60 | cell.detailTextLabel?.text = row.detail 61 | 62 | return cell 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /RedCarpetMVC/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /RedCarpetMVVM/MovieDetail/MovieDetailViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovieDetailViewController.swift 3 | // RedCarpetMVVM 4 | // 5 | // Created by Göksel Köksal on 16.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Commons 11 | 12 | final class MovieDetailViewController: UITableViewController { 13 | 14 | var viewModel: MovieDetailViewModelProtocol! 15 | private var presentation: MovieDetailPresentation? 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | title = "Movie Detail" 20 | configureTableView() 21 | presentation = MovieDetailPresentation(movie: viewModel.movie) 22 | tableView.reloadData() 23 | } 24 | } 25 | 26 | private extension MovieDetailViewController { 27 | 28 | func configureTableView() { 29 | tableView.allowsSelection = false 30 | tableView.alwaysBounceVertical = false 31 | tableView.tableFooterView = UIView() 32 | } 33 | } 34 | 35 | // MARK: - UITableViewDataSource 36 | 37 | extension MovieDetailViewController { 38 | 39 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 40 | return presentation?.infoList.count ?? 0 41 | } 42 | 43 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 44 | let cell: UITableViewCell 45 | let cellId = "defaultCell" 46 | 47 | if let someCell = tableView.dequeueReusableCell(withIdentifier: cellId) { 48 | cell = someCell 49 | } else { 50 | cell = UITableViewCell(style: .value2, reuseIdentifier: cellId) 51 | } 52 | 53 | // Presentation can't possibly be nil at this point. 54 | // If it is nil, there's a programmer error. 55 | let row = presentation!.infoList[indexPath.row] 56 | 57 | cell.textLabel?.text = row.title 58 | cell.detailTextLabel?.text = row.detail 59 | 60 | return cell 61 | } 62 | } 63 | 64 | -------------------------------------------------------------------------------- /RedCarpetRedux/MovieList/MovieListPresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovieListPresenter.swift 3 | // RedCarpetRedux 4 | // 5 | // Created by Göksel Köksal on 29.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Commons 11 | 12 | protocol MovieListViewProtocol: class { 13 | func updateMoviePresentations(_ presentations: [MovieListPresentation]) 14 | func handleError(_ error: Error) 15 | func setLoading(_ flag: Bool) 16 | } 17 | 18 | protocol MovieListPresenterProtocol: class { 19 | func didLoad() 20 | func willAppear() 21 | func didDisappear() 22 | func didTapOnMovie(at index: Int) 23 | } 24 | 25 | class MovieListPresenter: MovieListPresenterProtocol { 26 | 27 | private unowned var view: MovieListViewProtocol 28 | private(set) var movies: [Movie] = [] 29 | 30 | private let service: MovieServiceProtocol 31 | 32 | init(view: MovieListViewProtocol, service: MovieServiceProtocol) { 33 | self.view = view 34 | self.service = service 35 | } 36 | 37 | func didLoad() { 38 | core.fire(command: MovieFetchCommand(service: service)) 39 | } 40 | 41 | func willAppear() { 42 | core.add(subscriber: self, notifyOnQueue: .main, selector: { $0.movieListState }) 43 | } 44 | 45 | func didDisappear() { 46 | core.remove(subscriber: self) 47 | } 48 | 49 | func didTapOnMovie(at index: Int) { 50 | let movie = movies[index] 51 | core.fire(event: NavigationEvent.showDetailScreen(movie: movie)) 52 | } 53 | } 54 | 55 | extension MovieListPresenter: Subscriber { 56 | 57 | func update(with state: MovieListState) { 58 | view.setLoading(state.isLoading) 59 | 60 | switch state.movieFetchResult { 61 | case .success(let movies): 62 | self.movies = movies 63 | let presentations = movies.map { MovieListPresentation(movie: $0) } 64 | view.updateMoviePresentations(presentations) 65 | case .failure(let error): 66 | view.handleError(error) 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /RedCarpetMVC/MovieList/MovieListViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovieListViewController.swift 3 | // RedCarpet 4 | // 5 | // Created by Göksel Köksal on 7.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Commons 11 | 12 | final class MovieListViewController: UIViewController { 13 | 14 | var service: MovieServiceProtocol! 15 | 16 | override func loadView() { 17 | let myView = MovieListView.instantiate() 18 | myView.delegate = self 19 | self.view = myView 20 | } 21 | 22 | var myView: MovieListView { 23 | return view as! MovieListView 24 | } 25 | 26 | override func viewDidLoad() { 27 | super.viewDidLoad() 28 | title = "Movies" 29 | fetchMovies() 30 | } 31 | } 32 | 33 | extension MovieListViewController: MovieListViewDelegate { 34 | 35 | func movieListView(_ view: MovieListView, didSelectMovie movie: Movie) { 36 | let vc = MovieDetailViewController() 37 | vc.movie = movie 38 | navigationController?.pushViewController(vc, animated: true) 39 | } 40 | } 41 | 42 | private extension MovieListViewController { 43 | 44 | func fetchMovies() { 45 | setLoading(true) 46 | 47 | service.fetchMovies { [weak self] (result) in 48 | guard let strongSelf = self else { return } 49 | 50 | strongSelf.setLoading(false) 51 | 52 | switch result { 53 | case .success(let movies): 54 | strongSelf.myView.movies = movies 55 | case .failure(let error): 56 | strongSelf.handleError(error) 57 | } 58 | } 59 | } 60 | 61 | func setLoading(_ flag: Bool) { 62 | UIApplication.shared.isNetworkActivityIndicatorVisible = flag 63 | } 64 | 65 | func handleError(_ error: Error) { 66 | let alert = UIAlertController( 67 | title: "API Error", 68 | message: "Could not fetch movies at this time.", 69 | preferredStyle: .alert 70 | ) 71 | 72 | alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil)) 73 | 74 | present(alert, animated: true, completion: nil) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/UnboxPathError.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | /// Type for errors that can occur while unboxing a certain path 10 | public enum UnboxPathError: Error { 11 | /// An empty key path was given 12 | case emptyKeyPath 13 | /// A required key was missing (contains the key) 14 | case missingKey(String) 15 | /// An invalid value was found (contains the value, and its key) 16 | case invalidValue(Any, String) 17 | /// An invalid collection element type was found (contains the type) 18 | case invalidCollectionElementType(Any) 19 | /// An invalid array element was found (contains the element, and its index) 20 | case invalidArrayElement(Any, Int) 21 | /// An invalid dictionary key type was found (contains the type) 22 | case invalidDictionaryKeyType(Any) 23 | /// An invalid dictionary key was found (contains the key) 24 | case invalidDictionaryKey(Any) 25 | /// An invalid dictionary value was found (contains the value, and its key) 26 | case invalidDictionaryValue(Any, String) 27 | } 28 | 29 | extension UnboxPathError: CustomStringConvertible { 30 | public var description: String { 31 | switch self { 32 | case .emptyKeyPath: 33 | return "Key path can't be empty." 34 | case .missingKey(let key): 35 | return "The key \"\(key)\" is missing." 36 | case .invalidValue(let value, let key): 37 | return "Invalid value (\(value)) for key \"\(key)\"." 38 | case .invalidCollectionElementType(let type): 39 | return "Invalid collection element type: \(type). Must be UnboxCompatible or Unboxable." 40 | case .invalidArrayElement(let element, let index): 41 | return "Invalid array element (\(element)) at index \(index)." 42 | case .invalidDictionaryKeyType(let type): 43 | return "Invalid dictionary key type: \(type). Must be either String or UnboxableKey." 44 | case .invalidDictionaryKey(let key): 45 | return "Invalid dictionary key: \(key)." 46 | case .invalidDictionaryValue(let value, let key): 47 | return "Invalid dictionary value (\(value)) for key \"\(key)\"." 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /RedCarpetAppleMVC/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // RedCarpetAppleMVC 4 | // 5 | // Created by Göksel Köksal on 7.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /RedCarpetMVVM/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // RedCarpetMVVM 4 | // 5 | // Created by Göksel Köksal on 8.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | AppRouter.shared.configureInitialViewController(window!.rootViewController!) 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /RedCarpetVIPER/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // RedCarpetVIPER 4 | // 5 | // Created by Göksel Köksal on 8.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | AppRouter.shared.configureInitialViewController(window!.rootViewController!) 20 | return true 21 | } 22 | 23 | func applicationWillResignActive(_ application: UIApplication) { 24 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 25 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 26 | } 27 | 28 | func applicationDidEnterBackground(_ application: UIApplication) { 29 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | func applicationWillEnterForeground(_ application: UIApplication) { 34 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 35 | } 36 | 37 | func applicationDidBecomeActive(_ application: UIApplication) { 38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 39 | } 40 | 41 | func applicationWillTerminate(_ application: UIApplication) { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | 46 | } 47 | 48 | -------------------------------------------------------------------------------- /RedCarpetMVC/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // RedCarpet 4 | // 5 | // Created by Göksel Köksal on 7.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | window = UIWindow(frame: UIScreen.main.bounds) 19 | window?.rootViewController = AppLauncher.initialViewController() 20 | window?.makeKeyAndVisible() 21 | return true 22 | } 23 | 24 | func applicationWillResignActive(_ application: UIApplication) { 25 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 26 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 27 | } 28 | 29 | func applicationDidEnterBackground(_ application: UIApplication) { 30 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 31 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 32 | } 33 | 34 | func applicationWillEnterForeground(_ application: UIApplication) { 35 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 36 | } 37 | 38 | func applicationDidBecomeActive(_ application: UIApplication) { 39 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 40 | } 41 | 42 | func applicationWillTerminate(_ application: UIApplication) { 43 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 44 | } 45 | 46 | 47 | } 48 | 49 | -------------------------------------------------------------------------------- /RedCarpetRedux/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // RedCarpetRedux 4 | // 5 | // Created by Göksel Köksal on 29.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | window = UIWindow(frame: UIScreen.main.bounds) 20 | window?.rootViewController = AppLauncher.shared.initialViewController() 21 | window?.makeKeyAndVisible() 22 | return true 23 | } 24 | 25 | func applicationWillResignActive(_ application: UIApplication) { 26 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 27 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 28 | } 29 | 30 | func applicationDidEnterBackground(_ application: UIApplication) { 31 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | func applicationWillEnterForeground(_ application: UIApplication) { 36 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 37 | } 38 | 39 | func applicationDidBecomeActive(_ application: UIApplication) { 40 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 41 | } 42 | 43 | func applicationWillTerminate(_ application: UIApplication) { 44 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 45 | } 46 | 47 | 48 | } 49 | 50 | -------------------------------------------------------------------------------- /RedCarpetMVC/MovieDetail/MovieDetailView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovieDetailView.swift 3 | // RedCarpetMVC 4 | // 5 | // Created by Göksel Köksal on 7.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Commons 11 | 12 | final class MovieDetailView: UIView { 13 | 14 | private enum Const { 15 | static let cellId = "MovieDetailCell" 16 | } 17 | 18 | 19 | static func instantiate() -> MovieDetailView { 20 | let nibName = String(describing: self) 21 | guard let view = Bundle.main.loadNibNamed(nibName, owner: nil, options: nil)?.first as? MovieDetailView else { 22 | fatalError("There is no nib named \(nibName)") 23 | } 24 | return view 25 | } 26 | 27 | @IBOutlet weak var tableView: UITableView! 28 | 29 | var movie: Movie? = nil { 30 | didSet { 31 | if let movie = movie { 32 | presentation = MovieDetailPresentation(movie: movie) 33 | } else { 34 | presentation = nil 35 | } 36 | } 37 | } 38 | 39 | private var presentation: MovieDetailPresentation? { 40 | didSet { 41 | tableView.reloadData() 42 | } 43 | } 44 | 45 | override func awakeFromNib() { 46 | super.awakeFromNib() 47 | backgroundColor = .white 48 | tableView.dataSource = self 49 | tableView.allowsSelection = false 50 | tableView.alwaysBounceVertical = false 51 | tableView.tableFooterView = UIView() 52 | } 53 | } 54 | 55 | extension MovieDetailView: UITableViewDataSource { 56 | 57 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 58 | return presentation?.infoList.count ?? 0 59 | } 60 | 61 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 62 | let cell: UITableViewCell 63 | 64 | if let someCell = tableView.dequeueReusableCell(withIdentifier: Const.cellId) { 65 | cell = someCell 66 | } else { 67 | cell = UITableViewCell(style: .value2, reuseIdentifier: Const.cellId) 68 | } 69 | 70 | // Presentation can't possibly be nil at this point. 71 | // If it is nil, there's a programmer error. 72 | let row = presentation!.infoList[indexPath.row] 73 | 74 | cell.textLabel?.text = row.title 75 | cell.detailTextLabel?.text = row.detail 76 | 77 | return cell 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-Commons/Pods-Commons-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## Alamofire 5 | 6 | Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | 26 | 27 | ## Unbox 28 | 29 | The MIT License (MIT) 30 | 31 | Copyright (c) 2015 John Sundell 32 | 33 | Permission is hereby granted, free of charge, to any person obtaining a copy 34 | of this software and associated documentation files (the "Software"), to deal 35 | in the Software without restriction, including without limitation the rights 36 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 37 | copies of the Software, and to permit persons to whom the Software is 38 | furnished to do so, subject to the following conditions: 39 | 40 | The above copyright notice and this permission notice shall be included in all 41 | copies or substantial portions of the Software. 42 | 43 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 44 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 45 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 46 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 47 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 48 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 49 | SOFTWARE. 50 | 51 | 52 | Generated by CocoaPods - https://cocoapods.org 53 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetMVC/Pods-RedCarpetMVC-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## Alamofire 5 | 6 | Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | 26 | 27 | ## Unbox 28 | 29 | The MIT License (MIT) 30 | 31 | Copyright (c) 2015 John Sundell 32 | 33 | Permission is hereby granted, free of charge, to any person obtaining a copy 34 | of this software and associated documentation files (the "Software"), to deal 35 | in the Software without restriction, including without limitation the rights 36 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 37 | copies of the Software, and to permit persons to whom the Software is 38 | furnished to do so, subject to the following conditions: 39 | 40 | The above copyright notice and this permission notice shall be included in all 41 | copies or substantial portions of the Software. 42 | 43 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 44 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 45 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 46 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 47 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 48 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 49 | SOFTWARE. 50 | 51 | 52 | Generated by CocoaPods - https://cocoapods.org 53 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetMVVM/Pods-RedCarpetMVVM-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## Alamofire 5 | 6 | Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | 26 | 27 | ## Unbox 28 | 29 | The MIT License (MIT) 30 | 31 | Copyright (c) 2015 John Sundell 32 | 33 | Permission is hereby granted, free of charge, to any person obtaining a copy 34 | of this software and associated documentation files (the "Software"), to deal 35 | in the Software without restriction, including without limitation the rights 36 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 37 | copies of the Software, and to permit persons to whom the Software is 38 | furnished to do so, subject to the following conditions: 39 | 40 | The above copyright notice and this permission notice shall be included in all 41 | copies or substantial portions of the Software. 42 | 43 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 44 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 45 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 46 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 47 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 48 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 49 | SOFTWARE. 50 | 51 | 52 | Generated by CocoaPods - https://cocoapods.org 53 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetRedux/Pods-RedCarpetRedux-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## Alamofire 5 | 6 | Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | 26 | 27 | ## Unbox 28 | 29 | The MIT License (MIT) 30 | 31 | Copyright (c) 2015 John Sundell 32 | 33 | Permission is hereby granted, free of charge, to any person obtaining a copy 34 | of this software and associated documentation files (the "Software"), to deal 35 | in the Software without restriction, including without limitation the rights 36 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 37 | copies of the Software, and to permit persons to whom the Software is 38 | furnished to do so, subject to the following conditions: 39 | 40 | The above copyright notice and this permission notice shall be included in all 41 | copies or substantial portions of the Software. 42 | 43 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 44 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 45 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 46 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 47 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 48 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 49 | SOFTWARE. 50 | 51 | 52 | Generated by CocoaPods - https://cocoapods.org 53 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetVIPER/Pods-RedCarpetVIPER-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## Alamofire 5 | 6 | Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | 26 | 27 | ## Unbox 28 | 29 | The MIT License (MIT) 30 | 31 | Copyright (c) 2015 John Sundell 32 | 33 | Permission is hereby granted, free of charge, to any person obtaining a copy 34 | of this software and associated documentation files (the "Software"), to deal 35 | in the Software without restriction, including without limitation the rights 36 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 37 | copies of the Software, and to permit persons to whom the Software is 38 | furnished to do so, subject to the following conditions: 39 | 40 | The above copyright notice and this permission notice shall be included in all 41 | copies or substantial portions of the Software. 42 | 43 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 44 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 45 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 46 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 47 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 48 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 49 | SOFTWARE. 50 | 51 | 52 | Generated by CocoaPods - https://cocoapods.org 53 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-RedCarpetAppleMVC/Pods-RedCarpetAppleMVC-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## Alamofire 5 | 6 | Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | 26 | 27 | ## Unbox 28 | 29 | The MIT License (MIT) 30 | 31 | Copyright (c) 2015 John Sundell 32 | 33 | Permission is hereby granted, free of charge, to any person obtaining a copy 34 | of this software and associated documentation files (the "Software"), to deal 35 | in the Software without restriction, including without limitation the rights 36 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 37 | copies of the Software, and to permit persons to whom the Software is 38 | furnished to do so, subject to the following conditions: 39 | 40 | The above copyright notice and this permission notice shall be included in all 41 | copies or substantial portions of the Software. 42 | 43 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 44 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 45 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 46 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 47 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 48 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 49 | SOFTWARE. 50 | 51 | 52 | Generated by CocoaPods - https://cocoapods.org 53 | -------------------------------------------------------------------------------- /RedCarpetRedux/MovieDetail/MovieDetailViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovieDetailViewController.swift 3 | // RedCarpetRedux 4 | // 5 | // Created by Göksel Köksal on 29.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | import UIKit 12 | import Commons 13 | 14 | final class MovieDetailViewController: UITableViewController { 15 | 16 | var presenter: MovieDetailPresenterProtocol! 17 | 18 | var presentation: MovieDetailPresentation { 19 | return presenter.moviePresentation 20 | } 21 | 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | title = "Movie Detail" 25 | configureTableView() 26 | tableView.reloadData() 27 | } 28 | 29 | override func viewWillAppear(_ animated: Bool) { 30 | super.viewWillAppear(animated) 31 | presenter.willAppear() 32 | } 33 | 34 | override func viewDidDisappear(_ animated: Bool) { 35 | super.viewDidDisappear(animated) 36 | // Beware that this method can also be called when something is pushed 37 | // onto it. Assuming nothing will be pushed onto it for now to keep 38 | // things simple. 39 | presenter.didDisappear() 40 | presenter.didClose() 41 | } 42 | } 43 | 44 | private extension MovieDetailViewController { 45 | 46 | func configureTableView() { 47 | tableView.allowsSelection = false 48 | tableView.alwaysBounceVertical = false 49 | tableView.tableFooterView = UIView() 50 | } 51 | } 52 | 53 | // MARK: - UITableViewDataSource 54 | 55 | extension MovieDetailViewController { 56 | 57 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 58 | return presentation.infoList.count 59 | } 60 | 61 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 62 | let cell: UITableViewCell 63 | let cellId = "defaultCell" 64 | 65 | if let someCell = tableView.dequeueReusableCell(withIdentifier: cellId) { 66 | cell = someCell 67 | } else { 68 | cell = UITableViewCell(style: .value2, reuseIdentifier: cellId) 69 | } 70 | 71 | // Presentation can't possibly be nil at this point. 72 | // If it is nil, there's a programmer error. 73 | let row = presentation.infoList[indexPath.row] 74 | 75 | cell.textLabel?.text = row.title 76 | cell.detailTextLabel?.text = row.detail 77 | 78 | return cell 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /RedCarpetMVC/MovieList/MovieListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovieListView.swift 3 | // RedCarpetMVC 4 | // 5 | // Created by Göksel Köksal on 7.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Commons 11 | 12 | protocol MovieListViewDelegate: class { 13 | func movieListView(_ view: MovieListView, didSelectMovie movie: Movie) 14 | } 15 | 16 | final class MovieListView: UIView { 17 | 18 | private enum Const { 19 | static let cellId = "MovieCell" 20 | } 21 | 22 | static func instantiate() -> MovieListView { 23 | let nibName = String(describing: self) 24 | guard let view = Bundle.main.loadNibNamed(nibName, owner: nil, options: nil)?.first as? MovieListView else { 25 | fatalError("There is no nib named \(nibName)") 26 | } 27 | return view 28 | } 29 | 30 | @IBOutlet weak var tableView: UITableView! 31 | weak var delegate: MovieListViewDelegate? 32 | 33 | var movies: [Movie] = [] { 34 | didSet { 35 | tableView.reloadData() 36 | } 37 | } 38 | 39 | override func awakeFromNib() { 40 | super.awakeFromNib() 41 | 42 | tableView.delegate = self 43 | tableView.dataSource = self 44 | 45 | backgroundColor = .white 46 | } 47 | } 48 | 49 | extension MovieListView: UITableViewDataSource { 50 | 51 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 52 | return movies.count 53 | } 54 | 55 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 56 | let cell: UITableViewCell 57 | 58 | if let someCell = tableView.dequeueReusableCell(withIdentifier: Const.cellId) { 59 | cell = someCell 60 | } else { 61 | cell = UITableViewCell(style: .subtitle, reuseIdentifier: Const.cellId) 62 | } 63 | 64 | let movie = movies[indexPath.row] 65 | let presentation = MovieListPresentation(movie: movie) 66 | 67 | cell.textLabel?.text = presentation.title 68 | cell.detailTextLabel?.text = presentation.detail 69 | 70 | return cell 71 | } 72 | } 73 | 74 | extension MovieListView: UITableViewDelegate { 75 | 76 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 77 | tableView.deselectRow(at: indexPath, animated: false) 78 | 79 | let movie = movies[indexPath.row] 80 | delegate?.movieListView(self, didSelectMovie: movie) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/UnboxableCollection.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | // MARK: - Protocol 10 | 11 | /// Protocol used to enable collections to be unboxed. Default implementations exist for Array & Dictionary 12 | public protocol UnboxableCollection: Collection, UnboxCompatible { 13 | /// The value type that this collection contains 14 | associatedtype UnboxValue 15 | 16 | /// Unbox a value into a collection, optionally allowing invalid elements 17 | static func unbox(value: Any, allowInvalidElements: Bool, transformer: T) throws -> Self? where T.UnboxedElement == UnboxValue 18 | } 19 | 20 | // MARK: - Default implementations 21 | 22 | // Default implementation of `UnboxCompatible` for collections 23 | public extension UnboxableCollection { 24 | public static func unbox(value: Any, allowInvalidCollectionElements: Bool) throws -> Self? { 25 | if let matchingCollection = value as? Self { 26 | return matchingCollection 27 | } 28 | 29 | if let unboxableType = UnboxValue.self as? Unboxable.Type { 30 | let transformer = UnboxCollectionElementClosureTransformer() { element in 31 | let unboxer = Unboxer(dictionary: element) 32 | return try unboxableType.init(unboxer: unboxer) as? UnboxValue 33 | } 34 | 35 | return try self.unbox(value: value, allowInvalidElements: allowInvalidCollectionElements, transformer: transformer) 36 | } 37 | 38 | if let unboxCompatibleType = UnboxValue.self as? UnboxCompatible.Type { 39 | let transformer = UnboxCollectionElementClosureTransformer() { element in 40 | return try unboxCompatibleType.unbox(value: element, allowInvalidCollectionElements: allowInvalidCollectionElements) as? UnboxValue 41 | } 42 | 43 | return try self.unbox(value: value, allowInvalidElements: allowInvalidCollectionElements, transformer: transformer) 44 | } 45 | 46 | throw UnboxPathError.invalidCollectionElementType(UnboxValue.self) 47 | } 48 | } 49 | 50 | // MARK: - Utility types 51 | 52 | private class UnboxCollectionElementClosureTransformer: UnboxCollectionElementTransformer { 53 | private let closure: (I) throws -> O? 54 | 55 | init(closure: @escaping (I) throws -> O?) { 56 | self.closure = closure 57 | } 58 | 59 | func unbox(element: I, allowInvalidCollectionElements: Bool) throws -> O? { 60 | return try self.closure(element) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /RedCarpetVIPER/MovieList/MovieListViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovieListViewController.swift 3 | // RedCarpetVIPER 4 | // 5 | // Created by Göksel Köksal on 8.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Commons 11 | 12 | final class MovieListViewController: UITableViewController { 13 | 14 | var presenter: MovieListPresenterProtocol! 15 | 16 | private var moviePresentations: [MovieListPresentation] = [] 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | title = "Movie List" 21 | presenter.didLoad() 22 | } 23 | } 24 | 25 | extension MovieListViewController: MovieListViewProtocol { 26 | 27 | func updateMoviePresentations(_ presentations: [MovieListPresentation]) { 28 | self.moviePresentations = presentations 29 | tableView.reloadData() 30 | } 31 | 32 | func handleError(_ error: Error) { 33 | let alert = UIAlertController( 34 | title: "API Error", 35 | message: "Could not fetch movies at this time.", 36 | preferredStyle: .alert 37 | ) 38 | 39 | alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil)) 40 | 41 | present(alert, animated: true, completion: nil) 42 | } 43 | 44 | func setLoading(_ flag: Bool) { 45 | UIApplication.shared.isNetworkActivityIndicatorVisible = flag 46 | } 47 | } 48 | 49 | // MARK: - UITableViewDataSource 50 | 51 | extension MovieListViewController { 52 | 53 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 54 | return moviePresentations.count 55 | } 56 | 57 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 58 | let cell: UITableViewCell 59 | let cellId = "defaultCell" 60 | 61 | if let someCell = tableView.dequeueReusableCell(withIdentifier: cellId) { 62 | cell = someCell 63 | } else { 64 | cell = UITableViewCell(style: .subtitle, reuseIdentifier: cellId) 65 | } 66 | 67 | let presentation = moviePresentations[indexPath.row] 68 | 69 | cell.textLabel?.text = presentation.title 70 | cell.detailTextLabel?.text = presentation.detail 71 | 72 | return cell 73 | } 74 | } 75 | 76 | // MARK: - UITableViewDelegate 77 | 78 | extension MovieListViewController { 79 | 80 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 81 | tableView.deselectRow(at: indexPath, animated: false) 82 | presenter.didTapOnMovie(at: indexPath.row) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Pods/Unbox/Sources/Dictionary+Unbox.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Unbox 3 | * Copyright (c) 2015-2017 John Sundell 4 | * Licensed under the MIT license, see LICENSE file 5 | */ 6 | 7 | import Foundation 8 | 9 | /// Extension making `Dictionary` an unboxable collection 10 | extension Dictionary: UnboxableCollection { 11 | public typealias UnboxValue = Value 12 | 13 | public static func unbox(value: Any, allowInvalidElements: Bool, transformer: T) throws -> Dictionary? where T.UnboxedElement == UnboxValue { 14 | guard let dictionary = value as? [String : T.UnboxRawElement] else { 15 | return nil 16 | } 17 | 18 | let keyTransform = try self.makeKeyTransform() 19 | 20 | return try dictionary.map(allowInvalidElements: allowInvalidElements) { key, value in 21 | guard let unboxedKey = keyTransform(key) else { 22 | throw UnboxPathError.invalidDictionaryKey(key) 23 | } 24 | 25 | guard let unboxedValue = try transformer.unbox(element: value, allowInvalidCollectionElements: allowInvalidElements) else { 26 | throw UnboxPathError.invalidDictionaryValue(value, key) 27 | } 28 | 29 | return (unboxedKey, unboxedValue) 30 | } 31 | } 32 | 33 | private static func makeKeyTransform() throws -> (String) -> Key? { 34 | if Key.self is String.Type { 35 | return { $0 as? Key } 36 | } 37 | 38 | if let keyType = Key.self as? UnboxableKey.Type { 39 | return { keyType.transform(unboxedKey: $0) as? Key } 40 | } 41 | 42 | throw UnboxPathError.invalidDictionaryKeyType(Key.self) 43 | } 44 | } 45 | 46 | /// Extension making `Dictionary` an unbox path node 47 | extension Dictionary: UnboxPathNode { 48 | func unboxPathValue(forKey key: String) -> Any? { 49 | return self[key as! Key] 50 | } 51 | } 52 | 53 | // MARK: - Utilities 54 | 55 | private extension Dictionary { 56 | func map(allowInvalidElements: Bool, transform: (Key, Value) throws -> (K, V)?) throws -> [K : V]? { 57 | var transformedDictionary = [K : V]() 58 | 59 | for (key, value) in self { 60 | do { 61 | guard let transformed = try transform(key, value) else { 62 | if allowInvalidElements { 63 | continue 64 | } 65 | 66 | return nil 67 | } 68 | 69 | transformedDictionary[transformed.0] = transformed.1 70 | } catch { 71 | if !allowInvalidElements { 72 | throw error 73 | } 74 | } 75 | } 76 | 77 | return transformedDictionary 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /RedCarpetMVC/MovieList/MovieListView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /RedCarpetMVC/MovieDetail/MovieDetailView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /Pods/Alamofire/Source/Notifications.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Notifications.swift 3 | // 4 | // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/) 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | // 24 | 25 | import Foundation 26 | 27 | extension Notification.Name { 28 | /// Used as a namespace for all `URLSessionTask` related notifications. 29 | public struct Task { 30 | /// Posted when a `URLSessionTask` is resumed. The notification `object` contains the resumed `URLSessionTask`. 31 | public static let DidResume = Notification.Name(rawValue: "org.alamofire.notification.name.task.didResume") 32 | 33 | /// Posted when a `URLSessionTask` is suspended. The notification `object` contains the suspended `URLSessionTask`. 34 | public static let DidSuspend = Notification.Name(rawValue: "org.alamofire.notification.name.task.didSuspend") 35 | 36 | /// Posted when a `URLSessionTask` is cancelled. The notification `object` contains the cancelled `URLSessionTask`. 37 | public static let DidCancel = Notification.Name(rawValue: "org.alamofire.notification.name.task.didCancel") 38 | 39 | /// Posted when a `URLSessionTask` is completed. The notification `object` contains the completed `URLSessionTask`. 40 | public static let DidComplete = Notification.Name(rawValue: "org.alamofire.notification.name.task.didComplete") 41 | } 42 | } 43 | 44 | // MARK: - 45 | 46 | extension Notification { 47 | /// Used as a namespace for all `Notification` user info dictionary keys. 48 | public struct Key { 49 | /// User info dictionary key representing the `URLSessionTask` associated with the notification. 50 | public static let Task = "org.alamofire.notification.key.task" 51 | 52 | /// User info dictionary key representing the responseData associated with the notification. 53 | public static let ResponseData = "org.alamofire.notification.key.responseData" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /RedCarpetRedux/MovieList/MovieListViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MovieListViewController.swift 3 | // RedCarpetRedux 4 | // 5 | // Created by Göksel Köksal on 29.10.2017. 6 | // Copyright © 2017 Packt. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Commons 11 | 12 | import UIKit 13 | import Commons 14 | 15 | final class MovieListViewController: UITableViewController { 16 | 17 | var presenter: MovieListPresenterProtocol! 18 | 19 | private var moviePresentations: [MovieListPresentation] = [] 20 | 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | title = "Movie List" 24 | presenter.didLoad() 25 | } 26 | 27 | override func viewWillAppear(_ animated: Bool) { 28 | super.viewWillAppear(animated) 29 | presenter.willAppear() 30 | } 31 | 32 | override func viewDidDisappear(_ animated: Bool) { 33 | super.viewDidDisappear(animated) 34 | presenter.didDisappear() 35 | } 36 | } 37 | 38 | extension MovieListViewController: MovieListViewProtocol { 39 | 40 | func updateMoviePresentations(_ presentations: [MovieListPresentation]) { 41 | self.moviePresentations = presentations 42 | tableView.reloadData() 43 | } 44 | 45 | func handleError(_ error: Error) { 46 | let alert = UIAlertController( 47 | title: "API Error", 48 | message: "Could not fetch movies at this time.", 49 | preferredStyle: .alert 50 | ) 51 | 52 | alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil)) 53 | 54 | present(alert, animated: true, completion: nil) 55 | } 56 | 57 | func setLoading(_ flag: Bool) { 58 | UIApplication.shared.isNetworkActivityIndicatorVisible = flag 59 | } 60 | } 61 | 62 | // MARK: - UITableViewDataSource 63 | 64 | extension MovieListViewController { 65 | 66 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 67 | return moviePresentations.count 68 | } 69 | 70 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 71 | let cell: UITableViewCell 72 | let cellId = "defaultCell" 73 | 74 | if let someCell = tableView.dequeueReusableCell(withIdentifier: cellId) { 75 | cell = someCell 76 | } else { 77 | cell = UITableViewCell(style: .subtitle, reuseIdentifier: cellId) 78 | } 79 | 80 | let presentation = moviePresentations[indexPath.row] 81 | 82 | cell.textLabel?.text = presentation.title 83 | cell.detailTextLabel?.text = presentation.detail 84 | 85 | return cell 86 | } 87 | } 88 | 89 | // MARK: - UITableViewDelegate 90 | 91 | extension MovieListViewController { 92 | 93 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 94 | tableView.deselectRow(at: indexPath, animated: false) 95 | presenter.didTapOnMovie(at: indexPath.row) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /RedCarpet.xcodeproj/xcshareddata/xcschemes/Commons.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /RedCarpet.xcodeproj/xcshareddata/xcschemes/RedCarpetMVC.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | --------------------------------------------------------------------------------