├── .gitignore ├── LICENSE ├── README.md └── RxSwiftReactorKitSampleApp ├── .swiftlint.yml ├── Podfile ├── RxSwiftReactorKitSampleApp.xcodeproj └── project.pbxproj └── RxSwiftReactorKitSampleApp ├── Abstract └── BaseViewController.swift ├── AppDelegate.swift ├── Assets.xcassets ├── AppIcon.appiconset │ └── Contents.json └── Contents.json ├── Base.lproj └── LaunchScreen.storyboard ├── Common └── Constants │ └── Constants.swift ├── Flows ├── AppFlow.swift ├── CounterFlow.swift ├── DashboardFlow.swift ├── GitHubSearchFlow.swift ├── OnboardingFlow.swift ├── SampleStep.swift └── SettingFlow.swift ├── Info.plist ├── Logging └── Logger.swift ├── Models └── ModelType.swift ├── Networking ├── GitHubAPI.swift ├── Networking.swift └── Services │ └── GitHubService.swift ├── Resources └── Localizable │ ├── Localized.swift │ ├── en.lproj │ └── Localizable.strings │ └── ko.lproj │ └── Localizable.strings ├── RxSwiftReactorKitSampleApp-Bridging-Header.h ├── Scenarios ├── Dashboard │ ├── Counter │ │ ├── CounterViewController.storyboard │ │ ├── CounterViewController.swift │ │ └── CounterViewReactor.swift │ ├── GitHubSearch │ │ ├── GitHubSearchViewController.storyboard │ │ ├── GitHubSearchViewController.swift │ │ └── GitHubSearchViewReactor.swift │ └── Setting │ │ ├── SettingViewController.storyboard │ │ ├── SettingViewController.swift │ │ └── SettingViewReactor.swift └── Onboarding │ ├── OnboardingIntroViewController.storyboard │ ├── OnboardingIntroViewController.swift │ └── OnboardingIntroViewReactor.swift └── Services └── PreferencesService.swift /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/xcode,swift,cocoapods 3 | # Edit at https://www.gitignore.io/?templates=xcode,swift,cocoapods 4 | 5 | ### CocoaPods ### 6 | ## CocoaPods GitIgnore Template 7 | 8 | # CocoaPods - Only use to conserve bandwidth / Save time on Pushing 9 | # - Also handy if you have a large number of dependant pods 10 | # - AS PER https://guides.cocoapods.org/using/using-cocoapods.html NEVER IGNORE THE LOCK FILE 11 | Pods/ 12 | 13 | ### Swift ### 14 | # Xcode 15 | # 16 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 17 | 18 | ## Build generated 19 | build/ 20 | DerivedData/ 21 | 22 | ## Various settings 23 | *.pbxuser 24 | !default.pbxuser 25 | *.mode1v3 26 | !default.mode1v3 27 | *.mode2v3 28 | !default.mode2v3 29 | *.perspectivev3 30 | !default.perspectivev3 31 | xcuserdata/ 32 | 33 | ## Other 34 | *.moved-aside 35 | *.xccheckout 36 | *.xcscmblueprint 37 | 38 | ## Obj-C/Swift specific 39 | *.hmap 40 | *.ipa 41 | *.dSYM.zip 42 | *.dSYM 43 | 44 | ## Playgrounds 45 | timeline.xctimeline 46 | playground.xcworkspace 47 | 48 | # Swift Package Manager 49 | # 50 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 51 | # Packages/ 52 | # Package.pins 53 | # Package.resolved 54 | .build/ 55 | 56 | # CocoaPods 57 | # 58 | # We recommend against adding the Pods directory to your .gitignore. However 59 | # you should judge for yourself, the pros and cons are mentioned at: 60 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 61 | # 62 | # Pods/ 63 | # 64 | # Add this line if you want to avoid checking in source code from the Xcode workspace 65 | # *.xcworkspace 66 | 67 | # Carthage 68 | # 69 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 70 | # Carthage/Checkouts 71 | 72 | Carthage/Build 73 | 74 | # fastlane 75 | # 76 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 77 | # screenshots whenever they are needed. 78 | # For more information about the recommended setup visit: 79 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 80 | 81 | fastlane/report.xml 82 | fastlane/Preview.html 83 | fastlane/screenshots/**/*.png 84 | fastlane/test_output 85 | 86 | # Code Injection 87 | # 88 | # After new code Injection tools there's a generated folder /iOSInjectionProject 89 | # https://github.com/johnno1962/injectionforxcode 90 | 91 | iOSInjectionProject/ 92 | 93 | ### Xcode ### 94 | # Xcode 95 | # 96 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 97 | 98 | ## User settings 99 | ### Pods More 100 | Podfile.lock 101 | *.xcworkspace 102 | 103 | ### More in xcodeproj 104 | *.xcuserstate 105 | xcschememanagement.plist 106 | contents.xcworkspacedata 107 | 108 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 109 | 110 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 111 | 112 | ### Xcode Patch ### 113 | *.xcodeproj/* 114 | !*.xcodeproj/project.pbxproj 115 | !*.xcodeproj/xcshareddata/ 116 | !*.xcworkspace/contents.xcworkspacedata 117 | /*.gcno 118 | **/xcshareddata/WorkspaceSettings.xcsettings 119 | 120 | # End of https://www.gitignore.io/api/xcode,swift,cocoapods -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 ClintJang 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RxSwift ReactorKit Sample App 2 | - 프로젝트 개발 설계 할때, 기본 구조에 참고 할만한(될만한) 샘플 앱을 만들어 보려 합니다. 3 | 4 | ## 상세 설명 (Description) 5 | `RxSwift` 기반에 `ReactorKit` 아키텍쳐 라이브러리를 활용하여, `iOS Sample App`을 만들어보려합니다. 6 | 7 | - RxFlowDemo 소스를 많이 활용하여 이동처리 셈플 부분을 작성할 예정입니다. (적용 중) 8 | - RxReactorKit Examples 소스를 활용하여 화면 구성을 할 예정입니다. (적용 중) 9 | - [Counter](https://github.com/ReactorKit/ReactorKit/tree/master/Examples/Counter) 10 | - [GitHubSearch](https://github.com/ReactorKit/ReactorKit/tree/master/Examples/GitHubSearch) 11 | - 나머지 화면은 라이센스 사용 목록을 보여주는 정도로 할 예정입니다. 12 | - [GitHubSearch](https://github.com/ReactorKit/ReactorKit/tree/master/Examples/GitHubSearch) 에서 네트워크 처리한 부분은 [Drrrible](https://github.com/devxoul/Drrrible) 에서 네트워크 처리한 부분([Moya](https://github.com/Moya/Moya), [MoyaSugar](https://github.com/devxoul/MoyaSugar))을 참고한 방식으로 변경할 예정입니다. 13 | - MoyaSugar가 4.2 대응이 되어있지 않아 포크떠서 수정한 링크를 사용할 것입니다. 저처럼 Moya를 처음 사용한다면, MoyaSugar를 사용하면 처음에 조금 더 깔끔하게 사용할 수 있을 것이라 생각됩니다. 14 | - [awesome-blogs-ios](https://github.com/tilltue/awesome-blogs-ios) 소스도 참고하고 있습니다. 유용하다 판단되는 부분은 적극 활용하려고 합니다. 15 | 16 | ## 아키텍쳐 컨셉 (Architecture Concepts) 17 | 18 | - [Reactive eXtensions(RX)](http://reactivex.io/) 19 | - [ReactorKit](https://github.com/ReactorKit/ReactorKit) 20 | - [Flux Architecture](https://facebook.github.io/flux/) + RX (Reactive eXtentsion) 21 | - **A framework** for a reactive and unidirectional Swift application architecture 22 | - Data Binding (using [RxSwift](https://github.com/ReactiveX/RxSwift)) 23 | - Dependencies management (using [Cocoapods](https://cocoapods.org/)) 24 | 25 | 26 | 27 | ## 아키텍처 패턴 (Architecture Design Patterns) 28 | ### 샘플 앱은 ReactorKit을 사용합니다. 29 | 30 | - ReactorKit는 Flux와 반응형 프로그래밍의 조합입니다.
31 | 사용자 Actions 및 View States는 관찰 가능한 스트림을 통해 각 계층으로 전달됩니다. 이러한 흐름은 단방향입니다.
View는 Actions를 보낼 수 만 있고, reactor는 states만 emit 할 수 있습니다. 32 | 33 | 34 | 35 | - Action은 사용자 상호 작용을 나타내고 상태는 View State를 나타냅니다.
Mutation은 action과 state 사이에서 브릿지 역활을 합니다.
reactor는 mutate() 및 reduce()의 두 단계로 Action 스트림을 State 스트림으로 변환 단계를 거칩니다. 36 | 37 | 38 | * 상세한 내용은 [https://github.com/ReactorKit/ReactorKit](https://github.com/ReactorKit/ReactorKit) 에서 확인부탁드립니다. 39 | 40 | ``` 41 | ReactorKit은 반응형 단방향 앱을 위한 프레임워크로, 42 | StyleShare와 Kakao를 비롯한 여러 기업에서 43 | 사용하고 있는 기술입니다. 44 | ``` 45 | 46 | ## 컨셉 (Other concepts) 47 | - SwiftGen 을 이용한 지역화 처리 48 | - SwiftLint 를 이용하여 스타일 규칙 적용 49 | - Storyboards 를 UI 구현으로 사용 50 | - XIBs 공통 UI에 사용 51 | - RxFlow라는 Reactive Flow Coordinator를 구현해주는 프레임워크를 활용하였습니다. 52 | - 좋은 설명블로그 53 | - [RxFlow 파트 1 : 이론](https://pilgwon.github.io/blog/2018/11/14/RxFlow-Part-1-Theory.html) 54 | - [RxFlow 파트 2 : 실전](https://pilgwon.github.io/blog/2018/11/14/RxFlow-Part-2-Practice.html) 55 | - [RxFlow 파트 3 : 팁과 요령](https://pilgwon.github.io/blog/2018/12/03/RxFlow-Part-3-Tips-And-Tricks.html) 56 | 57 | - [Moya](https://github.com/Moya/Moya), [MoyaSugar](https://github.com/devxoul/MoyaSugar) : Network 58 | - API endpoints를 추상화하는 네트워크 추상화 레이어 59 | 60 | #### RxFlow 장점 61 | - 스토리보드를 유닛 단위로 쪼개서 UIViewController의 재사용성을 높임. 62 | - 네비게이션의 흐름(context)에 맞게 UIViewController를 다른 방식으로 보여줌. 63 | - 의존성 주입(Dependency Injection)을 쉽게 구현. 64 | - UIViewController에 있는 모든 네비게이션 매커니즘을 삭제. 65 | - 반응형 프로그래밍(Reactive Programming) 사용을 촉진. 66 | - 네비게이션에서 일어나는 대부분의 케이스를 처리하면서 선언형으로 표현. 67 | - 어플리케이션을 네비게이션의 논리적인 블록으로 나누어 처리. 68 | 69 | ## 요구사항 (Requirements) 70 | - Cocoapods 71 | - XCode 10.1+ 72 | - iOS 10.0+ 73 | - 90% 이상이 최신 OS와 한단계 전 OS를 사용. 74 | - https://developer.apple.com/support/app-store/ 75 | - [iOS OS버전을 사용하는 지나간 통계 정보](https://github.com/ClintJang/ios-swift-objc-questions-and-answers/blob/master/README.md#ios-%EB%B0%B0%ED%8F%AC-%EC%B5%9C%EC%86%8C-%EB%B2%84%EC%A0%84%EC%97%90-%EB%8C%80%ED%95%B4-%EA%B3%A0%EB%AF%BC%EC%A4%91%EC%9E%85%EB%8B%88%EB%8B%A4-ios-os%EB%B2%84%EC%A0%84%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%ED%86%B5%EA%B3%84-%EC%A0%95%EB%B3%B4%EB%A5%BC-%EC%95%8C-%EC%88%98-%EC%9E%88%EB%8A%94-%EC%A0%95%EB%B3%B4%EB%82%98-%EB%A7%81%ED%81%AC%EA%B0%80-%EC%9E%88%EC%9D%84%EA%B9%8C%EC%9A%94) 76 | - SwiftGen 77 | 78 | ## 옵션 (Optional) 79 | - SwiftLint 80 | - [FLEX](https://github.com/Flipboard/FLEX) : For Debugging 81 | - [firebase](https://firebase.google.com/) : 샘플 소스에는 미적용 82 | - [crashlytics](https://firebase.google.com/docs/crashlytics/) : 샘플 소스에는 미적용 83 | - (검토중) Fastlane 84 | 85 | ## 사용한 라이브러리 (Used Libraries) 86 | - Installation 87 | - [CocoaPods](https://github.com/CocoaPods/CocoaPods) 88 | - [Carthage](https://github.com/Carthage/Carthage) 89 | - 현재 샘플 소스라서 반드시 필요할 경우에만 사용, 쉽게 셋팅할 수 있도록 pods만을 최대한 활용해서 적용해 두려고 합니다. 90 | 91 | - Core 92 | - [RxSwift](https://github.com/ReactiveX/RxSwift) 93 | - [RxCocoa](https://cocoapods.org/pods/RxCocoa) 94 | - RxCodable 95 | - RxDataSources 96 | - Differentiator 97 | - RxOptional 98 | - RxKeyboard 99 | - RxGesture 100 | - RxViewController 101 | - SectionReactor 102 | 103 | - Architecture 104 | - [ReactorKit](https://github.com/ReactorKit/ReactorKit) 105 | 106 | - Network 107 | - RESTful 108 | - [Alamofire](https://github.com/Alamofire/Alamofire) 109 | - [Moya](https://github.com/Moya/Moya) 110 | - Socket 111 | - [Starscream](https://github.com/daltoniam/Starscream) 112 | 113 | - UI 114 | - [SnapKit](https://github.com/SnapKit/SnapKit) 115 | 116 | - Navigation 117 | - [RxFlow](https://github.com/RxSwiftCommunity/RxFlow) 118 | 119 | - ETC 120 | - [Reusable](https://github.com/AliSoftware/Reusable) 121 | 122 | - Log 123 | - [Reqres](https://github.com/AckeeCZ/Reqres) 124 | 125 | - CI & Documentation 126 | - [SwiftLint](https://github.com/realm/SwiftLint) 127 | - [SwiftGen](https://github.com/SwiftGen/SwiftGen) 128 | - [Fastlane](https://github.com/fastlane/fastlane) 129 | 130 | - Analytics 131 | - [Firebase](https://firebase.google.com/) 132 | - [Crashlytics](https://firebase.google.com/docs/crashlytics) 133 | 134 | ## 시작하기 (Getting started) 135 | 136 | ### Cocoapods 설정 137 | - 작성중 138 | 139 | ## 샘플 앱 140 | 141 | ### 구조 142 | - 작성중 143 | 144 | ## 주요 참조한 링크 145 | > Rx 구현 방식, 폴더 구조, 소스활용, ReactorKit 활용, Moya 네트워크 처리 방법 등등 여러가지로 참고 하였습니다. 146 | 147 | - [Drrrible](https://github.com/devxoul/Drrrible) : ReactorKit을 사용하는 비공식 Dribbble iOS 응용 프로그램입니다. 148 | - [awesome-blogs-ios](https://github.com/tilltue/awesome-blogs-ios) : 어썸블로그 ・ 개발 블로그 모음 ・ 개발 잡덕들을 위한 본격 고퀄리티 개발 블로그 큐레이션 서비스 🕵️‍♀️ - iOS 버전. 149 | - [ReactorKit Examples](https://github.com/ReactorKit/ReactorKit/tree/master/Examples) : ReactorKit 예제 소스 150 | - [RxFlow Demo](https://github.com/RxSwiftCommunity/RxFlow/tree/develop/RxFlowDemo) : RxFlow Demo 소스 151 | -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/.swiftlint.yml: -------------------------------------------------------------------------------- 1 | included: 2 | - RxSwiftReactorKitSampleApp 3 | # - RxSwiftReactorKitSampleApp/Common 4 | # - RxSwiftReactorKitSampleApp/Abstract 5 | # - RxSwiftReactorKitSampleApp/Models 6 | # - RxSwiftReactorKitSampleApp/Services 7 | # - RxSwiftReactorKitSampleApp/Networks 8 | # - RxSwiftReactorKitSampleApp/Scenarios 9 | excluded: 10 | - Carthage 11 | - Pods 12 | enabled_rules: 13 | - conditional_returns_on_newline 14 | - closure_spacing 15 | - colon 16 | - comma 17 | - control_statement 18 | - anyobject_protocol 19 | - array_init 20 | - attributes 21 | - closure_end_indentation 22 | - contains_over_first_not_nil 23 | - empty_count 24 | - empty_string 25 | - empty_xctest_method 26 | disabled_rules: # rule identifiers to exclude from running 27 | - anyobject_protocol 28 | - array_init 29 | - closure_end_indentation 30 | # - closure_spacing 31 | - collection_alignment 32 | - explicit_init 33 | - extension_access_modifier 34 | - fallthrough 35 | - fatal_error_message 36 | - file_header 37 | - file_name 38 | - first_where 39 | - identical_operands 40 | - joined_default_parameter 41 | - let_var_whitespace 42 | - literal_expression_end_indentation 43 | - lower_acl_than_parent 44 | - nimble_operator 45 | - number_separator 46 | - object_literal 47 | - operator_usage_whitespace 48 | - overridden_super_call 49 | - override_in_extension 50 | - pattern_matching_keywords 51 | - private_action 52 | - private_outlet 53 | - prohibited_interface_builder 54 | - prohibited_super_call 55 | - quick_discouraged_call 56 | - quick_discouraged_focused_test 57 | - quick_discouraged_pending_test 58 | - redundant_nil_coalescing 59 | - redundant_type_annotation 60 | - single_test_class 61 | - sorted_first_last 62 | - sorted_imports 63 | - static_operator 64 | - unavailable_function 65 | - unneeded_parentheses_in_closure_argument 66 | - untyped_error_in_catch 67 | - vertical_parameter_alignment_on_call 68 | - vertical_whitespace_closing_braces 69 | - vertical_whitespace_opening_braces 70 | - yoda_condition 71 | #opt_in_rules: # some rules are only opt-in 72 | 73 | #analyzer_rules: # Rules run by `swiftlint analyze` (experimental) 74 | # - unused_import 75 | # - unused_private_declaration 76 | 77 | # configurable rules can be customized from this configuration file 78 | # binary rules can set their severity level 79 | force_cast: warning # implicitly 80 | force_try: 81 | severity: warning # explicitly 82 | # rules that have both warning and error levels, can set just the warning level 83 | # implicitly 84 | line_length: 110 85 | # they can set both implicitly with an array 86 | type_body_length: 87 | - 300 # warning 88 | - 400 # error 89 | # or they can set both explicitly 90 | file_length: 91 | warning: 500 92 | error: 1200 93 | # naming rules can set warnings/errors for min_length and max_length 94 | # additionally they can set excluded names 95 | type_name: 96 | min_length: 4 # only warning 97 | max_length: # warning and error 98 | warning: 40 99 | error: 50 100 | excluded: 101 | - main.swift 102 | 103 | identifier_name: 104 | min_length: # only min_length 105 | error: 4 # only error 106 | excluded: 107 | - id 108 | number_separator: 109 | minimum_length: 5 110 | 111 | reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, junit, html, emoji, sonarqube, markdown) 112 | 113 | 114 | #custom_rules: 115 | # rule_id: 116 | # included: Source/SwiftLintFramework/Rules/.+/\w+\.swift 117 | # name: Rule ID 118 | # message: Rule IDs must be all lowercase, snake case and not end with `rule` 119 | # regex: identifier:\s*("\w+_rule"|"\S*[^a-z_]\S*") 120 | # severity: error 121 | # fatal_error: 122 | # name: Fatal Error 123 | # excluded: "Tests/*" 124 | # message: Prefer using `queuedFatalError` over `fatalError` to avoid leaking compiler host machine paths. 125 | # regex: \bfatalError\b 126 | # match_kinds: 127 | # - identifier 128 | # severity: error 129 | # rule_test_function: 130 | # included: Tests/SwiftLintFrameworkTests/RulesTests.swift 131 | # name: Rule Test Function 132 | # message: Rule Test Function mustn't end with `rule` 133 | # regex: func\s*test\w+(r|R)ule\(\) 134 | # severity: error 135 | -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '10.0' 2 | 3 | # 2019-01-05 4 | #Downloading dependencies 5 | #Using Alamofire (4.8.0) 6 | #Using Differentiator (3.1.0) 7 | #Using FLEX (2.4.0) 8 | #Using Firebase (5.15.0) 9 | #Using FirebaseAnalytics (5.4.0) 10 | #Using FirebaseAnalyticsInterop (1.1.0) 11 | #Using FirebaseCore (5.1.10) 12 | #Using FirebaseInstanceID (3.3.0) 13 | #Using FirebaseMessaging (3.2.2) 14 | #Using GoogleAppMeasurement (5.4.0) 15 | #Using GoogleUtilities (5.3.6) 16 | #Using Moya (12.0.1) 17 | #Using MoyaSugar (1.1.0) 18 | #Using Protobuf (3.6.1) 19 | #Using ReactorKit (1.2.1) 20 | #Using Reqres (3.0.0) 21 | #Using Result (4.1.0) 22 | #Using Reusable (4.0.5) 23 | #Using RxAtomic (4.4.0) 24 | #Using RxCocoa (4.4.0) 25 | #Using RxCodable (0.4.0) 26 | #Using RxDataSources (3.1.0) 27 | #Using RxFlow (1.6.2) 28 | #Using RxGesture (2.1.0) 29 | #Using RxKeyboard (0.9.0) 30 | #Using RxOptional (3.6.2) 31 | #Using RxSwift (4.4.0) 32 | #Using RxViewController (0.4.0) 33 | #Using SectionReactor (0.4.0) 34 | #Using Starscream (3.0.6) 35 | #Using SwiftGen (6.0.2) 36 | #Using SwiftLint (0.29.2) 37 | #Using Then (2.4.0) 38 | #Using nanopb (0.3.901) 39 | #Generating Pods project 40 | 41 | def core_pods 42 | # https://github.com/ReactiveX/RxSwift 43 | # Reactive Programming in Swift 44 | pod 'RxSwift' 45 | 46 | # https://github.com/ReactiveX/RxSwift/tree/master/RxCocoa 47 | # https://cocoapods.org/pods/RxCocoa 48 | # It is a library wrapped in Cocoa Sdk as Rx. 49 | pod 'RxCocoa' 50 | 51 | 52 | # Rx 53 | pod 'RxCodable' 54 | pod 'RxDataSources' 55 | pod 'Differentiator' 56 | pod 'RxOptional' 57 | pod 'RxKeyboard' 58 | pod 'RxGesture' 59 | pod 'RxViewController' 60 | pod 'SectionReactor' 61 | end 62 | 63 | def archi_pods 64 | # https://github.com/ReactorKit/ReactorKit 65 | pod 'ReactorKit' 66 | end 67 | 68 | def network_pods 69 | # https://github.com/Moya/Moya 70 | pod 'Moya' 71 | pod 'Moya/RxSwift' 72 | 73 | # pod 'MoyaSugar' 74 | # pod 'MoyaSugar/RxSwift' 75 | 76 | # pod 'MoyaSugar' // swift 4.2 ? error 77 | pod 'MoyaSugar', :git => 'https://github.com/ClintJang/MoyaSugar', :branch => 'clintjang/1.1.0/fix/moya-sugar-provider-fix' 78 | 79 | end 80 | 81 | def socket_pods 82 | # https://github.com/daltoniam/Starscream 83 | pod 'Starscream' 84 | end 85 | 86 | def navigation_pods 87 | # https://github.com/RxSwiftCommunity/RxFlow 88 | pod 'RxFlow' 89 | end 90 | 91 | def etc_pods 92 | pod 'Reusable' 93 | pod 'Then' 94 | end 95 | 96 | def lint_pods 97 | # Swift style and conventions 98 | pod 'SwiftLint' 99 | end 100 | 101 | def generate_pods 102 | # https://github.com/SwiftGen/SwiftGen 103 | # SwiftGen is a tool to auto-generate Swift code for resources of your projects, to make them type-safe to use 104 | pod 'SwiftGen' 105 | end 106 | 107 | def debugging_pods 108 | # For debugging 109 | pod 'FLEX', :configurations => ['Debug'] 110 | end 111 | 112 | def log_pods 113 | # https://github.com/AckeeCZ/Reqres 114 | # pod 'Reqres' 115 | 116 | # Logging 117 | pod 'CocoaLumberjack/Swift' 118 | end 119 | 120 | def firebase_pods 121 | # https://firebase.google.com/docs/ios/setup?hl=ko 122 | pod 'Firebase/Core' 123 | pod 'Firebase/Messaging' 124 | end 125 | 126 | target 'RxSwiftReactorKitSampleApp' do 127 | # Comment the next line if you're not using Swift and don't want to use dynamic frameworks 128 | use_frameworks! 129 | 130 | # core library 131 | core_pods 132 | 133 | # architecture library 134 | archi_pods 135 | 136 | # Network 137 | network_pods 138 | 139 | # Socket 140 | socket_pods 141 | 142 | # navigation 143 | navigation_pods 144 | 145 | # Misc. 146 | etc_pods 147 | 148 | # coding style 149 | lint_pods 150 | 151 | # code generate 152 | generate_pods 153 | 154 | # debugging library 155 | debugging_pods 156 | 157 | # log library 158 | log_pods 159 | 160 | # firebase library 161 | firebase_pods 162 | end 163 | -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 77B094B65B203E952A819380 /* Pods_RxSwiftReactorKitSampleApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5370CD8DFFAED3638126AC95 /* Pods_RxSwiftReactorKitSampleApp.framework */; }; 11 | BC18740421B4C3CF00AD3A01 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC18740321B4C3CF00AD3A01 /* AppDelegate.swift */; }; 12 | BC18740B21B4C3CF00AD3A01 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BC18740A21B4C3CF00AD3A01 /* Assets.xcassets */; }; 13 | BC18740E21B4C3CF00AD3A01 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BC18740C21B4C3CF00AD3A01 /* LaunchScreen.storyboard */; }; 14 | BC18742B21B50A7500AD3A01 /* SettingViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BC18742A21B50A7500AD3A01 /* SettingViewController.storyboard */; }; 15 | BC18742D21B50A8400AD3A01 /* SettingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC18742C21B50A8400AD3A01 /* SettingViewController.swift */; }; 16 | BC18742F21B50A9600AD3A01 /* SettingViewReactor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC18742E21B50A9600AD3A01 /* SettingViewReactor.swift */; }; 17 | BC18743321B515A500AD3A01 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = BC18743221B515A500AD3A01 /* README.md */; }; 18 | BC18743F21B604E400AD3A01 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = BC18744121B604E400AD3A01 /* Localizable.strings */; }; 19 | BC18744421B6056F00AD3A01 /* Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC18744321B6056F00AD3A01 /* Localized.swift */; }; 20 | BC18744621B608FC00AD3A01 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC18744521B608FC00AD3A01 /* Constants.swift */; }; 21 | BC18744C21B6445900AD3A01 /* .swiftlint.yml in Resources */ = {isa = PBXBuildFile; fileRef = BC18744B21B6445800AD3A01 /* .swiftlint.yml */; }; 22 | BC18744E21B64A1300AD3A01 /* BaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC18744D21B64A1300AD3A01 /* BaseViewController.swift */; }; 23 | BC18745521B67CAE00AD3A01 /* GitHubSearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC18745421B67CAE00AD3A01 /* GitHubSearchViewController.swift */; }; 24 | BC18745721B67CE100AD3A01 /* GitHubSearchViewReactor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC18745621B67CE100AD3A01 /* GitHubSearchViewReactor.swift */; }; 25 | BC18751921C06B7700AD3A01 /* Networking.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC18751821C06B7700AD3A01 /* Networking.swift */; }; 26 | BC1F056D21D8CD1F00666A0B /* OnboardingFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC1F056C21D8CD1F00666A0B /* OnboardingFlow.swift */; }; 27 | BC1F057021D8CE0F00666A0B /* OnboardingIntroViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC1F056F21D8CE0F00666A0B /* OnboardingIntroViewController.swift */; }; 28 | BC1F057221D8CE1B00666A0B /* OnboardingIntroViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BC1F057121D8CE1B00666A0B /* OnboardingIntroViewController.storyboard */; }; 29 | BC1F057421D8CF2600666A0B /* OnboardingIntroViewReactor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC1F057321D8CF2600666A0B /* OnboardingIntroViewReactor.swift */; }; 30 | BC1F058021E01C0B00666A0B /* ModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC1F057F21E01C0B00666A0B /* ModelType.swift */; }; 31 | BC1F058521E027C300666A0B /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC1F058421E027C300666A0B /* Logger.swift */; }; 32 | BC1F058721E35FB700666A0B /* GitHubAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC1F058621E35FB700666A0B /* GitHubAPI.swift */; }; 33 | BC1F058A21E3629100666A0B /* GitHubService.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC1F058921E3629100666A0B /* GitHubService.swift */; }; 34 | BC6B989721C9498B000FDD98 /* SampleStep.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC6B989621C9498B000FDD98 /* SampleStep.swift */; }; 35 | BC6B989921C94A18000FDD98 /* AppFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC6B989821C94A18000FDD98 /* AppFlow.swift */; }; 36 | BC6B989C21C95162000FDD98 /* PreferencesService.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC6B989B21C95162000FDD98 /* PreferencesService.swift */; }; 37 | BC788CB021CFE43B00B9AB63 /* DashboardFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC788CAF21CFE43B00B9AB63 /* DashboardFlow.swift */; }; 38 | BC788CB321CFE94C00B9AB63 /* CounterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC788CB221CFE94C00B9AB63 /* CounterViewController.swift */; }; 39 | BC788CB521CFE96D00B9AB63 /* CounterViewReactor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC788CB421CFE96D00B9AB63 /* CounterViewReactor.swift */; }; 40 | BC788CB721CFEA8200B9AB63 /* CounterViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BC788CB621CFEA8200B9AB63 /* CounterViewController.storyboard */; }; 41 | BC788CB921CFEB0B00B9AB63 /* GitHubSearchViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BC788CB821CFEB0B00B9AB63 /* GitHubSearchViewController.storyboard */; }; 42 | BC788CBB21CFEB9D00B9AB63 /* CounterFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC788CBA21CFEB9D00B9AB63 /* CounterFlow.swift */; }; 43 | BC788CBD21CFEBAD00B9AB63 /* GitHubSearchFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC788CBC21CFEBAD00B9AB63 /* GitHubSearchFlow.swift */; }; 44 | BC788CBF21CFEBB900B9AB63 /* SettingFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC788CBE21CFEBB900B9AB63 /* SettingFlow.swift */; }; 45 | /* End PBXBuildFile section */ 46 | 47 | /* Begin PBXFileReference section */ 48 | 0206F0A0B20768933D6BF794 /* Pods-RxSwiftReactorKitSampleApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RxSwiftReactorKitSampleApp.release.xcconfig"; path = "Pods/Target Support Files/Pods-RxSwiftReactorKitSampleApp/Pods-RxSwiftReactorKitSampleApp.release.xcconfig"; sourceTree = ""; }; 49 | 5370CD8DFFAED3638126AC95 /* Pods_RxSwiftReactorKitSampleApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RxSwiftReactorKitSampleApp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 50 | 9BD65D53911B88314410E609 /* Pods-RxSwiftReactorKitSampleApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RxSwiftReactorKitSampleApp.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RxSwiftReactorKitSampleApp/Pods-RxSwiftReactorKitSampleApp.debug.xcconfig"; sourceTree = ""; }; 51 | BC18740021B4C3CF00AD3A01 /* RxSwiftReactorKitSampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RxSwiftReactorKitSampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 52 | BC18740321B4C3CF00AD3A01 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 53 | BC18740A21B4C3CF00AD3A01 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 54 | BC18740D21B4C3CF00AD3A01 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 55 | BC18740F21B4C3CF00AD3A01 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 56 | BC18742621B502DE00AD3A01 /* RxSwiftReactorKitSampleApp-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RxSwiftReactorKitSampleApp-Bridging-Header.h"; sourceTree = ""; }; 57 | BC18742A21B50A7500AD3A01 /* SettingViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = SettingViewController.storyboard; sourceTree = ""; }; 58 | BC18742C21B50A8400AD3A01 /* SettingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingViewController.swift; sourceTree = ""; }; 59 | BC18742E21B50A9600AD3A01 /* SettingViewReactor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingViewReactor.swift; sourceTree = ""; }; 60 | BC18743221B515A500AD3A01 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 61 | BC18744021B604E400AD3A01 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; 62 | BC18744221B604E800AD3A01 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 63 | BC18744321B6056F00AD3A01 /* Localized.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Localized.swift; sourceTree = ""; }; 64 | BC18744521B608FC00AD3A01 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 65 | BC18744B21B6445800AD3A01 /* .swiftlint.yml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = .swiftlint.yml; sourceTree = ""; }; 66 | BC18744D21B64A1300AD3A01 /* BaseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseViewController.swift; sourceTree = ""; }; 67 | BC18745421B67CAE00AD3A01 /* GitHubSearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitHubSearchViewController.swift; sourceTree = ""; }; 68 | BC18745621B67CE100AD3A01 /* GitHubSearchViewReactor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitHubSearchViewReactor.swift; sourceTree = ""; }; 69 | BC18751821C06B7700AD3A01 /* Networking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Networking.swift; sourceTree = ""; }; 70 | BC1F056C21D8CD1F00666A0B /* OnboardingFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingFlow.swift; sourceTree = ""; }; 71 | BC1F056F21D8CE0F00666A0B /* OnboardingIntroViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingIntroViewController.swift; sourceTree = ""; }; 72 | BC1F057121D8CE1B00666A0B /* OnboardingIntroViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = OnboardingIntroViewController.storyboard; sourceTree = ""; }; 73 | BC1F057321D8CF2600666A0B /* OnboardingIntroViewReactor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingIntroViewReactor.swift; sourceTree = ""; }; 74 | BC1F057F21E01C0B00666A0B /* ModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelType.swift; sourceTree = ""; }; 75 | BC1F058421E027C300666A0B /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; 76 | BC1F058621E35FB700666A0B /* GitHubAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitHubAPI.swift; sourceTree = ""; }; 77 | BC1F058921E3629100666A0B /* GitHubService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitHubService.swift; sourceTree = ""; }; 78 | BC6B989621C9498B000FDD98 /* SampleStep.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleStep.swift; sourceTree = ""; }; 79 | BC6B989821C94A18000FDD98 /* AppFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppFlow.swift; sourceTree = ""; }; 80 | BC6B989B21C95162000FDD98 /* PreferencesService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesService.swift; sourceTree = ""; }; 81 | BC788CAF21CFE43B00B9AB63 /* DashboardFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardFlow.swift; sourceTree = ""; }; 82 | BC788CB221CFE94C00B9AB63 /* CounterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CounterViewController.swift; sourceTree = ""; }; 83 | BC788CB421CFE96D00B9AB63 /* CounterViewReactor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CounterViewReactor.swift; sourceTree = ""; }; 84 | BC788CB621CFEA8200B9AB63 /* CounterViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = CounterViewController.storyboard; sourceTree = ""; }; 85 | BC788CB821CFEB0B00B9AB63 /* GitHubSearchViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = GitHubSearchViewController.storyboard; sourceTree = ""; }; 86 | BC788CBA21CFEB9D00B9AB63 /* CounterFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CounterFlow.swift; sourceTree = ""; }; 87 | BC788CBC21CFEBAD00B9AB63 /* GitHubSearchFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitHubSearchFlow.swift; sourceTree = ""; }; 88 | BC788CBE21CFEBB900B9AB63 /* SettingFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingFlow.swift; sourceTree = ""; }; 89 | /* End PBXFileReference section */ 90 | 91 | /* Begin PBXFrameworksBuildPhase section */ 92 | BC1873FD21B4C3CF00AD3A01 /* Frameworks */ = { 93 | isa = PBXFrameworksBuildPhase; 94 | buildActionMask = 2147483647; 95 | files = ( 96 | 77B094B65B203E952A819380 /* Pods_RxSwiftReactorKitSampleApp.framework in Frameworks */, 97 | ); 98 | runOnlyForDeploymentPostprocessing = 0; 99 | }; 100 | /* End PBXFrameworksBuildPhase section */ 101 | 102 | /* Begin PBXGroup section */ 103 | 39AC0F8755FFE01900C02661 /* Frameworks */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | 5370CD8DFFAED3638126AC95 /* Pods_RxSwiftReactorKitSampleApp.framework */, 107 | ); 108 | name = Frameworks; 109 | sourceTree = ""; 110 | }; 111 | 79EA5F101F097F2611B9DBB2 /* Pods */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | 9BD65D53911B88314410E609 /* Pods-RxSwiftReactorKitSampleApp.debug.xcconfig */, 115 | 0206F0A0B20768933D6BF794 /* Pods-RxSwiftReactorKitSampleApp.release.xcconfig */, 116 | ); 117 | name = Pods; 118 | sourceTree = ""; 119 | }; 120 | BC1873F721B4C3CF00AD3A01 = { 121 | isa = PBXGroup; 122 | children = ( 123 | BC18744B21B6445800AD3A01 /* .swiftlint.yml */, 124 | BC18743221B515A500AD3A01 /* README.md */, 125 | BC18740221B4C3CF00AD3A01 /* RxSwiftReactorKitSampleApp */, 126 | BC18740121B4C3CF00AD3A01 /* Products */, 127 | 79EA5F101F097F2611B9DBB2 /* Pods */, 128 | 39AC0F8755FFE01900C02661 /* Frameworks */, 129 | ); 130 | sourceTree = ""; 131 | }; 132 | BC18740121B4C3CF00AD3A01 /* Products */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | BC18740021B4C3CF00AD3A01 /* RxSwiftReactorKitSampleApp.app */, 136 | ); 137 | name = Products; 138 | sourceTree = ""; 139 | }; 140 | BC18740221B4C3CF00AD3A01 /* RxSwiftReactorKitSampleApp */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | BC18741721B4F72D00AD3A01 /* Data */, 144 | BC18741821B4F74400AD3A01 /* Resources */, 145 | BC18741921B4F76700AD3A01 /* Common */, 146 | BC18744721B60BC700AD3A01 /* Abstract */, 147 | BC6B989521C94964000FDD98 /* Flows */, 148 | BC6B989A21C95148000FDD98 /* Services */, 149 | BC1F057E21E01BF700666A0B /* Models */, 150 | BC18741E21B4F81B00AD3A01 /* Networking */, 151 | BC18741A21B4F78400AD3A01 /* Scenarios */, 152 | BC1F058321E0272E00666A0B /* Logging */, 153 | BC18740321B4C3CF00AD3A01 /* AppDelegate.swift */, 154 | BC18740A21B4C3CF00AD3A01 /* Assets.xcassets */, 155 | BC18740C21B4C3CF00AD3A01 /* LaunchScreen.storyboard */, 156 | BC18740F21B4C3CF00AD3A01 /* Info.plist */, 157 | BC18742621B502DE00AD3A01 /* RxSwiftReactorKitSampleApp-Bridging-Header.h */, 158 | ); 159 | path = RxSwiftReactorKitSampleApp; 160 | sourceTree = ""; 161 | }; 162 | BC18741721B4F72D00AD3A01 /* Data */ = { 163 | isa = PBXGroup; 164 | children = ( 165 | ); 166 | path = Data; 167 | sourceTree = ""; 168 | }; 169 | BC18741821B4F74400AD3A01 /* Resources */ = { 170 | isa = PBXGroup; 171 | children = ( 172 | BC18743421B5FE9600AD3A01 /* Localizable */, 173 | ); 174 | path = Resources; 175 | sourceTree = ""; 176 | }; 177 | BC18741921B4F76700AD3A01 /* Common */ = { 178 | isa = PBXGroup; 179 | children = ( 180 | BC18743C21B600BB00AD3A01 /* Constants */, 181 | BC18741C21B4F7E700AD3A01 /* Extensions */, 182 | BC18741D21B4F7EF00AD3A01 /* Types */, 183 | BC18742021B4FBD200AD3A01 /* Views */, 184 | ); 185 | path = Common; 186 | sourceTree = ""; 187 | }; 188 | BC18741A21B4F78400AD3A01 /* Scenarios */ = { 189 | isa = PBXGroup; 190 | children = ( 191 | BC1F056E21D8CDCC00666A0B /* Onboarding */, 192 | BCF81FBE220D9CBE009C82AB /* Dashboard */, 193 | ); 194 | path = Scenarios; 195 | sourceTree = ""; 196 | }; 197 | BC18741C21B4F7E700AD3A01 /* Extensions */ = { 198 | isa = PBXGroup; 199 | children = ( 200 | ); 201 | path = Extensions; 202 | sourceTree = ""; 203 | }; 204 | BC18741D21B4F7EF00AD3A01 /* Types */ = { 205 | isa = PBXGroup; 206 | children = ( 207 | ); 208 | path = Types; 209 | sourceTree = ""; 210 | }; 211 | BC18741E21B4F81B00AD3A01 /* Networking */ = { 212 | isa = PBXGroup; 213 | children = ( 214 | BC18751821C06B7700AD3A01 /* Networking.swift */, 215 | BC1F058621E35FB700666A0B /* GitHubAPI.swift */, 216 | BC1F058821E3627B00666A0B /* Services */, 217 | ); 218 | path = Networking; 219 | sourceTree = ""; 220 | }; 221 | BC18742021B4FBD200AD3A01 /* Views */ = { 222 | isa = PBXGroup; 223 | children = ( 224 | ); 225 | path = Views; 226 | sourceTree = ""; 227 | }; 228 | BC18742921B50A5500AD3A01 /* Setting */ = { 229 | isa = PBXGroup; 230 | children = ( 231 | BC18742A21B50A7500AD3A01 /* SettingViewController.storyboard */, 232 | BC18742C21B50A8400AD3A01 /* SettingViewController.swift */, 233 | BC18742E21B50A9600AD3A01 /* SettingViewReactor.swift */, 234 | ); 235 | path = Setting; 236 | sourceTree = ""; 237 | }; 238 | BC18743421B5FE9600AD3A01 /* Localizable */ = { 239 | isa = PBXGroup; 240 | children = ( 241 | BC18744321B6056F00AD3A01 /* Localized.swift */, 242 | BC18744121B604E400AD3A01 /* Localizable.strings */, 243 | ); 244 | path = Localizable; 245 | sourceTree = ""; 246 | }; 247 | BC18743C21B600BB00AD3A01 /* Constants */ = { 248 | isa = PBXGroup; 249 | children = ( 250 | BC18744521B608FC00AD3A01 /* Constants.swift */, 251 | ); 252 | path = Constants; 253 | sourceTree = ""; 254 | }; 255 | BC18744721B60BC700AD3A01 /* Abstract */ = { 256 | isa = PBXGroup; 257 | children = ( 258 | BC18744D21B64A1300AD3A01 /* BaseViewController.swift */, 259 | ); 260 | path = Abstract; 261 | sourceTree = ""; 262 | }; 263 | BC18745121B67C3100AD3A01 /* GitHubSearch */ = { 264 | isa = PBXGroup; 265 | children = ( 266 | BC788CB821CFEB0B00B9AB63 /* GitHubSearchViewController.storyboard */, 267 | BC18745421B67CAE00AD3A01 /* GitHubSearchViewController.swift */, 268 | BC18745621B67CE100AD3A01 /* GitHubSearchViewReactor.swift */, 269 | ); 270 | path = GitHubSearch; 271 | sourceTree = ""; 272 | }; 273 | BC1F056E21D8CDCC00666A0B /* Onboarding */ = { 274 | isa = PBXGroup; 275 | children = ( 276 | BC1F057121D8CE1B00666A0B /* OnboardingIntroViewController.storyboard */, 277 | BC1F056F21D8CE0F00666A0B /* OnboardingIntroViewController.swift */, 278 | BC1F057321D8CF2600666A0B /* OnboardingIntroViewReactor.swift */, 279 | ); 280 | path = Onboarding; 281 | sourceTree = ""; 282 | }; 283 | BC1F057E21E01BF700666A0B /* Models */ = { 284 | isa = PBXGroup; 285 | children = ( 286 | BC1F057F21E01C0B00666A0B /* ModelType.swift */, 287 | ); 288 | path = Models; 289 | sourceTree = ""; 290 | }; 291 | BC1F058321E0272E00666A0B /* Logging */ = { 292 | isa = PBXGroup; 293 | children = ( 294 | BC1F058421E027C300666A0B /* Logger.swift */, 295 | ); 296 | path = Logging; 297 | sourceTree = ""; 298 | }; 299 | BC1F058821E3627B00666A0B /* Services */ = { 300 | isa = PBXGroup; 301 | children = ( 302 | BC1F058921E3629100666A0B /* GitHubService.swift */, 303 | ); 304 | path = Services; 305 | sourceTree = ""; 306 | }; 307 | BC6B989521C94964000FDD98 /* Flows */ = { 308 | isa = PBXGroup; 309 | children = ( 310 | BC6B989621C9498B000FDD98 /* SampleStep.swift */, 311 | BC6B989821C94A18000FDD98 /* AppFlow.swift */, 312 | BC1F056C21D8CD1F00666A0B /* OnboardingFlow.swift */, 313 | BC788CAF21CFE43B00B9AB63 /* DashboardFlow.swift */, 314 | BC788CBA21CFEB9D00B9AB63 /* CounterFlow.swift */, 315 | BC788CBC21CFEBAD00B9AB63 /* GitHubSearchFlow.swift */, 316 | BC788CBE21CFEBB900B9AB63 /* SettingFlow.swift */, 317 | ); 318 | path = Flows; 319 | sourceTree = ""; 320 | }; 321 | BC6B989A21C95148000FDD98 /* Services */ = { 322 | isa = PBXGroup; 323 | children = ( 324 | BC6B989B21C95162000FDD98 /* PreferencesService.swift */, 325 | ); 326 | path = Services; 327 | sourceTree = ""; 328 | }; 329 | BC788CB121CFE72300B9AB63 /* Counter */ = { 330 | isa = PBXGroup; 331 | children = ( 332 | BC788CB621CFEA8200B9AB63 /* CounterViewController.storyboard */, 333 | BC788CB221CFE94C00B9AB63 /* CounterViewController.swift */, 334 | BC788CB421CFE96D00B9AB63 /* CounterViewReactor.swift */, 335 | ); 336 | path = Counter; 337 | sourceTree = ""; 338 | }; 339 | BCF81FBE220D9CBE009C82AB /* Dashboard */ = { 340 | isa = PBXGroup; 341 | children = ( 342 | BC788CB121CFE72300B9AB63 /* Counter */, 343 | BC18745121B67C3100AD3A01 /* GitHubSearch */, 344 | BC18742921B50A5500AD3A01 /* Setting */, 345 | ); 346 | path = Dashboard; 347 | sourceTree = ""; 348 | }; 349 | /* End PBXGroup section */ 350 | 351 | /* Begin PBXNativeTarget section */ 352 | BC1873FF21B4C3CF00AD3A01 /* RxSwiftReactorKitSampleApp */ = { 353 | isa = PBXNativeTarget; 354 | buildConfigurationList = BC18741221B4C3CF00AD3A01 /* Build configuration list for PBXNativeTarget "RxSwiftReactorKitSampleApp" */; 355 | buildPhases = ( 356 | 2BC4874BB38F1BE452554736 /* [CP] Check Pods Manifest.lock */, 357 | BC1873FC21B4C3CF00AD3A01 /* Sources */, 358 | BC1873FD21B4C3CF00AD3A01 /* Frameworks */, 359 | BC1873FE21B4C3CF00AD3A01 /* Resources */, 360 | 0F6C615171794D5B045B0E3C /* [CP] Embed Pods Frameworks */, 361 | BC18743B21B5FFD300AD3A01 /* SwiftGen */, 362 | BC18744821B616F200AD3A01 /* Swiftlint */, 363 | ); 364 | buildRules = ( 365 | ); 366 | dependencies = ( 367 | ); 368 | name = RxSwiftReactorKitSampleApp; 369 | productName = RxSwiftReactorKitSampleApp; 370 | productReference = BC18740021B4C3CF00AD3A01 /* RxSwiftReactorKitSampleApp.app */; 371 | productType = "com.apple.product-type.application"; 372 | }; 373 | /* End PBXNativeTarget section */ 374 | 375 | /* Begin PBXProject section */ 376 | BC1873F821B4C3CF00AD3A01 /* Project object */ = { 377 | isa = PBXProject; 378 | attributes = { 379 | LastSwiftUpdateCheck = 1010; 380 | LastUpgradeCheck = 1010; 381 | ORGANIZATIONNAME = clintjang; 382 | TargetAttributes = { 383 | BC1873FF21B4C3CF00AD3A01 = { 384 | CreatedOnToolsVersion = 10.1; 385 | LastSwiftMigration = 1010; 386 | }; 387 | }; 388 | }; 389 | buildConfigurationList = BC1873FB21B4C3CF00AD3A01 /* Build configuration list for PBXProject "RxSwiftReactorKitSampleApp" */; 390 | compatibilityVersion = "Xcode 9.3"; 391 | developmentRegion = en; 392 | hasScannedForEncodings = 0; 393 | knownRegions = ( 394 | en, 395 | Base, 396 | ko, 397 | ); 398 | mainGroup = BC1873F721B4C3CF00AD3A01; 399 | productRefGroup = BC18740121B4C3CF00AD3A01 /* Products */; 400 | projectDirPath = ""; 401 | projectRoot = ""; 402 | targets = ( 403 | BC1873FF21B4C3CF00AD3A01 /* RxSwiftReactorKitSampleApp */, 404 | ); 405 | }; 406 | /* End PBXProject section */ 407 | 408 | /* Begin PBXResourcesBuildPhase section */ 409 | BC1873FE21B4C3CF00AD3A01 /* Resources */ = { 410 | isa = PBXResourcesBuildPhase; 411 | buildActionMask = 2147483647; 412 | files = ( 413 | BC18740E21B4C3CF00AD3A01 /* LaunchScreen.storyboard in Resources */, 414 | BC18743F21B604E400AD3A01 /* Localizable.strings in Resources */, 415 | BC18740B21B4C3CF00AD3A01 /* Assets.xcassets in Resources */, 416 | BC18744C21B6445900AD3A01 /* .swiftlint.yml in Resources */, 417 | BC18743321B515A500AD3A01 /* README.md in Resources */, 418 | BC1F057221D8CE1B00666A0B /* OnboardingIntroViewController.storyboard in Resources */, 419 | BC18742B21B50A7500AD3A01 /* SettingViewController.storyboard in Resources */, 420 | BC788CB721CFEA8200B9AB63 /* CounterViewController.storyboard in Resources */, 421 | BC788CB921CFEB0B00B9AB63 /* GitHubSearchViewController.storyboard in Resources */, 422 | ); 423 | runOnlyForDeploymentPostprocessing = 0; 424 | }; 425 | /* End PBXResourcesBuildPhase section */ 426 | 427 | /* Begin PBXShellScriptBuildPhase section */ 428 | 0F6C615171794D5B045B0E3C /* [CP] Embed Pods Frameworks */ = { 429 | isa = PBXShellScriptBuildPhase; 430 | buildActionMask = 2147483647; 431 | files = ( 432 | ); 433 | inputPaths = ( 434 | "${SRCROOT}/Pods/Target Support Files/Pods-RxSwiftReactorKitSampleApp/Pods-RxSwiftReactorKitSampleApp-frameworks.sh", 435 | "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework", 436 | "${BUILT_PRODUCTS_DIR}/CocoaLumberjack/CocoaLumberjack.framework", 437 | "${BUILT_PRODUCTS_DIR}/Differentiator/Differentiator.framework", 438 | "${BUILT_PRODUCTS_DIR}/FLEX/FLEX.framework", 439 | "${BUILT_PRODUCTS_DIR}/GoogleUtilities/GoogleUtilities.framework", 440 | "${BUILT_PRODUCTS_DIR}/Moya/Moya.framework", 441 | "${BUILT_PRODUCTS_DIR}/MoyaSugar/MoyaSugar.framework", 442 | "${BUILT_PRODUCTS_DIR}/Protobuf/Protobuf.framework", 443 | "${BUILT_PRODUCTS_DIR}/ReactorKit/ReactorKit.framework", 444 | "${BUILT_PRODUCTS_DIR}/Result/Result.framework", 445 | "${BUILT_PRODUCTS_DIR}/Reusable/Reusable.framework", 446 | "${BUILT_PRODUCTS_DIR}/RxAtomic/RxAtomic.framework", 447 | "${BUILT_PRODUCTS_DIR}/RxCocoa/RxCocoa.framework", 448 | "${BUILT_PRODUCTS_DIR}/RxCodable/RxCodable.framework", 449 | "${BUILT_PRODUCTS_DIR}/RxDataSources/RxDataSources.framework", 450 | "${BUILT_PRODUCTS_DIR}/RxFlow/RxFlow.framework", 451 | "${BUILT_PRODUCTS_DIR}/RxGesture/RxGesture.framework", 452 | "${BUILT_PRODUCTS_DIR}/RxKeyboard/RxKeyboard.framework", 453 | "${BUILT_PRODUCTS_DIR}/RxOptional/RxOptional.framework", 454 | "${BUILT_PRODUCTS_DIR}/RxSwift/RxSwift.framework", 455 | "${BUILT_PRODUCTS_DIR}/RxViewController/RxViewController.framework", 456 | "${BUILT_PRODUCTS_DIR}/SectionReactor/SectionReactor.framework", 457 | "${BUILT_PRODUCTS_DIR}/Starscream/Starscream.framework", 458 | "${BUILT_PRODUCTS_DIR}/Then/Then.framework", 459 | "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework", 460 | ); 461 | name = "[CP] Embed Pods Frameworks"; 462 | outputPaths = ( 463 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework", 464 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CocoaLumberjack.framework", 465 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Differentiator.framework", 466 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FLEX.framework", 467 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleUtilities.framework", 468 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Moya.framework", 469 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MoyaSugar.framework", 470 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Protobuf.framework", 471 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReactorKit.framework", 472 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Result.framework", 473 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reusable.framework", 474 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxAtomic.framework", 475 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxCocoa.framework", 476 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxCodable.framework", 477 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxDataSources.framework", 478 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxFlow.framework", 479 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxGesture.framework", 480 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxKeyboard.framework", 481 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxOptional.framework", 482 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxSwift.framework", 483 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxViewController.framework", 484 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SectionReactor.framework", 485 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Starscream.framework", 486 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Then.framework", 487 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework", 488 | ); 489 | runOnlyForDeploymentPostprocessing = 0; 490 | shellPath = /bin/sh; 491 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-RxSwiftReactorKitSampleApp/Pods-RxSwiftReactorKitSampleApp-frameworks.sh\"\n"; 492 | showEnvVarsInLog = 0; 493 | }; 494 | 2BC4874BB38F1BE452554736 /* [CP] Check Pods Manifest.lock */ = { 495 | isa = PBXShellScriptBuildPhase; 496 | buildActionMask = 2147483647; 497 | files = ( 498 | ); 499 | inputPaths = ( 500 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 501 | "${PODS_ROOT}/Manifest.lock", 502 | ); 503 | name = "[CP] Check Pods Manifest.lock"; 504 | outputPaths = ( 505 | "$(DERIVED_FILE_DIR)/Pods-RxSwiftReactorKitSampleApp-checkManifestLockResult.txt", 506 | ); 507 | runOnlyForDeploymentPostprocessing = 0; 508 | shellPath = /bin/sh; 509 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 510 | showEnvVarsInLog = 0; 511 | }; 512 | BC18743B21B5FFD300AD3A01 /* SwiftGen */ = { 513 | isa = PBXShellScriptBuildPhase; 514 | buildActionMask = 2147483647; 515 | files = ( 516 | ); 517 | inputPaths = ( 518 | ); 519 | name = SwiftGen; 520 | outputPaths = ( 521 | ); 522 | runOnlyForDeploymentPostprocessing = 0; 523 | shellPath = /bin/sh; 524 | shellScript = "$PODS_ROOT/SwiftGen/bin/swiftgen strings --param enumName=Localized \"${SRCROOT}/${TARGETNAME}/Resources/Localizable/ko.lproj/Localizable.strings\" -t structured-swift4 -o \"${SRCROOT}/${TARGETNAME}/Resources/Localizable/Localized.swift\"\n"; 525 | }; 526 | BC18744821B616F200AD3A01 /* Swiftlint */ = { 527 | isa = PBXShellScriptBuildPhase; 528 | buildActionMask = 2147483647; 529 | files = ( 530 | ); 531 | inputPaths = ( 532 | ); 533 | name = Swiftlint; 534 | outputPaths = ( 535 | ); 536 | runOnlyForDeploymentPostprocessing = 0; 537 | shellPath = /bin/sh; 538 | shellScript = "if which ${PODS_ROOT}/SwiftLint/swiftlint >/dev/null; then\n${PODS_ROOT}/SwiftLint/swiftlint autocorrect\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; 539 | }; 540 | /* End PBXShellScriptBuildPhase section */ 541 | 542 | /* Begin PBXSourcesBuildPhase section */ 543 | BC1873FC21B4C3CF00AD3A01 /* Sources */ = { 544 | isa = PBXSourcesBuildPhase; 545 | buildActionMask = 2147483647; 546 | files = ( 547 | BC6B989921C94A18000FDD98 /* AppFlow.swift in Sources */, 548 | BC788CB521CFE96D00B9AB63 /* CounterViewReactor.swift in Sources */, 549 | BC788CBD21CFEBAD00B9AB63 /* GitHubSearchFlow.swift in Sources */, 550 | BC788CBB21CFEB9D00B9AB63 /* CounterFlow.swift in Sources */, 551 | BC18745521B67CAE00AD3A01 /* GitHubSearchViewController.swift in Sources */, 552 | BC1F058A21E3629100666A0B /* GitHubService.swift in Sources */, 553 | BC6B989C21C95162000FDD98 /* PreferencesService.swift in Sources */, 554 | BC18742F21B50A9600AD3A01 /* SettingViewReactor.swift in Sources */, 555 | BC18744421B6056F00AD3A01 /* Localized.swift in Sources */, 556 | BC788CB321CFE94C00B9AB63 /* CounterViewController.swift in Sources */, 557 | BC1F058521E027C300666A0B /* Logger.swift in Sources */, 558 | BC1F058021E01C0B00666A0B /* ModelType.swift in Sources */, 559 | BC6B989721C9498B000FDD98 /* SampleStep.swift in Sources */, 560 | BC18751921C06B7700AD3A01 /* Networking.swift in Sources */, 561 | BC1F057421D8CF2600666A0B /* OnboardingIntroViewReactor.swift in Sources */, 562 | BC18744621B608FC00AD3A01 /* Constants.swift in Sources */, 563 | BC1F057021D8CE0F00666A0B /* OnboardingIntroViewController.swift in Sources */, 564 | BC18744E21B64A1300AD3A01 /* BaseViewController.swift in Sources */, 565 | BC1F056D21D8CD1F00666A0B /* OnboardingFlow.swift in Sources */, 566 | BC788CB021CFE43B00B9AB63 /* DashboardFlow.swift in Sources */, 567 | BC1F058721E35FB700666A0B /* GitHubAPI.swift in Sources */, 568 | BC18740421B4C3CF00AD3A01 /* AppDelegate.swift in Sources */, 569 | BC18745721B67CE100AD3A01 /* GitHubSearchViewReactor.swift in Sources */, 570 | BC788CBF21CFEBB900B9AB63 /* SettingFlow.swift in Sources */, 571 | BC18742D21B50A8400AD3A01 /* SettingViewController.swift in Sources */, 572 | ); 573 | runOnlyForDeploymentPostprocessing = 0; 574 | }; 575 | /* End PBXSourcesBuildPhase section */ 576 | 577 | /* Begin PBXVariantGroup section */ 578 | BC18740C21B4C3CF00AD3A01 /* LaunchScreen.storyboard */ = { 579 | isa = PBXVariantGroup; 580 | children = ( 581 | BC18740D21B4C3CF00AD3A01 /* Base */, 582 | ); 583 | name = LaunchScreen.storyboard; 584 | sourceTree = ""; 585 | }; 586 | BC18744121B604E400AD3A01 /* Localizable.strings */ = { 587 | isa = PBXVariantGroup; 588 | children = ( 589 | BC18744021B604E400AD3A01 /* ko */, 590 | BC18744221B604E800AD3A01 /* en */, 591 | ); 592 | name = Localizable.strings; 593 | sourceTree = ""; 594 | }; 595 | /* End PBXVariantGroup section */ 596 | 597 | /* Begin XCBuildConfiguration section */ 598 | BC18741021B4C3CF00AD3A01 /* Debug */ = { 599 | isa = XCBuildConfiguration; 600 | buildSettings = { 601 | ALWAYS_SEARCH_USER_PATHS = NO; 602 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 603 | CLANG_ANALYZER_NONNULL = YES; 604 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 605 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 606 | CLANG_CXX_LIBRARY = "libc++"; 607 | CLANG_ENABLE_MODULES = YES; 608 | CLANG_ENABLE_OBJC_ARC = YES; 609 | CLANG_ENABLE_OBJC_WEAK = YES; 610 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 611 | CLANG_WARN_BOOL_CONVERSION = YES; 612 | CLANG_WARN_COMMA = YES; 613 | CLANG_WARN_CONSTANT_CONVERSION = YES; 614 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 615 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 616 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 617 | CLANG_WARN_EMPTY_BODY = YES; 618 | CLANG_WARN_ENUM_CONVERSION = YES; 619 | CLANG_WARN_INFINITE_RECURSION = YES; 620 | CLANG_WARN_INT_CONVERSION = YES; 621 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 622 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 623 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 624 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 625 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 626 | CLANG_WARN_STRICT_PROTOTYPES = YES; 627 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 628 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 629 | CLANG_WARN_UNREACHABLE_CODE = YES; 630 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 631 | CODE_SIGN_IDENTITY = "iPhone Developer"; 632 | COPY_PHASE_STRIP = NO; 633 | DEBUG_INFORMATION_FORMAT = dwarf; 634 | ENABLE_STRICT_OBJC_MSGSEND = YES; 635 | ENABLE_TESTABILITY = YES; 636 | GCC_C_LANGUAGE_STANDARD = gnu11; 637 | GCC_DYNAMIC_NO_PIC = NO; 638 | GCC_NO_COMMON_BLOCKS = YES; 639 | GCC_OPTIMIZATION_LEVEL = 0; 640 | GCC_PREPROCESSOR_DEFINITIONS = ( 641 | "DEBUG=1", 642 | "$(inherited)", 643 | ); 644 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 645 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 646 | GCC_WARN_UNDECLARED_SELECTOR = YES; 647 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 648 | GCC_WARN_UNUSED_FUNCTION = YES; 649 | GCC_WARN_UNUSED_VARIABLE = YES; 650 | IPHONEOS_DEPLOYMENT_TARGET = 12.1; 651 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 652 | MTL_FAST_MATH = YES; 653 | ONLY_ACTIVE_ARCH = YES; 654 | SDKROOT = iphoneos; 655 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 656 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 657 | }; 658 | name = Debug; 659 | }; 660 | BC18741121B4C3CF00AD3A01 /* Release */ = { 661 | isa = XCBuildConfiguration; 662 | buildSettings = { 663 | ALWAYS_SEARCH_USER_PATHS = NO; 664 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 665 | CLANG_ANALYZER_NONNULL = YES; 666 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 667 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 668 | CLANG_CXX_LIBRARY = "libc++"; 669 | CLANG_ENABLE_MODULES = YES; 670 | CLANG_ENABLE_OBJC_ARC = YES; 671 | CLANG_ENABLE_OBJC_WEAK = YES; 672 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 673 | CLANG_WARN_BOOL_CONVERSION = YES; 674 | CLANG_WARN_COMMA = YES; 675 | CLANG_WARN_CONSTANT_CONVERSION = YES; 676 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 677 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 678 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 679 | CLANG_WARN_EMPTY_BODY = YES; 680 | CLANG_WARN_ENUM_CONVERSION = YES; 681 | CLANG_WARN_INFINITE_RECURSION = YES; 682 | CLANG_WARN_INT_CONVERSION = YES; 683 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 684 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 685 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 686 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 687 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 688 | CLANG_WARN_STRICT_PROTOTYPES = YES; 689 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 690 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 691 | CLANG_WARN_UNREACHABLE_CODE = YES; 692 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 693 | CODE_SIGN_IDENTITY = "iPhone Developer"; 694 | COPY_PHASE_STRIP = NO; 695 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 696 | ENABLE_NS_ASSERTIONS = NO; 697 | ENABLE_STRICT_OBJC_MSGSEND = YES; 698 | GCC_C_LANGUAGE_STANDARD = gnu11; 699 | GCC_NO_COMMON_BLOCKS = YES; 700 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 701 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 702 | GCC_WARN_UNDECLARED_SELECTOR = YES; 703 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 704 | GCC_WARN_UNUSED_FUNCTION = YES; 705 | GCC_WARN_UNUSED_VARIABLE = YES; 706 | IPHONEOS_DEPLOYMENT_TARGET = 12.1; 707 | MTL_ENABLE_DEBUG_INFO = NO; 708 | MTL_FAST_MATH = YES; 709 | SDKROOT = iphoneos; 710 | SWIFT_COMPILATION_MODE = wholemodule; 711 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 712 | VALIDATE_PRODUCT = YES; 713 | }; 714 | name = Release; 715 | }; 716 | BC18741321B4C3CF00AD3A01 /* Debug */ = { 717 | isa = XCBuildConfiguration; 718 | baseConfigurationReference = 9BD65D53911B88314410E609 /* Pods-RxSwiftReactorKitSampleApp.debug.xcconfig */; 719 | buildSettings = { 720 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 721 | CLANG_ENABLE_MODULES = YES; 722 | CODE_SIGN_STYLE = Automatic; 723 | DEVELOPMENT_TEAM = Y72B7R5K2B; 724 | INFOPLIST_FILE = RxSwiftReactorKitSampleApp/Info.plist; 725 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 726 | LD_RUNPATH_SEARCH_PATHS = ( 727 | "$(inherited)", 728 | "@executable_path/Frameworks", 729 | ); 730 | PRODUCT_BUNDLE_IDENTIFIER = kr.co.clintjang.RxSwiftReactorKitSampleApp; 731 | PRODUCT_NAME = "$(TARGET_NAME)"; 732 | SWIFT_OBJC_BRIDGING_HEADER = "RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp-Bridging-Header.h"; 733 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 734 | SWIFT_VERSION = 4.2; 735 | TARGETED_DEVICE_FAMILY = 1; 736 | }; 737 | name = Debug; 738 | }; 739 | BC18741421B4C3CF00AD3A01 /* Release */ = { 740 | isa = XCBuildConfiguration; 741 | baseConfigurationReference = 0206F0A0B20768933D6BF794 /* Pods-RxSwiftReactorKitSampleApp.release.xcconfig */; 742 | buildSettings = { 743 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 744 | CLANG_ENABLE_MODULES = YES; 745 | CODE_SIGN_STYLE = Automatic; 746 | DEVELOPMENT_TEAM = Y72B7R5K2B; 747 | INFOPLIST_FILE = RxSwiftReactorKitSampleApp/Info.plist; 748 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 749 | LD_RUNPATH_SEARCH_PATHS = ( 750 | "$(inherited)", 751 | "@executable_path/Frameworks", 752 | ); 753 | PRODUCT_BUNDLE_IDENTIFIER = kr.co.clintjang.RxSwiftReactorKitSampleApp; 754 | PRODUCT_NAME = "$(TARGET_NAME)"; 755 | SWIFT_OBJC_BRIDGING_HEADER = "RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp-Bridging-Header.h"; 756 | SWIFT_VERSION = 4.2; 757 | TARGETED_DEVICE_FAMILY = 1; 758 | }; 759 | name = Release; 760 | }; 761 | /* End XCBuildConfiguration section */ 762 | 763 | /* Begin XCConfigurationList section */ 764 | BC1873FB21B4C3CF00AD3A01 /* Build configuration list for PBXProject "RxSwiftReactorKitSampleApp" */ = { 765 | isa = XCConfigurationList; 766 | buildConfigurations = ( 767 | BC18741021B4C3CF00AD3A01 /* Debug */, 768 | BC18741121B4C3CF00AD3A01 /* Release */, 769 | ); 770 | defaultConfigurationIsVisible = 0; 771 | defaultConfigurationName = Release; 772 | }; 773 | BC18741221B4C3CF00AD3A01 /* Build configuration list for PBXNativeTarget "RxSwiftReactorKitSampleApp" */ = { 774 | isa = XCConfigurationList; 775 | buildConfigurations = ( 776 | BC18741321B4C3CF00AD3A01 /* Debug */, 777 | BC18741421B4C3CF00AD3A01 /* Release */, 778 | ); 779 | defaultConfigurationIsVisible = 0; 780 | defaultConfigurationName = Release; 781 | }; 782 | /* End XCConfigurationList section */ 783 | }; 784 | rootObject = BC1873F821B4C3CF00AD3A01 /* Project object */; 785 | } 786 | -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp/Abstract/BaseViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseViewController.swift 3 | // RxSwiftReactorKitSampleApp 4 | // 5 | // Created by Clint on 04/12/2018. 6 | // Copyright © 2018 clintjang. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class BaseViewController: UIViewController { 12 | lazy private(set) var className: String = { 13 | return type(of: self).description().components(separatedBy: ".").last ?? "" 14 | }() 15 | 16 | var disposeBag = DisposeBag() 17 | 18 | deinit { 19 | log.info("DEINIT: \(self.className)") 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // RxSwiftReactorKitSampleApp 4 | // 5 | // Created by Clint on 03/12/2018. 6 | // Copyright © 2018 clintjang. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | let disposeBag = DisposeBag() 14 | 15 | var window: UIWindow? 16 | var coordinator = Coordinator() 17 | var appFlow: AppFlow! 18 | let preferencesService = PreferencesService() 19 | lazy var appServices = { 20 | return AppServices(preferencesService: self.preferencesService) 21 | }() 22 | 23 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 24 | guard let window = self.window else { return false } 25 | 26 | coordinator.rx.willNavigate.subscribe(onNext: { (flow, step) in 27 | log.debug("\n➡️ will navigate to flow=\(flow) and step=\(step)") 28 | }).disposed(by: self.disposeBag) 29 | 30 | // 작업 후에.. 31 | coordinator.rx.didNavigate.subscribe(onNext: { (flow, step) in 32 | log.debug("\n➡️ did navigate to flow=\(flow) and step=\(step)") 33 | }).disposed(by: self.disposeBag) 34 | 35 | self.appFlow = AppFlow(withWindow: window, andServices: self.appServices) 36 | 37 | coordinator.coordinate(flow: self.appFlow, with: AppStepper(withServices: self.appServices)) 38 | 39 | return true 40 | } 41 | 42 | } 43 | 44 | struct AppServices: HasPreferencesService { 45 | let preferencesService: PreferencesService 46 | } 47 | -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp/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 | } -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp/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 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp/Common/Constants/Constants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Constants.swift 3 | // RxSwiftReactorKitSampleApp 4 | // 5 | // Created by Clint on 04/12/2018. 6 | // Copyright © 2018 clintjang. All rights reserved. 7 | // 8 | 9 | enum Constants { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp/Flows/AppFlow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppFlow.swift 3 | // RxSwiftReactorKitSampleApp 4 | // 5 | // Created by Clint on 19/12/2018. 6 | // Copyright © 2018 clintjang. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class AppFlow: Flow { 12 | 13 | var root: Presentable { 14 | return self.rootWindow 15 | } 16 | 17 | private let rootWindow: UIWindow 18 | private let services: AppServices 19 | 20 | init(withWindow window: UIWindow, andServices services: AppServices) { 21 | self.rootWindow = window 22 | self.services = services 23 | } 24 | 25 | deinit { 26 | log.info("\(type(of: self)): \(#function)") 27 | } 28 | 29 | func navigate(to step: Step) -> FlowContributors { 30 | guard let step = step as? SampleStep else { return FlowContributors.none } 31 | 32 | switch step { 33 | case .onboardingIsRequired: 34 | return navigationToOnboardingScreen() 35 | case .onboardingIsComplete, .dashboardIsRequired: 36 | return navigationToDashboardScreen() 37 | default: 38 | return FlowContributors.none 39 | } 40 | } 41 | 42 | private func navigationToOnboardingScreen() -> FlowContributors { 43 | 44 | if let rootViewController = self.rootWindow.rootViewController { 45 | rootViewController.dismiss(animated: false) 46 | } 47 | 48 | let onboardingFlow = OnboardingFlow(withServices: self.services) 49 | Flows.whenReady(flow1: onboardingFlow) { [unowned self] (root) in 50 | self.rootWindow.rootViewController = root 51 | } 52 | 53 | return .one(flowContributor: .contribute(withNextPresentable: onboardingFlow, withNextStepper: OneStepper(withSingleStep: SampleStep.introIsRequired))) 54 | } 55 | 56 | private func navigationToDashboardScreen() -> FlowContributors { 57 | let dashboardFlow = DashboardFlow(withServices: self.services) 58 | 59 | Flows.whenReady(flow1: dashboardFlow) { [unowned self] (root) in 60 | self.rootWindow.rootViewController = root 61 | } 62 | 63 | return .one(flowContributor: .contribute(withNextPresentable: dashboardFlow, 64 | withNextStepper: OneStepper(withSingleStep: SampleStep.dashboardIsRequired))) 65 | } 66 | } 67 | 68 | class AppStepper: Stepper { 69 | 70 | let steps = PublishRelay() 71 | private let appServices: AppServices 72 | private let disposeBag = DisposeBag() 73 | 74 | init(withServices services: AppServices) { 75 | self.appServices = services 76 | } 77 | 78 | var initialStep: Step { 79 | // return SampleStep.dashboardIsRequired 80 | return SampleStep.onboardingIsRequired 81 | } 82 | 83 | /// callback used to emit steps once the FlowCoordinator is ready to listen to them to contribute to the Flow 84 | func readyToEmitSteps() { 85 | self.appServices 86 | .preferencesService.rx 87 | .isOnboarded 88 | .debug() 89 | .map { $0 ? SampleStep.onboardingIsComplete : SampleStep.onboardingIsRequired } 90 | .debug() 91 | .bind(to: self.steps) 92 | .disposed(by: self.disposeBag) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp/Flows/CounterFlow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CounterFlow.swift 3 | // RxSwiftReactorKitSampleApp 4 | // 5 | // Created by Clint on 24/12/2018. 6 | // Copyright © 2018 clintjang. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class CounterFlow: Flow { 12 | var root: Presentable { 13 | return self.rootViewController 14 | } 15 | 16 | private let rootViewController = UINavigationController() 17 | private let services: AppServices 18 | 19 | init(withServices services: AppServices) { 20 | self.services = services 21 | } 22 | 23 | deinit { 24 | log.info("\(type(of: self)): \(#function)") 25 | } 26 | 27 | func navigate(to step: Step) -> FlowContributors { 28 | 29 | guard let step = step as? SampleStep else { return FlowContributors.none } 30 | 31 | switch step { 32 | 33 | case .counterIsRequired: 34 | return navigateToCounterScreen() 35 | default: 36 | return FlowContributors.none 37 | } 38 | } 39 | 40 | private func navigateToCounterScreen() -> FlowContributors { 41 | let viewController = CounterViewController.instantiate() 42 | viewController.title = "Counter" 43 | 44 | self.rootViewController.pushViewController(viewController, animated: true) 45 | return .one(flowContributor: .contribute(withNextPresentable: viewController, withNextStepper: OneStepper(withSingleStep: SampleStep.counterIsRequired))) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp/Flows/DashboardFlow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DashboardFlow.swift 3 | // RxSwiftReactorKitSampleApp 4 | // 5 | // Created by Clint on 24/12/2018. 6 | // Copyright © 2018 clintjang. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | final class DashboardFlow: Flow { 12 | var root: Presentable { 13 | return self.rootViewController 14 | } 15 | 16 | let rootViewController = UITabBarController() 17 | private let services: AppServices 18 | 19 | init(withServices services: AppServices) { 20 | self.services = services 21 | } 22 | 23 | deinit { 24 | log.info("\(type(of: self)): \(#function)") 25 | } 26 | 27 | func navigate(to step: Step) -> FlowContributors { 28 | guard let step = step as? SampleStep else { return FlowContributors.none } 29 | 30 | switch step { 31 | case .dashboardIsRequired: 32 | return navigateToDashboard() 33 | default: 34 | return .none 35 | } 36 | } 37 | 38 | private func navigateToDashboard() -> FlowContributors { 39 | let counterFlow = CounterFlow(withServices: self.services) 40 | let githubSearchFlow = GitHubSearchFlow(withServices: self.services) 41 | let settingFlow = SettingFlow(withServices: self.services) 42 | 43 | Flows.whenReady(flow1: counterFlow, flow2: githubSearchFlow, flow3: settingFlow) { [unowned self] (root1: UINavigationController, root2: UINavigationController, root3: UINavigationController) in 44 | let tabBarItem1 = UITabBarItem(title: "Counter", image: nil, selectedImage: nil) 45 | let tabBarItem2 = UITabBarItem(title: "Search", image: nil, selectedImage: nil) 46 | let tabBarItem3 = UITabBarItem(title: "Setting", image: nil, selectedImage: nil) 47 | 48 | root1.tabBarItem = tabBarItem1 49 | root1.title = "Counter" 50 | root2.tabBarItem = tabBarItem2 51 | root2.title = "GitHub Search" 52 | root3.tabBarItem = tabBarItem3 53 | root3.title = "Setting" 54 | 55 | self.rootViewController.setViewControllers([root1, root2, root3], animated: false) 56 | } 57 | 58 | return .multiple(flowContributors: [.contribute(withNextPresentable: counterFlow, 59 | withNextStepper: OneStepper(withSingleStep: SampleStep.counterIsRequired)), 60 | .contribute(withNextPresentable: githubSearchFlow, 61 | withNextStepper: OneStepper(withSingleStep: SampleStep.gitHubSearchIsRequired)), 62 | .contribute(withNextPresentable: settingFlow, 63 | withNextStepper: OneStepper(withSingleStep: SampleStep.settingIsRequired))]) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp/Flows/GitHubSearchFlow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitHubSearchFlow.swift 3 | // RxSwiftReactorKitSampleApp 4 | // 5 | // Created by Clint on 24/12/2018. 6 | // Copyright © 2018 clintjang. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class GitHubSearchFlow: Flow { 12 | var root: Presentable { 13 | return self.rootViewController 14 | } 15 | 16 | private let rootViewController = UINavigationController() 17 | private let services: AppServices 18 | 19 | init(withServices services: AppServices) { 20 | self.services = services 21 | } 22 | 23 | deinit { 24 | log.info("\(type(of: self)): \(#function)") 25 | } 26 | 27 | func navigate(to step: Step) -> FlowContributors { 28 | 29 | guard let step = step as? SampleStep else { return FlowContributors.none } 30 | 31 | switch step { 32 | 33 | case .gitHubSearchIsRequired: 34 | return navigateToGitHubSearchScreen() 35 | default: 36 | return FlowContributors.none 37 | } 38 | } 39 | 40 | private func navigateToGitHubSearchScreen() -> FlowContributors { 41 | let viewController = GitHubSearchViewController.instantiate() 42 | viewController.title = "GitHubSearch" 43 | 44 | self.rootViewController.pushViewController(viewController, animated: true) 45 | return .one(flowContributor: .contribute(withNextPresentable: viewController, withNextStepper: OneStepper(withSingleStep: SampleStep.gitHubSearchIsRequired))) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp/Flows/OnboardingFlow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OnboardingFlow.swift 3 | // RxSwiftReactorKitSampleApp 4 | // 5 | // Created by Clint on 30/12/2018. 6 | // Copyright © 2018 clintjang. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | final class OnboardingFlow: Flow { 12 | var root: Presentable { 13 | return self.rootViewController 14 | } 15 | 16 | private lazy var rootViewController: UINavigationController = { 17 | let viewController = UINavigationController() 18 | viewController.navigationBar.topItem?.title = "OnBoarding" 19 | return viewController 20 | }() 21 | 22 | private let services: AppServices 23 | 24 | init(withServices services: AppServices) { 25 | self.services = services 26 | } 27 | 28 | deinit { 29 | log.info("\(type(of: self)): \(#function)") 30 | } 31 | 32 | func navigate(to step: Step) -> FlowContributors { 33 | guard let step = step as? SampleStep else { return .none } 34 | 35 | switch step { 36 | case .introIsRequired: 37 | return navigationToOnboardingIntroScreen() 38 | case .introIsComplete: 39 | return .end(forwardToParentFlowWithStep: SampleStep.onboardingIsComplete) 40 | default: 41 | return .none 42 | } 43 | } 44 | 45 | private func navigationToOnboardingIntroScreen() -> FlowContributors { 46 | let onboardingIntroViewController = OnboardingIntroViewController.instantiate() 47 | 48 | onboardingIntroViewController.title = "앱 소개" 49 | self.rootViewController.pushViewController(onboardingIntroViewController, animated: false) 50 | return .one(flowContributor: .contribute(withNextPresentable: onboardingIntroViewController, 51 | withNextStepper: onboardingIntroViewController)) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp/Flows/SampleStep.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SampleStep.swift 3 | // RxSwiftReactorKitSampleApp 4 | // 5 | // Created by Clint on 19/12/2018. 6 | // Copyright © 2018 clintjang. All rights reserved. 7 | // 8 | 9 | enum SampleStep: Step { 10 | case onboardingIsRequired 11 | case onboardingIsComplete 12 | 13 | case introIsRequired 14 | case introIsComplete 15 | 16 | case dashboardIsRequired 17 | 18 | case counterIsRequired 19 | 20 | case gitHubSearchIsRequired 21 | 22 | case settingIsRequired 23 | 24 | } 25 | -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp/Flows/SettingFlow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingFlow.swift 3 | // RxSwiftReactorKitSampleApp 4 | // 5 | // Created by Clint on 24/12/2018. 6 | // Copyright © 2018 clintjang. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | final class SettingFlow: Flow { 12 | var root: Presentable { 13 | return self.rootViewController 14 | } 15 | 16 | private let rootViewController = UINavigationController() 17 | private let services: AppServices 18 | 19 | init(withServices services: AppServices) { 20 | self.services = services 21 | } 22 | 23 | deinit { 24 | log.info("\(type(of: self)): \(#function)") 25 | } 26 | 27 | func navigate(to step: Step) -> FlowContributors { 28 | 29 | guard let step = step as? SampleStep else { return FlowContributors.none } 30 | 31 | switch step { 32 | 33 | case .settingIsRequired: 34 | return navigateToSettingScreen() 35 | default: 36 | return FlowContributors.none 37 | } 38 | } 39 | 40 | private func navigateToSettingScreen() -> FlowContributors { 41 | let viewController = SettingViewController.instantiate() 42 | viewController.title = "Setting" 43 | 44 | self.rootViewController.pushViewController(viewController, animated: true) 45 | return .one(flowContributor: .contribute(withNextPresentable: viewController, withNextStepper: OneStepper(withSingleStep: SampleStep.settingIsRequired))) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp/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 | LaunchScreen 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 | -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp/Logging/Logger.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Logger.swift 3 | // RxSwiftReactorKitSampleApp 4 | // 5 | // Created by Clint on 05/01/2019. 6 | // Copyright © 2019 clintjang. All rights reserved. 7 | // 8 | 9 | import CocoaLumberjack 10 | 11 | extension DDLogFlag { 12 | public var level: String { 13 | switch self { 14 | case DDLogFlag.error: return "❤️ ERROR" 15 | case DDLogFlag.warning: return "💛 WARNING" 16 | case DDLogFlag.info: return "💙 INFO" 17 | case DDLogFlag.debug: return "💚 DEBUG" 18 | case DDLogFlag.verbose: return "💜 VERBOSE" 19 | default: return "☠️ UNKNOWN" 20 | } 21 | } 22 | } 23 | 24 | private class LogFormatter: NSObject, DDLogFormatter { 25 | 26 | static let dateFormatter = DateFormatter().then { 27 | $0.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS" 28 | } 29 | 30 | public func format(message logMessage: DDLogMessage) -> String? { 31 | let timestamp = LogFormatter.dateFormatter.string(from: logMessage.timestamp) 32 | let level = logMessage.flag.level 33 | let filename = logMessage.fileName 34 | let function = logMessage.function ?? "" 35 | let line = logMessage.line 36 | let message = logMessage.message.components(separatedBy: "\n").joined(separator: "\n ") 37 | return "\(timestamp) \(level) \(filename).\(function):\(line) - \(message)" 38 | } 39 | 40 | private func formattedDate(from date: Date) -> String { 41 | return LogFormatter.dateFormatter.string(from: date) 42 | } 43 | 44 | } 45 | 46 | /// A shared instance of `Logger`. 47 | let log = Logger() 48 | 49 | final class Logger { 50 | 51 | // MARK: Initialize 52 | 53 | init() { 54 | setenv("XcodeColors", "YES", 0) 55 | 56 | // TTY = Xcode console 57 | DDTTYLogger.sharedInstance.do { 58 | $0.logFormatter = LogFormatter() 59 | $0.colorsEnabled = false /*true*/ // Note: doesn't work in Xcode 8 60 | $0.setForegroundColor(DDMakeColor(30, 121, 214), backgroundColor: nil, for: .info) 61 | $0.setForegroundColor(DDMakeColor(50, 143, 72), backgroundColor: nil, for: .debug) 62 | DDLog.add($0) 63 | } 64 | 65 | // File logger 66 | DDFileLogger().do { 67 | $0.rollingFrequency = TimeInterval(60 * 60 * 24) // 24 hours 68 | $0.logFileManager.maximumNumberOfLogFiles = 7 69 | DDLog.add($0) 70 | } 71 | } 72 | 73 | // MARK: Logging 74 | 75 | func error( 76 | _ items: Any..., 77 | file: StaticString = #file, 78 | function: StaticString = #function, 79 | line: UInt = #line 80 | ) { 81 | let message = self.message(from: items) 82 | DDLogError(message, file: file, function: function, line: line) 83 | } 84 | 85 | func warning( 86 | _ items: Any..., 87 | file: StaticString = #file, 88 | function: StaticString = #function, 89 | line: UInt = #line 90 | ) { 91 | let message = self.message(from: items) 92 | DDLogWarn(message, file: file, function: function, line: line) 93 | } 94 | 95 | func info( 96 | _ items: Any..., 97 | file: StaticString = #file, 98 | function: StaticString = #function, 99 | line: UInt = #line 100 | ) { 101 | let message = self.message(from: items) 102 | DDLogInfo(message, file: file, function: function, line: line) 103 | } 104 | 105 | func debug( 106 | _ items: Any..., 107 | file: StaticString = #file, 108 | function: StaticString = #function, 109 | line: UInt = #line 110 | ) { 111 | let message = self.message(from: items) 112 | DDLogDebug(message, file: file, function: function, line: line) 113 | } 114 | 115 | func verbose( 116 | _ items: Any..., 117 | file: StaticString = #file, 118 | function: StaticString = #function, 119 | line: UInt = #line 120 | ) { 121 | let message = self.message(from: items) 122 | DDLogVerbose(message, file: file, function: function, line: line) 123 | } 124 | 125 | // MARK: Utils 126 | 127 | private func message(from items: [Any]) -> String { 128 | return items 129 | .map { String(describing: $0) } 130 | .joined(separator: " ") 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp/Models/ModelType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ModelType.swift 3 | // RxSwiftReactorKitSampleApp 4 | // 5 | // Created by Clint on 05/01/2019. 6 | // Copyright © 2019 clintjang. All rights reserved. 7 | // 8 | 9 | import Then 10 | 11 | protocol ModelType: Codable, Then { 12 | associatedtype Event 13 | 14 | static var dateDecodingStrategy: JSONDecoder.DateDecodingStrategy { get } 15 | } 16 | 17 | extension ModelType { 18 | static var dateDecodingStrategy: JSONDecoder.DateDecodingStrategy { 19 | return .iso8601 20 | } 21 | 22 | static var decoder: JSONDecoder { 23 | let decoder = JSONDecoder() 24 | decoder.dateDecodingStrategy = self.dateDecodingStrategy 25 | return decoder 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp/Networking/GitHubAPI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitHubSearchAPI.swift 3 | // RxSwiftReactorKitSampleApp 4 | // 5 | // Created by Clint on 07/01/2019. 6 | // Copyright © 2019 clintjang. All rights reserved. 7 | // 8 | 9 | import Moya 10 | import MoyaSugar 11 | 12 | enum GitHubAPI { 13 | case search(query: String, page: String) 14 | } 15 | 16 | extension GitHubAPI: SugarTargetType { 17 | var baseURL: URL { 18 | return URL(string: "https://api.github.com")! 19 | } 20 | 21 | var route: Route { 22 | switch self { 23 | case .search: 24 | return .get("search/repositories") 25 | } 26 | } 27 | 28 | var parameters: Parameters? { 29 | switch self { 30 | case let .search(query, page): 31 | return ["q": query, "page": page] 32 | } 33 | } 34 | 35 | var headers: [String: String]? { 36 | return nil 37 | } 38 | 39 | var sampleData: Data { 40 | return Data() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp/Networking/Networking.swift: -------------------------------------------------------------------------------- 1 | import MoyaSugar 2 | 3 | final class Networking: MoyaSugarProvider { 4 | init(plugins: [PluginType] = []) { 5 | let configuration = URLSessionConfiguration.default 6 | configuration.httpAdditionalHeaders = Manager.defaultHTTPHeaders 7 | configuration.timeoutIntervalForRequest = 10 8 | 9 | let manager = Manager(configuration: configuration) 10 | manager.startRequestsImmediately = false 11 | super.init(manager: manager, plugins: plugins) 12 | } 13 | 14 | func request( 15 | _ target: Target, 16 | file: StaticString = #file, 17 | function: StaticString = #function, 18 | line: UInt = #line 19 | ) -> Single { 20 | let requestString = "\(target.method) \(target.path)" 21 | 22 | return self.rx.request(target) 23 | .filterSuccessfulStatusCodes() 24 | .do( 25 | onSuccess: { value in 26 | let message = "SUCCESS: \(requestString) (\(value.statusCode))" 27 | log.debug(message, file: file, function: function, line: line) 28 | }, 29 | onError: { error in 30 | if let response = (error as? MoyaError)?.response { 31 | if let jsonObject = try? response.mapJSON(failsOnEmptyData: false) { 32 | let message = "FAILURE: \(requestString) (\(response.statusCode))\n\(jsonObject)" 33 | log.warning(message, file: file, function: function, line: line) 34 | } else if let rawString = String(data: response.data, encoding: .utf8) { 35 | let message = "FAILURE: \(requestString) (\(response.statusCode))\n\(rawString)" 36 | log.warning(message, file: file, function: function, line: line) 37 | } else { 38 | let message = "FAILURE: \(requestString) (\(response.statusCode))" 39 | log.warning(message, file: file, function: function, line: line) 40 | } 41 | } else { 42 | let message = "FAILURE: \(requestString)\n\(error)" 43 | log.warning(message, file: file, function: function, line: line) 44 | } 45 | }, 46 | onSubscribed: { 47 | let message = "REQUEST: \(requestString)" 48 | log.debug(message, file: file, function: function, line: line) 49 | } 50 | ) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp/Networking/Services/GitHubService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitHubService.swift 3 | // RxSwiftReactorKitSampleApp 4 | // 5 | // Created by Clint on 07/01/2019. 6 | // Copyright © 2019 clintjang. All rights reserved. 7 | // 8 | 9 | protocol GitHubServiceType { 10 | func search(query: String, page: String) -> Single 11 | } 12 | 13 | final class GitHubService: GitHubServiceType { 14 | fileprivate let networking = Networking() 15 | 16 | func search(query: String, page: String) -> Single { 17 | return self.networking.request(.search(query: query, page: page)) 18 | .map { response in 19 | log.info(response) 20 | return true 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp/Resources/Localizable/Localized.swift: -------------------------------------------------------------------------------- 1 | // swiftlint:disable all 2 | // Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen 3 | 4 | import Foundation 5 | 6 | // swiftlint:disable superfluous_disable_command 7 | // swiftlint:disable file_length 8 | 9 | // MARK: - Strings 10 | 11 | // swiftlint:disable explicit_type_interface function_parameter_count identifier_name line_length 12 | // swiftlint:disable nesting type_body_length type_name 13 | internal enum Localized { 14 | /// 코딩.. 15 | internal static let coding = Localized.tr("Localizable", "coding") 16 | } 17 | // swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length 18 | // swiftlint:enable nesting type_body_length type_name 19 | 20 | // MARK: - Implementation Details 21 | 22 | extension Localized { 23 | private static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String { 24 | // swiftlint:disable:next nslocalizedstring_key 25 | let format = NSLocalizedString(key, tableName: table, bundle: Bundle(for: BundleToken.self), comment: "") 26 | return String(format: format, locale: Locale.current, arguments: args) 27 | } 28 | } 29 | 30 | private final class BundleToken {} 31 | -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp/Resources/Localizable/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | RxSwiftReactorKitSampleApp 4 | 5 | Created by Clint on 04/12/2018. 6 | Copyright © 2018 clintjang. All rights reserved. 7 | */ 8 | 9 | "coding" = "Coding.."; 10 | -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp/Resources/Localizable/ko.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | RxSwiftReactorKitSampleApp 4 | 5 | Created by Clint on 04/12/2018. 6 | Copyright © 2018 clintjang. All rights reserved. 7 | */ 8 | 9 | "coding" = "코딩.."; 10 | -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | @import RxSwift; 6 | @import RxCocoa; 7 | @import ReactorKit; 8 | @import RxFlow; 9 | @import Moya; 10 | 11 | 12 | -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp/Scenarios/Dashboard/Counter/CounterViewController.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 | 31 | 36 | 41 | 44 | 50 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp/Scenarios/Dashboard/Counter/CounterViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CounterViewController.swift 3 | // RxSwiftReactorKitSampleApp 4 | // 5 | // Created by Clint on 24/12/2018. 6 | // Copyright © 2018 clintjang. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Reusable 11 | import SafariServices 12 | 13 | class CounterViewController: BaseViewController, StoryboardBased, StoryboardView { 14 | 15 | @IBOutlet var decreaseButton: UIButton! 16 | @IBOutlet var increaseButton: UIButton! 17 | @IBOutlet var valueLabel: UILabel! 18 | @IBOutlet var activityIndicatorView: UIActivityIndicatorView! 19 | @IBOutlet weak var linkButton: UIButton! 20 | 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | 24 | self.reactor = CounterViewReactor() 25 | } 26 | 27 | func bind(reactor: CounterViewReactor) { 28 | bindAction(reactor) 29 | bindState(reactor) 30 | bindView(reactor) 31 | } 32 | } 33 | 34 | // MARK: - 35 | // MARK: Bind 36 | private extension CounterViewController { 37 | func bindView(_ reactor: CounterViewReactor) { 38 | linkButton.rx.tap 39 | .subscribe(onNext: { [weak self] in 40 | guard let self = self else { return } 41 | guard let url = URL(string: "https://github.com/ReactorKit/ReactorKit/blob/master/Examples/Counter/README.md") else { return } 42 | let viewController = SFSafariViewController(url: url) 43 | self.present(viewController, animated: true, completion: nil) 44 | }) 45 | .disposed(by: disposeBag) 46 | } 47 | 48 | func bindAction(_ reactor: CounterViewReactor) { 49 | increaseButton.rx.tap // Tap event 50 | .map { Reactor.Action.increase } // Convert to Action.increase 51 | .bind(to: reactor.action) // Bind to reactor.action 52 | .disposed(by: disposeBag) 53 | 54 | decreaseButton.rx.tap 55 | .map { Reactor.Action.decrease } 56 | .bind(to: reactor.action) 57 | .disposed(by: disposeBag) 58 | } 59 | 60 | func bindState(_ reactor: CounterViewReactor) { 61 | reactor.state.map { $0.value } // 10 62 | .distinctUntilChanged() 63 | .map { "\($0)" } // "10" 64 | .bind(to: valueLabel.rx.text) // Bind to valueLabel 65 | .disposed(by: disposeBag) 66 | 67 | reactor.state.map { $0.isLoading } 68 | .distinctUntilChanged() 69 | .bind(to: activityIndicatorView.rx.isAnimating) 70 | .disposed(by: disposeBag) 71 | } 72 | } 73 | 74 | // MARK: - 75 | -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp/Scenarios/Dashboard/Counter/CounterViewReactor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CounterViewReactor.swift 3 | // RxSwiftReactorKitSampleApp 4 | // 5 | // Created by Clint on 24/12/2018. 6 | // Copyright © 2018 clintjang. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | final class CounterViewReactor: Reactor { 12 | 13 | // Action is an user interaction 14 | enum Action { 15 | case increase 16 | case decrease 17 | } 18 | 19 | // Mutate is a state manipulator which is not exposed to a view 20 | enum Mutation { 21 | case increaseValue 22 | case decreaseValue 23 | case setLoading(Bool) 24 | } 25 | 26 | // State is a current view state 27 | struct State { 28 | var value: Int 29 | var isLoading: Bool 30 | } 31 | 32 | let initialState: State 33 | 34 | init() { 35 | self.initialState = State( 36 | value: 0, // start from 0 37 | isLoading: false 38 | ) 39 | } 40 | 41 | // Action -> Mutation 42 | func mutate(action: Action) -> Observable { 43 | switch action { 44 | case .increase: 45 | return Observable.concat([ 46 | Observable.just(Mutation.setLoading(true)), 47 | Observable.just(Mutation.increaseValue).delay(0.5, scheduler: MainScheduler.instance), 48 | Observable.just(Mutation.setLoading(false)) 49 | ]) 50 | 51 | case .decrease: 52 | return Observable.concat([ 53 | Observable.just(Mutation.setLoading(true)), 54 | Observable.just(Mutation.decreaseValue).delay(0.5, scheduler: MainScheduler.instance), 55 | Observable.just(Mutation.setLoading(false)) 56 | ]) 57 | } 58 | } 59 | 60 | // Mutation -> State 61 | func reduce(state: State, mutation: Mutation) -> State { 62 | var state = state 63 | switch mutation { 64 | case .increaseValue: 65 | state.value += 1 66 | 67 | case .decreaseValue: 68 | state.value -= 1 69 | 70 | case let .setLoading(isLoading): 71 | state.isLoading = isLoading 72 | } 73 | return state 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp/Scenarios/Dashboard/GitHubSearch/GitHubSearchViewController.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 47 | 48 | 49 | 50 | 51 | 52 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp/Scenarios/Dashboard/GitHubSearch/GitHubSearchViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitHubSearchViewController.swift 3 | // RxSwiftReactorKitSampleApp 4 | // 5 | // Created by Clint on 04/12/2018. 6 | // Copyright © 2018 clintjang. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Reusable 11 | import SafariServices 12 | 13 | class GitHubSearchViewController: BaseViewController, StoryboardView, StoryboardBased { 14 | 15 | @IBOutlet weak var tableView: UITableView! 16 | @IBOutlet weak var linkButton: UIButton! 17 | 18 | let searchController = UISearchController(searchResultsController: nil) 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | self.definesPresentationContext = true 23 | 24 | reactor = GitHubSearchViewReactor() 25 | tableView.scrollIndicatorInsets.top = tableView.contentInset.top 26 | searchController.dimsBackgroundDuringPresentation = false 27 | if #available(iOS 11.0, *) { 28 | navigationItem.searchController = searchController 29 | } 30 | } 31 | 32 | func bind(reactor: GitHubSearchViewReactor) { 33 | 34 | bindAction(reactor) 35 | bindState(reactor) 36 | bindView(reactor) 37 | 38 | } 39 | } 40 | 41 | // MARK: - 42 | // MARK: Bind 43 | private extension GitHubSearchViewController { 44 | func bindView(_ reactor: GitHubSearchViewReactor) { 45 | tableView.rx.itemSelected 46 | .subscribe(onNext: { [weak self, weak reactor] indexPath in 47 | guard let self = self, let strongReactor = reactor else { return } 48 | self.view.endEditing(true) 49 | self.tableView.deselectRow(at: indexPath, animated: false) 50 | let repo = strongReactor.currentState.repos[indexPath.row] 51 | guard let url = URL(string: "https://github.com/\(repo)") else { return } 52 | let viewController = SFSafariViewController(url: url) 53 | self.searchController.present(viewController, animated: true, completion: nil) 54 | }) 55 | .disposed(by: disposeBag) 56 | 57 | linkButton.rx.tap 58 | .subscribe(onNext: { [weak self] in 59 | guard let self = self else { return } 60 | guard let url = URL(string: "https://github.com/ReactorKit/ReactorKit/blob/master/Examples/GitHubSearch/README.md") else { return } 61 | let viewController = SFSafariViewController(url: url) 62 | self.present(viewController, animated: true, completion: nil) 63 | }) 64 | .disposed(by: disposeBag) 65 | } 66 | 67 | func bindAction(_ reactor: GitHubSearchViewReactor) { 68 | searchController.searchBar.rx.text 69 | .throttle(0.3, scheduler: MainScheduler.instance) 70 | .map { Reactor.Action.updateQuery($0) } 71 | .bind(to: reactor.action) 72 | .disposed(by: disposeBag) 73 | 74 | tableView.rx.contentOffset 75 | .filter { [weak self] offset in 76 | guard let self = self else { return false } 77 | guard self.tableView.frame.height > 0 else { return false } 78 | return offset.y + self.tableView.frame.height >= self.tableView.contentSize.height - 100 79 | } 80 | .map { _ in Reactor.Action.loadNextPage } 81 | .bind(to: reactor.action) 82 | .disposed(by: disposeBag) 83 | } 84 | 85 | func bindState(_ reactor: GitHubSearchViewReactor) { 86 | reactor.state.map { $0.repos } 87 | .bind(to: tableView.rx.items(cellIdentifier: "cell")) { _, repo, cell in 88 | cell.textLabel?.text = repo 89 | } 90 | .disposed(by: disposeBag) 91 | } 92 | } 93 | 94 | // MARK: - 95 | -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp/Scenarios/Dashboard/GitHubSearch/GitHubSearchViewReactor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GitHubSearchViewReactor.swift 3 | // RxSwiftReactorKitSampleApp 4 | // 5 | // Created by Clint on 04/12/2018. 6 | // Copyright © 2018 clintjang. All rights reserved. 7 | // 8 | 9 | //https://github.com/ReactorKit/ReactorKit/blob/master/Examples/GitHubSearch/GitHubSearch/GitHubSearchViewReactor.swift 10 | 11 | final class GitHubSearchViewReactor: Reactor { 12 | enum Action { 13 | case updateQuery(String?) 14 | case loadNextPage 15 | } 16 | 17 | enum Mutation { 18 | case setQuery(String?) 19 | case setRepos([String], nextPage: Int?) 20 | case appendRepos([String], nextPage: Int?) 21 | case setLoadingNextPage(Bool) 22 | } 23 | 24 | struct State { 25 | var query: String? 26 | var repos: [String] = [] 27 | var nextPage: Int? 28 | var isLoadingNextPage: Bool = false 29 | } 30 | 31 | let initialState = State() 32 | 33 | func mutate(action: Action) -> Observable { 34 | switch action { 35 | case let .updateQuery(query): 36 | 37 | return Observable.concat([ 38 | // 1) set current state's query (.setQuery) 39 | Observable.just(Mutation.setQuery(query)), 40 | 41 | // 2) call API and set repos (.setRepos) 42 | self.search(query: query, page: 1) 43 | // cancel previous request when the new `.updateQuery` action is fired 44 | .takeUntil(self.action.filter(isUpdateQueryAction)) 45 | .map { Mutation.setRepos($0, nextPage: $1) } 46 | ]) 47 | 48 | case .loadNextPage: 49 | guard !self.currentState.isLoadingNextPage else { return Observable.empty() } // prevent from multiple requests 50 | guard let page = self.currentState.nextPage else { return Observable.empty() } 51 | return Observable.concat([ 52 | // 1) set loading status to true 53 | Observable.just(Mutation.setLoadingNextPage(true)), 54 | 55 | // 2) call API and append repos 56 | self.search(query: self.currentState.query, page: page) 57 | .takeUntil(self.action.filter(isUpdateQueryAction)) 58 | .map { Mutation.appendRepos($0, nextPage: $1) }, 59 | 60 | // 3) set loading status to false 61 | Observable.just(Mutation.setLoadingNextPage(false)) 62 | ]) 63 | } 64 | } 65 | 66 | func reduce(state: State, mutation: Mutation) -> State { 67 | switch mutation { 68 | case let .setQuery(query): 69 | var newState = state 70 | newState.query = query 71 | return newState 72 | 73 | case let .setRepos(repos, nextPage): 74 | var newState = state 75 | newState.repos = repos 76 | newState.nextPage = nextPage 77 | return newState 78 | 79 | case let .appendRepos(repos, nextPage): 80 | var newState = state 81 | newState.repos.append(contentsOf: repos) 82 | newState.nextPage = nextPage 83 | return newState 84 | 85 | case let .setLoadingNextPage(isLoadingNextPage): 86 | var newState = state 87 | newState.isLoadingNextPage = isLoadingNextPage 88 | return newState 89 | } 90 | } 91 | 92 | private func url(for query: String?, page: Int) -> URL? { 93 | guard let query = query, !query.isEmpty else { return nil } 94 | return URL(string: "https://api.github.com/search/repositories?q=\(query)&page=\(page)") 95 | } 96 | 97 | private func search(query: String?, page: Int) -> Observable<(repos: [String], nextPage: Int?)> { 98 | let emptyResult: ([String], Int?) = ([], nil) 99 | guard let url = self.url(for: query, page: page) else { return .just(emptyResult) } 100 | return URLSession.shared.rx.json(url: url) 101 | .map { json -> ([String], Int?) in 102 | guard let dict = json as? [String: Any] else { return emptyResult } 103 | guard let items = dict["items"] as? [[String: Any]] else { return emptyResult } 104 | let repos = items.compactMap { $0["full_name"] as? String } 105 | let nextPage = repos.isEmpty ? nil : page + 1 106 | return (repos, nextPage) 107 | } 108 | .do(onError: { error in 109 | if case let .some(.httpRequestFailed(response, _)) = error as? RxCocoaURLError, response.statusCode == 403 { 110 | log.info("⚠️ GitHub API rate limit exceeded. Wait for 60 seconds and try again.") 111 | } 112 | }) 113 | .catchErrorJustReturn(emptyResult) 114 | } 115 | 116 | private func isUpdateQueryAction(_ action: Action) -> Bool { 117 | if case .updateQuery = action { 118 | return true 119 | } else { 120 | return false 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp/Scenarios/Dashboard/Setting/SettingViewController.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 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp/Scenarios/Dashboard/Setting/SettingViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingViewController.swift 3 | // RxSwiftReactorKitSampleApp 4 | // 5 | // Created by Clint on 03/12/2018. 6 | // Copyright © 2018 clintjang. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Reusable 11 | 12 | class SettingViewController: BaseViewController, StoryboardView, StoryboardBased { 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | reactor = SettingViewReactor() 17 | 18 | // GitHubService Test 19 | let gitHubService = GitHubService() 20 | let test = gitHubService.search(query: "test", page: "1").asObservable() 21 | test.debug().subscribe().disposed(by: self.disposeBag) 22 | 23 | } 24 | 25 | func bind(reactor: SettingViewReactor) { 26 | bindAction(reactor) 27 | bindState(reactor) 28 | bindView(reactor) 29 | } 30 | } 31 | 32 | // MARK: - 33 | // MARK: Bind 34 | private extension SettingViewController { 35 | func bindView(_ reactor: SettingViewReactor) {} 36 | func bindAction(_ reactor: SettingViewReactor) {} 37 | func bindState(_ reactor: SettingViewReactor) {} 38 | } 39 | 40 | // MARK: - 41 | -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp/Scenarios/Dashboard/Setting/SettingViewReactor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingViewReactor.swift 3 | // RxSwiftReactorKitSampleApp 4 | // 5 | // Created by Clint on 03/12/2018. 6 | // Copyright © 2018 clintjang. All rights reserved. 7 | // 8 | 9 | final class SettingViewReactor: Reactor { 10 | 11 | enum Action { 12 | case Temp 13 | } 14 | 15 | struct State { 16 | var temp: String = "Temp" 17 | } 18 | 19 | let initialState = State() 20 | } 21 | -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp/Scenarios/Onboarding/OnboardingIntroViewController.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 34 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp/Scenarios/Onboarding/OnboardingIntroViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OnboardingIntroViewController.swift 3 | // RxSwiftReactorKitSampleApp 4 | // 5 | // Created by Clint on 30/12/2018. 6 | // Copyright © 2018 clintjang. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Reusable 11 | 12 | final class OnboardingIntroViewController: BaseViewController, StoryboardView, StoryboardBased, Stepper { 13 | let steps = PublishRelay() 14 | 15 | @IBOutlet weak var completeButton: UIButton! 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | 20 | self.reactor = OnboardingIntroViewReactor() 21 | } 22 | 23 | func bind(reactor: OnboardingIntroViewReactor) { 24 | bindAction(reactor) 25 | bindState(reactor) 26 | } 27 | } 28 | 29 | // MARK: - 30 | // MARK: Bind 31 | private extension OnboardingIntroViewController { 32 | func bindAction(_ reactor: OnboardingIntroViewReactor) { 33 | completeButton.rx.tap 34 | .map { _ in Reactor.Action.introIsComplete } 35 | .bind(to: reactor.action) 36 | .disposed(by: self.disposeBag) 37 | } 38 | 39 | func bindState(_ reactor: OnboardingIntroViewReactor) { 40 | reactor.state 41 | .map { $0.step } 42 | .bind(to: self.steps) 43 | .disposed(by: disposeBag) 44 | } 45 | } 46 | 47 | // MARK: - 48 | -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp/Scenarios/Onboarding/OnboardingIntroViewReactor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OnboardingIntroViewReactor.swift 3 | // RxSwiftReactorKitSampleApp 4 | // 5 | // Created by Clint on 30/12/2018. 6 | // Copyright © 2018 clintjang. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | final class OnboardingIntroViewReactor: Reactor, HasPreferencesService { 12 | let preferencesService = PreferencesService() 13 | 14 | enum Action { 15 | case introIsComplete 16 | } 17 | 18 | enum Mutation { 19 | case moveDashboard 20 | } 21 | 22 | struct State { 23 | var step: Step = SampleStep.introIsRequired 24 | } 25 | 26 | let initialState = State() 27 | 28 | func mutate(action: OnboardingIntroViewReactor.Action) -> Observable { 29 | switch action { 30 | case .introIsComplete: 31 | preferencesService.setOnboarded() 32 | return Observable.just(Mutation.moveDashboard) 33 | } 34 | } 35 | 36 | func reduce(state: OnboardingIntroViewReactor.State, mutation: OnboardingIntroViewReactor.Mutation) -> OnboardingIntroViewReactor.State { 37 | var state = state 38 | switch mutation { 39 | case .moveDashboard: 40 | state.step = SampleStep.introIsComplete 41 | return state 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /RxSwiftReactorKitSampleApp/RxSwiftReactorKitSampleApp/Services/PreferencesService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreferencesService.swift 3 | // RxSwiftReactorKitSampleApp 4 | // 5 | // Created by Clint on 19/12/2018. 6 | // Copyright © 2018 clintjang. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | 12 | protocol HasPreferencesService { 13 | var preferencesService: PreferencesService { get } 14 | } 15 | 16 | /// Represents the keys that are used to store user defaults 17 | struct UserPreferences { 18 | private init () {} 19 | 20 | /// key for onBoarded preference 21 | static let onBoarded = "onBoarded" 22 | } 23 | 24 | /// This service manage the user preferences 25 | class PreferencesService { 26 | 27 | /// sets the onBoarded preference to true 28 | func setOnboarded () { 29 | let defaults = UserDefaults.standard 30 | defaults.set(true, forKey: UserPreferences.onBoarded) 31 | } 32 | 33 | /// removes the onBoarded preference 34 | func setNotOnboarded () { 35 | let defaults = UserDefaults.standard 36 | defaults.removeObject(forKey: UserPreferences.onBoarded) 37 | } 38 | 39 | /// Returns true if the user has already onboarded, false otherwise 40 | /// 41 | /// - Returns: true if the user has already onboarded, false otherwise 42 | func isOnboarded () -> Bool { 43 | let defaults = UserDefaults.standard 44 | return defaults.bool(forKey: UserPreferences.onBoarded) 45 | } 46 | } 47 | 48 | extension PreferencesService: ReactiveCompatible {} 49 | 50 | extension Reactive where Base: PreferencesService { 51 | var isOnboarded: Observable { 52 | return UserDefaults.standard 53 | .rx 54 | .observe(Bool.self, UserPreferences.onBoarded) 55 | .map { $0 ?? false } 56 | } 57 | } 58 | --------------------------------------------------------------------------------