├── .ruby-version ├── .swift-version ├── _config.yml ├── .bundle └── config ├── img ├── draw │ ├── architecture.jpg │ └── ios-clean-arch.graffle └── ios │ ├── add_task_framed.png │ ├── settings_framed.png │ └── today_tasks_framed.png ├── CleanArchitecture ├── Resources │ ├── Images │ │ └── Assets.xcassets │ │ │ ├── Contents.json │ │ │ ├── img-checked.imageset │ │ │ ├── checked@1x.png │ │ │ ├── checked@2x.png │ │ │ ├── checked@3x.png │ │ │ └── Contents.json │ │ │ ├── img-today.imageset │ │ │ ├── img-today@1x.png │ │ │ ├── img-today@2x.png │ │ │ ├── img-today@3x.png │ │ │ └── Contents.json │ │ │ ├── img-settings.imageset │ │ │ ├── settings@1x.png │ │ │ ├── settings@2x.png │ │ │ ├── settings@3x.png │ │ │ └── Contents.json │ │ │ ├── img-splash.imageset │ │ │ ├── img-splash@1x.png │ │ │ ├── img-splash@2x.png │ │ │ ├── img-splash@3x.png │ │ │ └── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ └── LaunchScreen │ │ └── Base.lproj │ │ └── LaunchScreen.storyboard ├── Define │ ├── App.swift │ ├── App.Image.swift │ ├── App.Key.swift │ ├── App.String.swift │ └── App.Theme.swift ├── Services │ ├── Realm │ │ ├── Core │ │ │ ├── RealmError.swift │ │ │ ├── ModelConvertibleType.swift │ │ │ ├── RealmRepresentable.swift │ │ │ ├── Object.swift │ │ │ ├── Reactive.swift │ │ │ └── RealmRepository.swift │ │ ├── Entities │ │ │ └── RTask.swift │ │ └── Modules │ │ │ └── RealmTask.swift │ └── Network │ │ ├── Core │ │ ├── NetworkError.swift │ │ ├── HTTPTask.swift │ │ ├── TargetType.swift │ │ ├── Mapping.swift │ │ └── Router.swift │ │ └── Modules │ │ └── Info │ │ ├── InfoNetwork.swift │ │ └── InfoTarget.swift ├── Application │ ├── Model │ │ ├── Entity │ │ │ ├── Settings.swift │ │ │ ├── Info.swift │ │ │ └── Task.swift │ │ └── UseCase │ │ │ ├── InfoUseCase.swift │ │ │ └── TaskUseCase.swift │ ├── Base │ │ ├── View.swift │ │ ├── ViewModel.swift │ │ ├── TableViewCell.swift │ │ ├── ViewController.swift │ │ ├── Coordinate.swift │ │ └── NavigationController.swift │ ├── View-ViewModel │ │ ├── Scene │ │ │ ├── 000 - TabBar │ │ │ │ ├── TabBarViewModel.swift │ │ │ │ ├── TabBarController.swift │ │ │ │ └── TabBarCoordinator.swift │ │ │ ├── 002 - Settings │ │ │ │ ├── SettingsCoordinator.swift │ │ │ │ ├── SettingsViewController.swift │ │ │ │ ├── SettingsViewModel.swift │ │ │ │ └── SettingsViewController.xib │ │ │ ├── 003 - AddTask │ │ │ │ ├── AddTaskCoordinator.swift │ │ │ │ ├── AddTaskViewModel.swift │ │ │ │ ├── AddTaskViewController.swift │ │ │ │ └── AddTaskViewController.xib │ │ │ └── 001 - Today │ │ │ │ ├── TodayCoordinator.swift │ │ │ │ ├── TodayViewController.swift │ │ │ │ ├── TodayViewController.xib │ │ │ │ └── TodayViewModel.swift │ │ └── Control │ │ │ ├── Cell │ │ │ ├── TaskCell │ │ │ │ ├── TaskCellViewModel.swift │ │ │ │ ├── TaskCell.swift │ │ │ │ └── TaskCell.xib │ │ │ └── DetailCell │ │ │ │ ├── DetailCellViewModel.swift │ │ │ │ ├── DetailCell.swift │ │ │ │ └── DetailCell.xib │ │ │ └── View │ │ │ └── SView.swift │ ├── Application.swift │ ├── AppDelegate.swift │ └── RootCoordinator.swift ├── Common │ ├── Apple │ │ ├── UITableViewCell.swift │ │ ├── JSONDecoder.swift │ │ ├── Sequence.swift │ │ ├── DateFormatter.swift │ │ ├── Date.swift │ │ ├── UIColor.swift │ │ └── Dictionary.swift │ └── Reactive │ │ ├── Driver.swift │ │ ├── RxExtension.swift │ │ ├── RxError.swift │ │ └── RxIndicator.swift └── Support │ └── Info.plist ├── api └── about.json ├── Gemfile ├── CleanArchitecture.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── project.pbxproj ├── fastlane ├── Appfile ├── Scanfile ├── Fastfile └── README.md ├── CleanArchitecture.xcworkspace ├── xcshareddata │ └── IDEWorkspaceChecks.plist └── contents.xcworkspacedata ├── scripts └── lint ├── CleanArchitectureTests ├── CleanArchitectureTests.swift └── Info.plist ├── .travis.yml ├── Podfile ├── LICENSE ├── .gitignore ├── Podfile.lock ├── .swiftlint.yml ├── Gemfile.lock └── README.md /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.5.1 2 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 4.2 2 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /.bundle/config: -------------------------------------------------------------------------------- 1 | --- 2 | BUNDLE_RETRY: "3" 3 | BUNDLE_PATH: "vendor/bundle" 4 | BUNDLE_JOBS: "4" 5 | -------------------------------------------------------------------------------- /img/draw/architecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suho/ios-clean-architecture/HEAD/img/draw/architecture.jpg -------------------------------------------------------------------------------- /img/ios/add_task_framed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suho/ios-clean-architecture/HEAD/img/ios/add_task_framed.png -------------------------------------------------------------------------------- /img/ios/settings_framed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suho/ios-clean-architecture/HEAD/img/ios/settings_framed.png -------------------------------------------------------------------------------- /img/ios/today_tasks_framed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suho/ios-clean-architecture/HEAD/img/ios/today_tasks_framed.png -------------------------------------------------------------------------------- /img/draw/ios-clean-arch.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suho/ios-clean-architecture/HEAD/img/draw/ios-clean-arch.graffle -------------------------------------------------------------------------------- /CleanArchitecture/Resources/Images/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /api/about.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iOS Clean Architecture", 3 | "author": "suho", 4 | "repo": "@suho/ios-clean-architecture", 5 | "year": 2019 6 | } 7 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "cocoapods" 6 | gem "fastlane" 7 | gem "linterbot" 8 | gem "xcpretty" 9 | -------------------------------------------------------------------------------- /CleanArchitecture/Define/App.swift: -------------------------------------------------------------------------------- 1 | // 2 | // App.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 12/16/18. 6 | // Copyright © 2018 mlsuho. All rights reserved. 7 | // 8 | 9 | enum App {} 10 | -------------------------------------------------------------------------------- /CleanArchitecture/Resources/Images/Assets.xcassets/img-checked.imageset/checked@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suho/ios-clean-architecture/HEAD/CleanArchitecture/Resources/Images/Assets.xcassets/img-checked.imageset/checked@1x.png -------------------------------------------------------------------------------- /CleanArchitecture/Resources/Images/Assets.xcassets/img-checked.imageset/checked@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suho/ios-clean-architecture/HEAD/CleanArchitecture/Resources/Images/Assets.xcassets/img-checked.imageset/checked@2x.png -------------------------------------------------------------------------------- /CleanArchitecture/Resources/Images/Assets.xcassets/img-checked.imageset/checked@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suho/ios-clean-architecture/HEAD/CleanArchitecture/Resources/Images/Assets.xcassets/img-checked.imageset/checked@3x.png -------------------------------------------------------------------------------- /CleanArchitecture/Resources/Images/Assets.xcassets/img-today.imageset/img-today@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suho/ios-clean-architecture/HEAD/CleanArchitecture/Resources/Images/Assets.xcassets/img-today.imageset/img-today@1x.png -------------------------------------------------------------------------------- /CleanArchitecture/Resources/Images/Assets.xcassets/img-today.imageset/img-today@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suho/ios-clean-architecture/HEAD/CleanArchitecture/Resources/Images/Assets.xcassets/img-today.imageset/img-today@2x.png -------------------------------------------------------------------------------- /CleanArchitecture/Resources/Images/Assets.xcassets/img-today.imageset/img-today@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suho/ios-clean-architecture/HEAD/CleanArchitecture/Resources/Images/Assets.xcassets/img-today.imageset/img-today@3x.png -------------------------------------------------------------------------------- /CleanArchitecture/Resources/Images/Assets.xcassets/img-settings.imageset/settings@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suho/ios-clean-architecture/HEAD/CleanArchitecture/Resources/Images/Assets.xcassets/img-settings.imageset/settings@1x.png -------------------------------------------------------------------------------- /CleanArchitecture/Resources/Images/Assets.xcassets/img-settings.imageset/settings@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suho/ios-clean-architecture/HEAD/CleanArchitecture/Resources/Images/Assets.xcassets/img-settings.imageset/settings@2x.png -------------------------------------------------------------------------------- /CleanArchitecture/Resources/Images/Assets.xcassets/img-settings.imageset/settings@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suho/ios-clean-architecture/HEAD/CleanArchitecture/Resources/Images/Assets.xcassets/img-settings.imageset/settings@3x.png -------------------------------------------------------------------------------- /CleanArchitecture/Resources/Images/Assets.xcassets/img-splash.imageset/img-splash@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suho/ios-clean-architecture/HEAD/CleanArchitecture/Resources/Images/Assets.xcassets/img-splash.imageset/img-splash@1x.png -------------------------------------------------------------------------------- /CleanArchitecture/Resources/Images/Assets.xcassets/img-splash.imageset/img-splash@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suho/ios-clean-architecture/HEAD/CleanArchitecture/Resources/Images/Assets.xcassets/img-splash.imageset/img-splash@2x.png -------------------------------------------------------------------------------- /CleanArchitecture/Resources/Images/Assets.xcassets/img-splash.imageset/img-splash@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suho/ios-clean-architecture/HEAD/CleanArchitecture/Resources/Images/Assets.xcassets/img-splash.imageset/img-splash@3x.png -------------------------------------------------------------------------------- /CleanArchitecture.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /fastlane/Appfile: -------------------------------------------------------------------------------- 1 | # app_identifier("[[APP_IDENTIFIER]]") # The bundle identifier of your app 2 | # apple_id("[[APPLE_ID]]") # Your Apple email address 3 | 4 | 5 | # For more information about the Appfile, see: 6 | # https://docs.fastlane.tools/advanced/#appfile 7 | -------------------------------------------------------------------------------- /CleanArchitecture.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CleanArchitecture.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /CleanArchitecture/Services/Realm/Core/RealmError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RealmError.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 12/23/18. 6 | // Copyright © 2018 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum RealmError: Error { 12 | case notFound 13 | case system 14 | } 15 | -------------------------------------------------------------------------------- /CleanArchitecture/Application/Model/Entity/Settings.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Settings.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 1/16/19. 6 | // Copyright © 2019 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | final class Settings { 12 | static var current: Settings = Settings() 13 | } 14 | -------------------------------------------------------------------------------- /CleanArchitecture/Services/Network/Core/NetworkError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkError.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 12/17/18. 6 | // Copyright © 2018 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum NetworkError: Error { 12 | case json 13 | case offline 14 | } 15 | -------------------------------------------------------------------------------- /CleanArchitecture.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CleanArchitecture/Application/Base/View.swift: -------------------------------------------------------------------------------- 1 | // 2 | // View.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 1/16/19. 6 | // Copyright © 2019 mlsuho. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol View { 12 | associatedtype ViewModelType: ViewModel 13 | var viewModel: ViewModelType! { get set } 14 | } 15 | -------------------------------------------------------------------------------- /CleanArchitecture/Application/Model/UseCase/InfoUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InfoUseCase.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 12/20/18. 6 | // Copyright © 2018 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | 12 | protocol InfoUseCase { 13 | func about() -> Observable 14 | } 15 | -------------------------------------------------------------------------------- /scripts/lint: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "TRAVIS_REPO_SLUG = $TRAVIS_REPO_SLUG" 4 | echo "TRAVIS_PULL_REQUEST = $TRAVIS_PULL_REQUEST" 5 | 6 | if [ "$TRAVIS_PULL_REQUEST" != "false" ] 7 | then 8 | ./Pods/SwiftLint/swiftlint --reporter json > swiftlint-report.json || false 9 | bundle exec linterbot $TRAVIS_REPO_SLUG $TRAVIS_PULL_REQUEST < swiftlint-report.json 10 | fi -------------------------------------------------------------------------------- /CleanArchitecture/Common/Apple/UITableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UITableViewCell.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 2/1/19. 6 | // Copyright © 2019 mlsuho. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UITableViewCell { 12 | static var identify: String { 13 | return String(describing: self) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /CleanArchitecture/Services/Realm/Core/ModelConvertibleType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ModelConvertibleType.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 12/19/18. 6 | // Copyright © 2018 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol ModelConvertibleType { 12 | associatedtype ModelType 13 | func asModel() -> ModelType 14 | } 15 | -------------------------------------------------------------------------------- /CleanArchitectureTests/CleanArchitectureTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CleanArchitectureTests.swift 3 | // CleanArchitectureTests 4 | // 5 | // Created by Su Ho V. on 12/16/18. 6 | // Copyright © 2018 mlsuho. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import CleanArchitecture 11 | 12 | final class CleanArchitectureTests: XCTestCase { 13 | 14 | func testExample() {} 15 | } 16 | -------------------------------------------------------------------------------- /CleanArchitecture/Services/Network/Core/HTTPTask.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HTTPTask.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 12/16/18. 6 | // Copyright © 2018 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Alamofire 11 | 12 | enum HTTPTask { 13 | case requestPlain 14 | case requestParameters(params: Parameters, encoding: ParameterEncoding) 15 | } 16 | -------------------------------------------------------------------------------- /CleanArchitecture/Application/View-ViewModel/Scene/000 - TabBar/TabBarViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TabBarViewModel.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 1/16/19. 6 | // Copyright © 2019 mlsuho. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class TabBarViewModel: ViewModel { 12 | 13 | var coordinator: TabBarCoordinator? 14 | 15 | func transform(input: Void) {} 16 | } 17 | -------------------------------------------------------------------------------- /CleanArchitecture/Define/App.Image.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Image.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 1/16/19. 6 | // Copyright © 2019 mlsuho. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension App { 12 | enum Image { 13 | static let today: UIImage? = UIImage(named: "img-today") 14 | static let settings: UIImage? = UIImage(named: "img-settings") 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /CleanArchitecture/Define/App.Key.swift: -------------------------------------------------------------------------------- 1 | // 2 | // App.Key.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 2/12/19. 6 | // Copyright © 2019 mlsuho. All rights reserved. 7 | // 8 | 9 | extension App { 10 | enum Key {} 11 | } 12 | 13 | // MARK: - Github 14 | extension App.Key { 15 | enum Github { 16 | static let clientIdKey = "client_id" 17 | static let clientIdValue = "49730bf0294929517d82" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode10.1 3 | cache: 4 | bundler: true 5 | cocoapods: true 6 | directories: 7 | - ~/.cocoapods 8 | - .bundle 9 | - vendor 10 | install: 11 | - set -o pipefail 12 | - sudo systemsetup -settimezone Asia/Ho_Chi_Minh 13 | - bundle install --path=vendor/bundle --jobs 4 --retry 3 14 | script: 15 | - bundle exec pod install --repo-update 16 | - ./scripts/lint 17 | - bundle exec fastlane test 18 | -------------------------------------------------------------------------------- /CleanArchitecture/Services/Realm/Core/RealmRepresentable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RealmRepresentable.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 12/19/18. 6 | // Copyright © 2018 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol RealmRepresentable { 12 | associatedtype RealmType: ModelConvertibleType 13 | associatedtype KeyType 14 | var uid: KeyType { get } 15 | 16 | func asRealm() -> RealmType 17 | } 18 | -------------------------------------------------------------------------------- /CleanArchitecture/Services/Realm/Core/Object.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Object.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 12/19/18. 6 | // Copyright © 2018 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Realm 11 | import RealmSwift 12 | 13 | extension Object { 14 | static func build(_ builder: (O) -> Void) -> O { 15 | let object = O() 16 | builder(object) 17 | return object 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /CleanArchitecture/Common/Apple/JSONDecoder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSONDecoder.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 12/17/18. 6 | // Copyright © 2018 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension JSONDecoder { 12 | static let shared: JSONDecoder = { 13 | let decoder = JSONDecoder() 14 | decoder.dateDecodingStrategy = .formatted(DateFormatter.iso8601) 15 | return decoder 16 | }() 17 | } 18 | -------------------------------------------------------------------------------- /CleanArchitecture/Application/Base/ViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewModel.swift 3 | // Common 4 | // 5 | // Created by Su Ho V. on 12/9/18. 6 | // Copyright © 2018 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol ViewModel { 12 | associatedtype Input 13 | associatedtype Output 14 | associatedtype CoordinatorType: Coordinate 15 | 16 | var coordinator: CoordinatorType? { get set } 17 | 18 | func transform(input: Input) -> Output 19 | } 20 | -------------------------------------------------------------------------------- /CleanArchitecture/Services/Network/Core/TargetType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TargetType.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 12/17/18. 6 | // Copyright © 2018 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Alamofire 11 | 12 | protocol TargetType { 13 | var baseURL: String { get } 14 | var path: String { get } 15 | var method: HTTPMethod { get } 16 | var task: HTTPTask { get } 17 | var headers: HTTPHeaders? { get } 18 | } 19 | -------------------------------------------------------------------------------- /fastlane/Scanfile: -------------------------------------------------------------------------------- 1 | # For more information about this configuration visit 2 | # https://docs.fastlane.tools/actions/scan/#scanfile 3 | 4 | # In general, you can use the options available 5 | # fastlane scan --help 6 | 7 | # Remove the # in front of the line to enable the option 8 | 9 | scheme("CleanArchitecture") 10 | device("iPhone 6") 11 | 12 | # open_report(true) 13 | 14 | clean(true) 15 | 16 | # Enable skip_build to skip debug builds for faster test performance 17 | skip_build(true) 18 | -------------------------------------------------------------------------------- /CleanArchitecture/Application/Base/TableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewCell.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 1/17/19. 6 | // Copyright © 2019 mlsuho. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxSwift 11 | 12 | class TableViewCell: UITableViewCell { 13 | 14 | let bag: DisposeBag = DisposeBag() 15 | 16 | override func awakeFromNib() { 17 | super.awakeFromNib() 18 | selectionStyle = .none 19 | setupUI() 20 | } 21 | 22 | func setupUI() {} 23 | } 24 | -------------------------------------------------------------------------------- /CleanArchitecture/Resources/Images/Assets.xcassets/img-checked.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "checked@1x.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "checked@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "checked@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /CleanArchitecture/Resources/Images/Assets.xcassets/img-settings.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "settings@1x.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "settings@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "settings@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /CleanArchitecture/Resources/Images/Assets.xcassets/img-today.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "img-today@1x.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "img-today@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "img-today@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /CleanArchitecture/Application/Model/UseCase/TaskUseCase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TaskUseCase.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 1/15/19. 6 | // Copyright © 2019 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | 12 | protocol TaskUseCase { 13 | func add(_ task: Task) -> Observable 14 | func update(_ task: Task) -> Observable 15 | func today() -> Observable<[Task]> 16 | func find(by id: String) -> Observable 17 | func delete(_ task: Task) -> Observable 18 | } 19 | -------------------------------------------------------------------------------- /CleanArchitecture/Resources/Images/Assets.xcassets/img-splash.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "img-splash@1x.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "img-splash@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "img-splash@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /CleanArchitecture/Application/Application.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Application.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 12/16/18. 6 | // Copyright © 2018 mlsuho. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class Application { 12 | static let current = Application() 13 | var coodinator: RootCoordinator? 14 | 15 | private init() {} 16 | 17 | func root(in window: UIWindow?) { 18 | let coodinator = RootCoordinator() 19 | coodinator.window = window 20 | coodinator.showScreen(.tabBar) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /CleanArchitecture/Common/Reactive/Driver.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Driver.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 12/19/18. 6 | // Copyright © 2018 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | import RxCocoa 12 | 13 | extension ObservableType { 14 | func emptyDriverIfError() -> Driver { 15 | return asDriver { _ in return Driver.empty() } 16 | } 17 | 18 | func emptyObservableIfError() -> Observable { 19 | return catchError { _ in return Observable.empty() } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /CleanArchitecture/Common/Reactive/RxExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RxExtension.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 4/5/19. 6 | // Copyright © 2019 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | import RxCocoa 12 | 13 | extension SharedSequence { 14 | public func unwrap() -> SharedSequence where E == T? { 15 | return self.filter { $0 != nil }.map({ (value) -> T in 16 | guard let value = value else { fatalError() } 17 | return value 18 | }) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /CleanArchitecture/Services/Network/Modules/Info/InfoNetwork.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InfoNetwork.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 4/6/19. 6 | // Copyright © 2019 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | 12 | final class InfoNetwork: InfoUseCase { 13 | 14 | private let router: Router 15 | 16 | init(router: Router) { 17 | self.router = router 18 | } 19 | 20 | func about() -> Observable { 21 | return router.request(.about).map(Info.self) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /CleanArchitecture/Define/App.String.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 12/16/18. 6 | // Copyright © 2018 mlsuho. All rights reserved. 7 | // 8 | 9 | extension App { 10 | enum String { 11 | static let appName = "CleanArchitecture" 12 | static let today = "Today" 13 | static let settings = "Settings" 14 | static let ok = "OK" 15 | static let name = "Name" 16 | static let author = "Author" 17 | static let repo = "Repository" 18 | static let year = "Year" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /CleanArchitecture/Application/View-ViewModel/Scene/002 - Settings/SettingsCoordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SetttingsCoordinator.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 1/16/19. 6 | // Copyright © 2019 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | final class SettingsCoordinator: Coordinate { 12 | var viewController: SettingsViewController? 13 | 14 | func showScreen(_ screen: SettingsCoordinator.Screen) { 15 | switch screen {} 16 | } 17 | } 18 | 19 | // MARK: - Screen 20 | extension SettingsCoordinator { 21 | enum Screen {} 22 | } 23 | -------------------------------------------------------------------------------- /CleanArchitecture/Common/Apple/Sequence.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Sequence.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 12/17/18. 6 | // Copyright © 2018 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - Array 12 | extension Array { 13 | var isNotEmpty: Bool { 14 | return !isEmpty 15 | } 16 | } 17 | 18 | // MARK: - ArraySlice 19 | extension ArraySlice { 20 | var isNotEmpty: Bool { 21 | return !isEmpty 22 | } 23 | } 24 | 25 | // MARK: - String 26 | extension String { 27 | var isNotEmpty: Bool { 28 | return !isEmpty 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | # This file contains the fastlane.tools configuration 2 | # You can find the documentation at https://docs.fastlane.tools 3 | # 4 | # For a list of all available actions, check out 5 | # 6 | # https://docs.fastlane.tools/actions 7 | # 8 | # For a list of all available plugins, check out 9 | # 10 | # https://docs.fastlane.tools/plugins/available-plugins 11 | # 12 | 13 | # Uncomment the line if you want fastlane to automatically update itself 14 | # update_fastlane 15 | 16 | default_platform(:ios) 17 | 18 | platform :ios do 19 | desc "Run Tests" 20 | lane :test do 21 | scan 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /CleanArchitecture/Application/View-ViewModel/Control/Cell/TaskCell/TaskCellViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TaskViewModel.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 1/17/19. 6 | // Copyright © 2019 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | final class TaskCellViewModel { 12 | let name: String 13 | let time: String 14 | let isFinish: Bool 15 | let taskId: String 16 | 17 | init(with task: Task) { 18 | name = task.name 19 | time = DateFormatter.hour.string(from: task.startAt) 20 | isFinish = task.isFinish 21 | taskId = task.id 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /CleanArchitecture/Application/View-ViewModel/Scene/000 - TabBar/TabBarController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TabBarController.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 1/15/19. 6 | // Copyright © 2019 mlsuho. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class TabBarController: UITabBarController, View { 12 | 13 | var viewModel: TabBarViewModel! 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | setupUI() 18 | } 19 | 20 | func setupUI() { 21 | tabBar.barStyle = .black 22 | tabBar.tintColor = App.Theme.current.package.tabbarTint 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /CleanArchitecture/Common/Apple/DateFormatter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DateFormatter.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 12/17/18. 6 | // Copyright © 2018 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - Mapping For Date 12 | extension DateFormatter { 13 | static let iso8601: DateFormatter = { 14 | let formatter = DateFormatter() 15 | formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'" 16 | return formatter 17 | }() 18 | 19 | static let hour: DateFormatter = { 20 | let formatter = DateFormatter() 21 | formatter.dateFormat = "h:mm a" 22 | return formatter 23 | }() 24 | } 25 | -------------------------------------------------------------------------------- /CleanArchitecture/Application/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 12/16/18. 6 | // Copyright © 2018 mlsuho. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxSwift 11 | 12 | @UIApplicationMain 13 | final class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | var window: UIWindow? 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | window = UIWindow(frame: UIScreen.main.bounds) 19 | Application.current.root(in: window) 20 | window?.makeKeyAndVisible() 21 | return true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /CleanArchitecture/Application/View-ViewModel/Scene/003 - AddTask/AddTaskCoordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AddTaskCoordinator.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 4/4/19. 6 | // Copyright © 2019 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | final class AddTaskCoordinator: Coordinate { 13 | 14 | var viewController: AddTaskViewController? 15 | 16 | func showScreen(_ screen: AddTaskCoordinator.Screen) { 17 | switch screen { 18 | case .dismiss: 19 | viewController?.dismiss(animated: true, completion: nil) 20 | } 21 | } 22 | } 23 | 24 | // MARK: - Screen 25 | extension AddTaskCoordinator { 26 | enum Screen { 27 | case dismiss 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /CleanArchitecture/Application/Model/Entity/Info.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Info.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 4/6/19. 6 | // Copyright © 2019 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | final class Info { 12 | let name: String 13 | let author: String 14 | let repo: String 15 | let year: Int 16 | 17 | init(name: String, author: String, repo: String, year: Int) { 18 | self.name = name 19 | self.author = author 20 | self.repo = repo 21 | self.year = year 22 | } 23 | } 24 | 25 | // MARK: - Codable 26 | extension Info: Codable { 27 | private enum CodingKeys: String, CodingKey { 28 | case name 29 | case author 30 | case repo 31 | case year 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /fastlane/README.md: -------------------------------------------------------------------------------- 1 | fastlane documentation 2 | ================ 3 | # Installation 4 | 5 | Make sure you have the latest version of the Xcode command line tools installed: 6 | 7 | ``` 8 | xcode-select --install 9 | ``` 10 | 11 | Install _fastlane_ using 12 | ``` 13 | [sudo] gem install fastlane -NV 14 | ``` 15 | or alternatively using `brew cask install fastlane` 16 | 17 | # Available Actions 18 | ## iOS 19 | ### ios test 20 | ``` 21 | fastlane ios test 22 | ``` 23 | Run Tests 24 | 25 | ---- 26 | 27 | This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. 28 | More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). 29 | The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). 30 | -------------------------------------------------------------------------------- /CleanArchitectureTests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /CleanArchitecture/Application/Base/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 12/16/18. 6 | // Copyright © 2018 mlsuho. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxSwift 11 | 12 | class ViewController: UIViewController { 13 | 14 | let bag: DisposeBag = DisposeBag() 15 | var navi: NavigationController? { 16 | return navigationController as? NavigationController 17 | } 18 | 19 | override var preferredStatusBarStyle: UIStatusBarStyle { 20 | return .lightContent 21 | } 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | setupUI() 26 | bindViewModel() 27 | } 28 | 29 | func bindViewModel() {} 30 | 31 | func setupUI() { 32 | view.backgroundColor = App.Theme.current.package.viewBackground 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | platform :ios, '11.0' 3 | 4 | inhibit_all_warnings! 5 | 6 | def rx_swift 7 | pod 'RxSwift' 8 | end 9 | 10 | def rx_cocoa 11 | pod 'RxCocoa' 12 | end 13 | 14 | def network_pods 15 | pod 'RxAlamofire' 16 | end 17 | 18 | def database_pods 19 | pod 'RxRealm' 20 | pod 'RealmSwift' 21 | pod 'Realm' 22 | end 23 | 24 | def dev_pods 25 | pod 'SwiftLint' 26 | end 27 | 28 | def test_pods 29 | pod 'RxTest' 30 | pod 'RxBlocking' 31 | end 32 | 33 | def util_pods 34 | pod 'SVProgressHUD' 35 | end 36 | 37 | target 'CleanArchitecture' do 38 | use_frameworks! 39 | rx_swift 40 | rx_cocoa 41 | network_pods 42 | database_pods 43 | dev_pods 44 | util_pods 45 | 46 | target 'CleanArchitectureTests' do 47 | inherit! :search_paths 48 | rx_swift 49 | dev_pods 50 | test_pods 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /CleanArchitecture/Application/Base/Coordinate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Coordinate.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 12/19/18. 6 | // Copyright © 2018 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | protocol Coordinate { 13 | associatedtype Screen 14 | associatedtype View: UIViewController 15 | 16 | var viewController: View? { get set } 17 | 18 | func showScreen(_ screen: Screen) 19 | func showError(_ error: Error) 20 | } 21 | 22 | // MARK: - Coordinator 23 | extension Coordinate { 24 | func showError(_ error: Error) { 25 | let alert = UIAlertController(title: App.String.appName, message: error.localizedDescription, preferredStyle: .alert) 26 | let ok = UIAlertAction(title: App.String.ok, style: .cancel) 27 | alert.addAction(ok) 28 | viewController?.present(alert, animated: true, completion: nil) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /CleanArchitecture/Application/Model/Entity/Task.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Task.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 1/15/19. 6 | // Copyright © 2019 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | final class Task { 12 | let id: String 13 | var name: String 14 | var startAt: Date 15 | var createdAt: Date 16 | var updatedAt: Date 17 | var isFinish: Bool 18 | 19 | init(id: String = UUID().uuidString, 20 | name: String, startAt: Date, 21 | createdAt: Date = Date(), updatedAt: Date = Date(), 22 | isFinish: Bool = false) { 23 | self.id = id 24 | self.name = name 25 | self.startAt = startAt 26 | self.createdAt = createdAt 27 | self.updatedAt = updatedAt 28 | self.isFinish = isFinish 29 | } 30 | 31 | func updateStatus(_ status: Bool = true) { 32 | isFinish = status 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /CleanArchitecture/Common/Apple/Date.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Date.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 1/15/19. 6 | // Copyright © 2019 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - Calendar 12 | extension Calendar { 13 | init() { 14 | self.init(identifier: Calendar.Identifier.gregorian) 15 | } 16 | 17 | func dateComponents(from date: Date) -> DateComponents { 18 | return dateComponents([.year, .month, .day, .hour, .minute, .second], from: date) 19 | } 20 | } 21 | 22 | // MARK: - DateComponents 23 | extension DateComponents { 24 | mutating func begin() -> DateComponents { 25 | hour = 00 26 | minute = 00 27 | second = 00 28 | return self 29 | } 30 | 31 | mutating func end() -> DateComponents { 32 | hour = 23 33 | minute = 59 34 | second = 59 35 | return self 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /CleanArchitecture/Common/Reactive/RxError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RxError.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 12/19/18. 6 | // Copyright © 2018 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | import RxCocoa 12 | 13 | final class RxError: ObservableConvertibleType { 14 | private let _subject = PublishSubject() 15 | 16 | deinit { 17 | _subject.onCompleted() 18 | } 19 | 20 | func asObservable() -> Observable { 21 | return _subject.asObservable() 22 | } 23 | 24 | fileprivate func track(from source: O) -> Observable { 25 | return source.asObservable().do(onError: { error in 26 | self._subject.onNext(error) 27 | }) 28 | } 29 | } 30 | 31 | // MARK: - ObservableConvertibleType 32 | extension ObservableConvertibleType { 33 | func trackError(into error: RxError) -> Observable { 34 | return error.track(from: self) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /CleanArchitecture/Services/Network/Modules/Info/InfoTarget.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InfoTarget.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 4/6/19. 6 | // Copyright © 2019 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Alamofire 11 | 12 | enum InfoTarget { 13 | case about 14 | } 15 | 16 | // MARK: - TargetType 17 | extension InfoTarget: TargetType { 18 | var baseURL: String { 19 | return "https://raw.githubusercontent.com/suho/ios-clean-architecture/master/api" 20 | } 21 | 22 | var path: String { 23 | switch self { 24 | case .about: 25 | return "/about.json" 26 | } 27 | } 28 | 29 | var method: HTTPMethod { 30 | switch self { 31 | case .about: 32 | return .get 33 | } 34 | } 35 | 36 | var task: HTTPTask { 37 | switch self { 38 | case .about: 39 | return .requestPlain 40 | } 41 | } 42 | 43 | var headers: HTTPHeaders? { 44 | return nil 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Su Ho V. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /CleanArchitecture/Application/View-ViewModel/Control/Cell/DetailCell/DetailCellViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CellViewModel.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 4/7/19. 6 | // Copyright © 2019 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | final class DetailCellViewModel { 12 | let title: String 13 | let detail: String 14 | let kind: Kind 15 | 16 | init(kind: Kind, info: Info) { 17 | self.kind = kind 18 | switch kind { 19 | case .name: 20 | title = App.String.name 21 | detail = info.name 22 | case .author: 23 | title = App.String.author 24 | detail = info.author 25 | case .repo: 26 | title = App.String.repo 27 | detail = info.repo 28 | case .year: 29 | title = App.String.year 30 | detail = "\(info.year)" 31 | } 32 | } 33 | } 34 | 35 | // MARK: - Kind 36 | extension DetailCellViewModel { 37 | enum Kind: CaseIterable { 38 | case name 39 | case author 40 | case repo 41 | case year 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /CleanArchitecture/Common/Apple/UIColor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 1/15/19. 6 | // Copyright © 2019 mlsuho. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIColor { 12 | convenience init(hex: Int, transparency: CGFloat = 1) { 13 | var trans = transparency 14 | if trans < 0 { trans = 0 } 15 | if trans > 1 { trans = 1 } 16 | 17 | let red = (hex >> 16) & 0xff 18 | let green = (hex >> 8) & 0xff 19 | let blue = hex & 0xff 20 | self.init(red: red, green: green, blue: blue, transparency: trans) 21 | } 22 | 23 | convenience init(red: Int, green: Int, blue: Int, transparency: CGFloat = 1) { 24 | guard red >= 0 && red <= 255 else { fatalError() } 25 | guard green >= 0 && green <= 255 else { fatalError() } 26 | guard blue >= 0 && blue <= 255 else { fatalError() } 27 | 28 | var trans = transparency 29 | if trans < 0 { trans = 0 } 30 | if trans > 1 { trans = 1 } 31 | 32 | self.init(red: CGFloat(red) / 255.0, green: CGFloat(green) / 255.0, blue: CGFloat(blue) / 255.0, alpha: trans) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /CleanArchitecture/Services/Network/Core/Mapping.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Mapping.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 12/17/18. 6 | // Copyright © 2018 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | 12 | // MARK: - Mapping from data 13 | extension Observable where E == Data { 14 | func map(_ type: T.Type, atKeyPath keyPath: String? = nil) -> Observable where T: Decodable { 15 | if let keyPath = keyPath { 16 | return map { (data) -> T in 17 | guard let value = try? JSONSerialization.jsonObject(with: data, options: []), 18 | let json = value as? [String: Any] else { throw NetworkError.json } 19 | guard let dataJSON = json.value(of: keyPath) else { throw NetworkError.json } 20 | let dataFromJSON = try JSONSerialization.data(withJSONObject: dataJSON, options: []) 21 | return try JSONDecoder.shared.decode(T.self, from: dataFromJSON) 22 | } 23 | } else { 24 | return map { data -> T in 25 | return try JSONDecoder.shared.decode(T.self, from: data) 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /CleanArchitecture/Application/RootCoordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RootCoordinator.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 1/16/19. 6 | // Copyright © 2019 mlsuho. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class RootCoordinator: Coordinate { 12 | 13 | weak var viewController: UIViewController? 14 | weak var window: UIWindow? { 15 | didSet { 16 | viewController = window?.rootViewController 17 | } 18 | } 19 | 20 | func showScreen(_ screen: Screen) { 21 | switch screen { 22 | case .tabBar: 23 | // Init 24 | let tabbar = TabBarController() 25 | let viewModel = TabBarViewModel() 26 | let coordinator = TabBarCoordinator() 27 | 28 | // Reference 29 | tabbar.viewModel = viewModel 30 | coordinator.viewController = tabbar 31 | viewModel.coordinator = coordinator 32 | window?.rootViewController = tabbar 33 | 34 | // Config 35 | coordinator.setupTabbar() 36 | } 37 | } 38 | } 39 | 40 | // MARK: - Define Screens 41 | extension RootCoordinator { 42 | enum Screen { 43 | case tabBar 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /CleanArchitecture/Application/View-ViewModel/Control/Cell/DetailCell/DetailCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailCell.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 4/7/19. 6 | // Copyright © 2019 mlsuho. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class DetailCell: TableViewCell { 12 | @IBOutlet weak var titleLabel: UILabel! 13 | @IBOutlet weak var detailLabel: UILabel! 14 | var viewModel: DetailCellViewModel! { 15 | didSet { 16 | guard viewModel != nil else { return } 17 | self.bind(self.viewModel) 18 | } 19 | } 20 | 21 | override func setupUI() { 22 | super.setupUI() 23 | setupContent() 24 | setupTexts() 25 | } 26 | 27 | private func bind(_ viewModel: DetailCellViewModel) { 28 | titleLabel.text = viewModel.title 29 | detailLabel.text = viewModel.detail 30 | } 31 | } 32 | 33 | // MARK: - UI 34 | extension DetailCell { 35 | private func setupContent() { 36 | backgroundColor = App.Theme.current.package.detailCellBackground 37 | contentView.backgroundColor = App.Theme.current.package.detailCellBackground 38 | } 39 | 40 | private func setupTexts() { 41 | titleLabel.textColor = App.Theme.current.package.detailCellText 42 | detailLabel.textColor = App.Theme.current.package.detailCellText 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /CleanArchitecture/Application/View-ViewModel/Scene/001 - Today/TodayCoordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TodayCoordinator.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 1/16/19. 6 | // Copyright © 2019 mlsuho. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class TodayCoordinator: Coordinate { 12 | var viewController: TodayViewController? 13 | 14 | func showScreen(_ screen: TodayCoordinator.Screen) { 15 | switch screen { 16 | case .addTask: 17 | let controller = addTaskController() 18 | viewController?.present(controller, animated: true, completion: nil) 19 | case .detailTask: break 20 | } 21 | } 22 | 23 | private func addTaskController() -> UIViewController { 24 | let controller = AddTaskViewController() 25 | let repository = RealmRepository() 26 | let useCase = RealmTask(repository: repository) 27 | let coordinator = AddTaskCoordinator() 28 | coordinator.viewController = controller 29 | let viewModel = AddTaskViewModel(useCase: useCase, coordinator: coordinator) 30 | controller.viewModel = viewModel 31 | let navigationController = NavigationController(rootViewController: controller) 32 | return navigationController 33 | } 34 | } 35 | 36 | // MARK: - Screen 37 | extension TodayCoordinator { 38 | enum Screen { 39 | case addTask 40 | case detailTask 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /CleanArchitecture/Services/Realm/Entities/RTask.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RTask.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 1/15/19. 6 | // Copyright © 2019 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RealmSwift 11 | import Realm 12 | 13 | final class RTask: Object { 14 | @objc dynamic var id: String = "" 15 | @objc dynamic var name: String = "" 16 | @objc dynamic var startAt: Date = Date() 17 | @objc dynamic var createdAt: Date = Date() 18 | @objc dynamic var updatedAt: Date = Date() 19 | @objc dynamic var isFinish: Bool = false 20 | 21 | override class func primaryKey() -> String? { 22 | return "id" 23 | } 24 | } 25 | 26 | // MARK: - ModelConvertibleType 27 | extension RTask: ModelConvertibleType { 28 | func asModel() -> Task { 29 | return Task(id: id, name: name, startAt: startAt, 30 | createdAt: createdAt, updatedAt: updatedAt, 31 | isFinish: isFinish) 32 | } 33 | } 34 | 35 | // MARK: - RealmRepresentable 36 | extension Task: RealmRepresentable { 37 | var uid: String { 38 | return id 39 | } 40 | 41 | func asRealm() -> RTask { 42 | return RTask.build { 43 | $0.id = uid 44 | $0.name = name 45 | $0.startAt = startAt 46 | $0.createdAt = createdAt 47 | $0.updatedAt = updatedAt 48 | $0.isFinish = isFinish 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /CleanArchitecture/Common/Reactive/RxIndicator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RxIndicator.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 12/19/18. 6 | // Copyright © 2018 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | import RxCocoa 12 | import SVProgressHUD 13 | 14 | final class RxIndicator: ObservableConvertibleType { 15 | private let _relay = BehaviorRelay(value: false) 16 | private let _loading: Observable 17 | 18 | init() { 19 | _loading = _relay.distinctUntilChanged() 20 | } 21 | 22 | func asObservable() -> Observable { 23 | return _loading.asObservable() 24 | } 25 | 26 | fileprivate func track(_ source: O) -> Observable { 27 | return source.asObservable() 28 | .do(onNext: { _ in 29 | self.dismiss() 30 | }, onError: { _ in 31 | self.dismiss() 32 | }, onCompleted: { 33 | self.dismiss() 34 | }, onSubscribe: show) 35 | } 36 | 37 | private func show() { 38 | _relay.accept(true) 39 | SVProgressHUD.show() 40 | } 41 | 42 | private func dismiss() { 43 | _relay.accept(false) 44 | SVProgressHUD.popActivity() 45 | } 46 | } 47 | 48 | // MARK: - ObservableConvertibleType 49 | extension ObservableConvertibleType { 50 | func indicate(_ indicator: RxIndicator) -> Observable { 51 | return indicator.track(self) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /CleanArchitecture/Services/Network/Core/Router.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Network.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 12/16/18. 6 | // Copyright © 2018 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxAlamofire 11 | import Alamofire 12 | import RxSwift 13 | 14 | protocol NetworkRouter: class { 15 | associatedtype Target: TargetType 16 | func request(_ target: Target) -> Observable 17 | } 18 | 19 | final class Router: NetworkRouter { 20 | 21 | private let scheduler: ConcurrentDispatchQueueScheduler 22 | 23 | init() { 24 | let qos = DispatchQoS(qosClass: DispatchQoS.QoSClass.background, 25 | relativePriority: 1) 26 | self.scheduler = ConcurrentDispatchQueueScheduler(qos: qos) 27 | } 28 | 29 | func request(_ target: Target) -> Observable { 30 | guard let manager = NetworkReachabilityManager(), manager.isReachable else { 31 | return Observable.error(NetworkError.offline) 32 | } 33 | let url = target.baseURL + target.path 34 | switch target.task { 35 | case .requestPlain: 36 | return RxAlamofire 37 | .request(target.method, url, headers: target.headers) 38 | .observeOn(scheduler) 39 | .data() 40 | case .requestParameters(let parameters, let encoding): 41 | return RxAlamofire 42 | .request(target.method, url, parameters: parameters, encoding: encoding, headers: target.headers) 43 | .observeOn(scheduler) 44 | .data() 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /CleanArchitecture/Application/View-ViewModel/Scene/002 - Settings/SettingsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsViewController.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 1/16/19. 6 | // Copyright © 2019 mlsuho. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class SettingsViewController: ViewController, View { 12 | 13 | @IBOutlet weak var tableView: UITableView! 14 | var viewModel: SettingsViewModel! 15 | 16 | override func setupUI() { 17 | super.setupUI() 18 | setupNavigation() 19 | setupTableView() 20 | } 21 | 22 | override func bindViewModel() { 23 | super.bindViewModel() 24 | let viewWillAppear = rx 25 | .sentMessage(#selector(UIViewController.viewWillAppear(_:))) 26 | .map { _ in return }.emptyDriverIfError() 27 | let input = SettingsViewModel.Input(loadTrigger: viewWillAppear) 28 | let output = viewModel.transform(input: input) 29 | output.viewModels.drive(tableView.rx.items(cellIdentifier: DetailCell.identify, cellType: DetailCell.self)) { (_, viewModel, cell) in 30 | cell.viewModel = viewModel 31 | }.disposed(by: bag) 32 | output.indicator.drive().disposed(by: bag) 33 | output.error.drive().disposed(by: bag) 34 | } 35 | } 36 | 37 | // MARK: - UI 38 | extension SettingsViewController { 39 | private func setupNavigation() { 40 | title = App.String.settings 41 | } 42 | 43 | private func setupTableView() { 44 | tableView.backgroundColor = App.Theme.current.package.viewBackground 45 | tableView.register(UINib(nibName: DetailCell.identify, bundle: nil), forCellReuseIdentifier: DetailCell.identify) 46 | tableView.tableFooterView = UIView() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /CleanArchitecture/Application/View-ViewModel/Scene/003 - AddTask/AddTaskViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AddTaskViewModel.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 4/4/19. 6 | // Copyright © 2019 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | import RxCocoa 12 | 13 | final class AddTaskViewModel: ViewModel { 14 | struct Input { 15 | let cancelTrigger: Driver 16 | let saveTrigger: Driver 17 | let time: Driver 18 | let name: Driver 19 | } 20 | 21 | struct Output { 22 | let cancel: Driver 23 | let save: Driver 24 | } 25 | 26 | var coordinator: AddTaskCoordinator? 27 | 28 | private let useCase: TaskUseCase 29 | 30 | init(useCase: TaskUseCase, coordinator: AddTaskCoordinator) { 31 | self.useCase = useCase 32 | self.coordinator = coordinator 33 | } 34 | 35 | func transform(input: Input) -> Output { 36 | let cancel = input.cancelTrigger.do(onNext: { _ in 37 | self.coordinator?.showScreen(.dismiss) 38 | }) 39 | let save: Driver = input 40 | .saveTrigger 41 | .debounce(0.3) 42 | .flatMapLatest { _ in 43 | return Driver.combineLatest(input.time, input.name) 44 | } 45 | .map { (date, name) -> Task in 46 | return Task(name: name, startAt: date) 47 | } 48 | .flatMapLatest { (task) in 49 | return self.useCase.add(task).emptyDriverIfError() 50 | } 51 | .do(onNext: { _ in 52 | self.coordinator?.showScreen(.dismiss) 53 | }) 54 | return Output(cancel: cancel, save: save) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /CleanArchitecture/Application/Base/NavigationController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NavigationController.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 1/16/19. 6 | // Copyright © 2019 mlsuho. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class NavigationController: UINavigationController { 12 | weak var progress: UIProgressView! 13 | var isProgressHidden: Bool = true { 14 | didSet { 15 | setupProgress() 16 | } 17 | } 18 | 19 | override func viewDidLoad() { 20 | super.viewDidLoad() 21 | setupUI() 22 | } 23 | 24 | func setupUI() { 25 | navigationBar.prefersLargeTitles = true 26 | navigationBar.barStyle = .black 27 | navigationBar.tintColor = App.Theme.current.package.tabbarTint 28 | } 29 | 30 | private func setupProgress() { 31 | if isProgressHidden { 32 | if progress != nil { 33 | progress.removeFromSuperview() 34 | updateViewConstraints() 35 | } 36 | } else { 37 | let progress = UIProgressView(progressViewStyle: .bar) 38 | progress.translatesAutoresizingMaskIntoConstraints = false 39 | progress.progress = 0 40 | progress.backgroundColor = .black 41 | navigationBar.addSubview(progress) 42 | NSLayoutConstraint.activate([ 43 | progress.heightAnchor.constraint(equalToConstant: 5), 44 | progress.leftAnchor.constraint(equalTo: navigationBar.leftAnchor), 45 | progress.rightAnchor.constraint(equalTo: navigationBar.rightAnchor), 46 | progress.topAnchor.constraint(equalTo: navigationBar.bottomAnchor) 47 | ]) 48 | self.progress = progress 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /CleanArchitecture/Application/View-ViewModel/Scene/002 - Settings/SettingsViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsViewModel.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 1/16/19. 6 | // Copyright © 2019 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | import RxCocoa 12 | 13 | final class SettingsViewModel: ViewModel { 14 | 15 | var coordinator: SettingsCoordinator? 16 | private let useCase: InfoUseCase 17 | 18 | init(useCase: InfoUseCase, coordinator: SettingsCoordinator) { 19 | self.useCase = useCase 20 | self.coordinator = coordinator 21 | } 22 | 23 | func transform(input: Input) -> Output { 24 | let indicator: RxIndicator = RxIndicator() 25 | let rxError: RxError = RxError() 26 | let viewModels: Driver<[DetailCellViewModel]> = input.loadTrigger 27 | .flatMapLatest { _ -> Driver in 28 | return self.useCase.about().indicate(indicator).trackError(into: rxError).emptyDriverIfError() 29 | } 30 | .map { info -> [DetailCellViewModel] in 31 | return DetailCellViewModel.Kind.allCases.map { DetailCellViewModel(kind: $0, info: info) } 32 | } 33 | let error = rxError.asObservable().do(onNext: { (error) in 34 | self.coordinator?.showError(error) 35 | }).emptyDriverIfError() 36 | let output = Output(viewModels: viewModels, indicator: indicator.asObservable().emptyDriverIfError(), error: error) 37 | return output 38 | } 39 | } 40 | 41 | // MARK: - Define 42 | extension SettingsViewModel { 43 | struct Input { 44 | let loadTrigger: Driver 45 | } 46 | 47 | struct Output { 48 | let viewModels: Driver<[DetailCellViewModel]> 49 | let indicator: Driver 50 | let error: Driver 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | Packages/ 39 | Package.pins 40 | Package.resolved 41 | .build/ 42 | 43 | # CocoaPods 44 | # 45 | # We recommend against adding the Pods directory to your .gitignore. However 46 | # you should judge for yourself, the pros and cons are mentioned at: 47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 48 | # 49 | Pods/ 50 | 51 | # Carthage 52 | # 53 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 54 | Carthage/Checkouts 55 | 56 | Carthage/Build 57 | 58 | # fastlane 59 | # 60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 61 | # screenshots whenever they are needed. 62 | # For more information about the recommended setup visit: 63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 64 | 65 | fastlane/report.xml 66 | fastlane/Preview.html 67 | fastlane/screenshots/**/*.png 68 | fastlane/test_output 69 | 70 | # Visual Studio Code 71 | .vscode 72 | 73 | # Bundler 74 | vendor 75 | 76 | # Swiftlint 77 | swiftlint-report.json -------------------------------------------------------------------------------- /CleanArchitecture/Services/Realm/Modules/RealmTask.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RealmTask.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 1/15/19. 6 | // Copyright © 2019 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RealmSwift 11 | import RxRealm 12 | import RxSwift 13 | 14 | final class RealmTask: TaskUseCase where R.Entity == Task { 15 | private let repository: R 16 | 17 | init(repository: R) { 18 | self.repository = repository 19 | } 20 | 21 | func add(_ task: Task) -> Observable { 22 | return repository.save(task) 23 | } 24 | 25 | func update(_ task: Task) -> Observable { 26 | return repository.save(task) 27 | } 28 | 29 | func today() -> Observable<[Task]> { 30 | let calendar = Calendar() 31 | let today = Date() 32 | var components = calendar.dateComponents(from: today) 33 | guard let startDate = calendar.date(from: components.begin()), 34 | let endDate = calendar.date(from: components.end()) else { 35 | return Observable.error(RealmError.system) 36 | } 37 | let predicate = NSPredicate(format: "startAt >= %@ AND startAt =< %@", argumentArray: [startDate, endDate]) 38 | let sortDescriptors: [NSSortDescriptor] = [NSSortDescriptor(key: "isFinish", ascending: true), 39 | NSSortDescriptor(key: "startAt", ascending: true)] 40 | return repository.find(with: predicate, sortDescriptors: sortDescriptors) 41 | } 42 | 43 | func find(by id: String) -> Observable { 44 | let predicate = NSPredicate(format: "id = %@", argumentArray: [id]) 45 | return repository.find(with: predicate, sortDescriptors: []).map({ tasks -> Task? in 46 | return tasks.first 47 | }) 48 | } 49 | 50 | func delete(_ task: Task) -> Observable { 51 | return repository.delete(task) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /CleanArchitecture/Support/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleURLTypes 6 | 7 | 8 | CFBundleURLSchemes 9 | 10 | iOSCleanArchitecture 11 | 12 | CFBundleURLName 13 | me.mlsuho.CleanArchitecture 14 | 15 | 16 | CFBundleDevelopmentRegion 17 | $(DEVELOPMENT_LANGUAGE) 18 | CFBundleExecutable 19 | $(EXECUTABLE_NAME) 20 | CFBundleIdentifier 21 | $(PRODUCT_BUNDLE_IDENTIFIER) 22 | CFBundleInfoDictionaryVersion 23 | 6.0 24 | CFBundleName 25 | $(PRODUCT_NAME) 26 | CFBundlePackageType 27 | APPL 28 | CFBundleShortVersionString 29 | 1.0 30 | CFBundleVersion 31 | 1 32 | LSRequiresIPhoneOS 33 | 34 | NSAppTransportSecurity 35 | 36 | NSAllowsLocalNetworking 37 | 38 | 39 | UILaunchStoryboardName 40 | LaunchScreen 41 | UIRequiredDeviceCapabilities 42 | 43 | armv7 44 | 45 | UIStatusBarHidden 46 | 47 | UIStatusBarStyle 48 | UIStatusBarStyleLightContent 49 | UISupportedInterfaceOrientations 50 | 51 | UIInterfaceOrientationPortrait 52 | 53 | UISupportedInterfaceOrientations~ipad 54 | 55 | UIInterfaceOrientationPortrait 56 | UIInterfaceOrientationPortraitUpsideDown 57 | UIInterfaceOrientationLandscapeLeft 58 | UIInterfaceOrientationLandscapeRight 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /CleanArchitecture/Resources/Images/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 | } -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (4.8.0) 3 | - Realm (3.12.0): 4 | - Realm/Headers (= 3.12.0) 5 | - Realm/Headers (3.12.0) 6 | - RealmSwift (3.12.0): 7 | - Realm (= 3.12.0) 8 | - RxAlamofire (4.3.0): 9 | - RxAlamofire/Core (= 4.3.0) 10 | - RxAlamofire/Core (4.3.0): 11 | - Alamofire (~> 4.5) 12 | - RxSwift (~> 4) 13 | - RxAtomic (4.4.0) 14 | - RxBlocking (4.4.0): 15 | - RxAtomic (~> 4.4) 16 | - RxSwift (~> 4.0) 17 | - RxCocoa (4.4.0): 18 | - RxSwift (~> 4.0) 19 | - RxRealm (0.7.6): 20 | - RealmSwift (~> 3.0) 21 | - RxSwift (~> 4.0) 22 | - RxSwift (4.4.0): 23 | - RxAtomic (~> 4.4) 24 | - RxTest (4.4.0): 25 | - RxAtomic (~> 4.4) 26 | - RxSwift (~> 4.0) 27 | - SVProgressHUD (2.2.5) 28 | - SwiftLint (0.29.1) 29 | 30 | DEPENDENCIES: 31 | - Realm 32 | - RealmSwift 33 | - RxAlamofire 34 | - RxBlocking 35 | - RxCocoa 36 | - RxRealm 37 | - RxSwift 38 | - RxTest 39 | - SVProgressHUD 40 | - SwiftLint 41 | 42 | SPEC REPOS: 43 | https://github.com/cocoapods/specs.git: 44 | - Alamofire 45 | - Realm 46 | - RealmSwift 47 | - RxAlamofire 48 | - RxAtomic 49 | - RxBlocking 50 | - RxCocoa 51 | - RxRealm 52 | - RxSwift 53 | - RxTest 54 | - SVProgressHUD 55 | - SwiftLint 56 | 57 | SPEC CHECKSUMS: 58 | Alamofire: 3ec537f71edc9804815215393ae2b1a8ea33a844 59 | Realm: cdaef23c4ddb36ab1ddffed23f5a7f3332fc5585 60 | RealmSwift: 5576324033f0aa5ef1e0a839a3da2281dff47a7f 61 | RxAlamofire: 09624d0f2d48ed8b686e4eb4cf68e28cbd2df556 62 | RxAtomic: eacf60db868c96bfd63320e28619fe29c179656f 63 | RxBlocking: 138ad53217434444d6eeeb4fb406a45431d92e31 64 | RxCocoa: df63ebf7b9a70d6b4eeea407ed5dd4efc8979749 65 | RxRealm: 5379eddd74f8d617ca7681d1f8d144af25b432b0 66 | RxSwift: 5976ecd04fc2fefd648827c23de5e11157faa973 67 | RxTest: 19d03286bdc0a3aaea5d61d4cde31fdf4bb8a5ba 68 | SVProgressHUD: 1428aafac632c1f86f62aa4243ec12008d7a51d6 69 | SwiftLint: 6772320e40b52049053a518c17db9b0634a0b45a 70 | 71 | PODFILE CHECKSUM: 4885f9804b170535dc343ef349005a2687eeed1b 72 | 73 | COCOAPODS: 1.5.3 74 | -------------------------------------------------------------------------------- /CleanArchitecture/Services/Realm/Core/Reactive.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Realm+Rx.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 12/19/18. 6 | // Copyright © 2018 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Realm 11 | import RealmSwift 12 | import RxSwift 13 | 14 | extension Reactive where Base: Realm { 15 | func save(_ objects: S, update: Bool = true) -> Observable where S.Element: RealmRepresentable, 16 | S.Element.RealmType: Object { 17 | return Observable.create { observer in 18 | do { 19 | try self.base.write { 20 | self.base.add(objects.map { $0.asRealm() }, update: update) 21 | } 22 | observer.onNext(objects) 23 | observer.onCompleted() 24 | } catch { 25 | observer.onError(error) 26 | } 27 | return Disposables.create() 28 | } 29 | } 30 | 31 | func save(_ object: R, update: Bool = true) -> Observable where R.RealmType: Object { 32 | return Observable.create { observer in 33 | do { 34 | try self.base.write { 35 | self.base.add(object.asRealm(), update: update) 36 | } 37 | observer.onNext(object) 38 | observer.onCompleted() 39 | } catch { 40 | observer.onError(error) 41 | } 42 | return Disposables.create() 43 | } 44 | } 45 | 46 | func delete(_ object: R) -> Observable where R.RealmType: Object { 47 | return Observable.create { observer in 48 | do { 49 | guard let object = self.base.object(ofType: R.RealmType.self, forPrimaryKey: object.uid) else { fatalError() } 50 | try self.base.write { 51 | self.base.delete(object) 52 | } 53 | observer.onNext(()) 54 | observer.onCompleted() 55 | } catch { 56 | observer.onError(error) 57 | } 58 | return Disposables.create() 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /CleanArchitecture/Common/Apple/Dictionary.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dictionary.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 12/17/18. 6 | // Copyright © 2018 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Dictionary where Key == String, Value == Any { 12 | 13 | /// Get New Object with Keypath 14 | /// 15 | /// - Parameter key: Keypath for access to exactly object 16 | /// - Returns: New Object From Dictionary 17 | /// Key example: 18 | /// - "data" ~ Access to key "data" 19 | /// - "data.object" ~ Access to key "data", then key "object" 20 | /// - "data.3.id" ~ Access to key "data", then get object index 3 in Array data, then get id of that object 21 | func value(of key: String) -> Any? { 22 | let keyPaths = ArraySlice(key.components(separatedBy: ".")) 23 | return jsonObject(keyPaths, dictionary: self) 24 | } 25 | 26 | private func jsonObject(_ keyPaths: ArraySlice, dictionary: [String: Any]) -> Any? { 27 | guard keyPaths.isNotEmpty, 28 | let keyPath = keyPaths.first, 29 | let object = dictionary[keyPath] else { return nil} 30 | if let dict = object as? [String: Any], keyPaths.count > 1 { 31 | let tail = keyPaths.dropFirst() 32 | return jsonObject(tail, dictionary: dict) 33 | } else if let array = object as? [Any], keyPaths.count > 1 { 34 | let tail = keyPaths.dropFirst() 35 | return arrayObject(tail, array: array) 36 | } else { 37 | return object 38 | } 39 | } 40 | 41 | private func arrayObject(_ keyPaths: ArraySlice, array: [Any]) -> Any? { 42 | guard keyPaths.isNotEmpty, 43 | let keyPath = keyPaths.first, 44 | let index = Int(keyPath), 45 | index >= 0, 46 | index < array.count else { return nil } 47 | let object = array[index] 48 | if let dict = object as? [String: Any], keyPaths.count > 1 { 49 | let tail = keyPaths.dropFirst() 50 | return jsonObject(tail, dictionary: dict) 51 | } else if let array = object as? [Any], keyPaths.count > 1 { 52 | let tail = keyPaths.dropFirst() 53 | return arrayObject(tail, array: array) 54 | } else { 55 | return object 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /CleanArchitecture/Define/App.Theme.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Theme.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 12/18/18. 6 | // Copyright © 2018 mlsuho. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol ColorPackage { 12 | var tabbarBackground: UIColor { get } 13 | var tabbarText: UIColor { get } 14 | var tabbarTint: UIColor { get } 15 | var viewBackground: UIColor { get } 16 | var taskCellBackground: UIColor { get } 17 | var taskCellVerticalLine: UIColor { get } 18 | var taskCellActiveCircle: UIColor { get } 19 | var taskCellInactiveCircle: UIColor { get } 20 | var taskCellTitle: UIColor { get } 21 | var taskCellTime: UIColor { get } 22 | var taskCellDoneButton: UIColor { get } 23 | var taskCellTickImage: UIColor { get } 24 | var taskCellCoverView: UIColor { get } 25 | var detailCellBackground: UIColor { get } 26 | var detailCellText: UIColor { get } 27 | } 28 | 29 | // MARK: - App Theme 30 | extension App { 31 | enum Theme { 32 | case night 33 | case day 34 | 35 | static var current: Theme = .night 36 | 37 | var package: ColorPackage { 38 | switch self { 39 | case .day: 40 | return NightPackage() 41 | case .night: 42 | return NightPackage() 43 | } 44 | } 45 | } 46 | 47 | struct NightPackage: ColorPackage { 48 | var tabbarBackground: UIColor = UIColor(hex: 0x181818) 49 | var tabbarText: UIColor = UIColor(hex: 0x7f7f7f) 50 | var tabbarTint: UIColor = UIColor(hex: 0xff9500) 51 | var viewBackground: UIColor = UIColor(hex: 0x0d0d0d) 52 | var taskCellBackground: UIColor = UIColor(hex: 0x201f25) 53 | var taskCellActiveCircle: UIColor = UIColor(hex: 0xff9500) 54 | var taskCellInactiveCircle: UIColor = UIColor(hex: 0x34333b) 55 | var taskCellVerticalLine: UIColor = UIColor(hex: 0x34333b) 56 | var taskCellTitle: UIColor = UIColor(hex: 0xffffff) 57 | var taskCellTime: UIColor = UIColor(hex: 0x919093) 58 | var taskCellDoneButton: UIColor = UIColor(hex: 0x919093) 59 | var taskCellTickImage: UIColor = UIColor(hex: 0x201f25) 60 | var taskCellCoverView: UIColor = UIColor(hex: 0x000000, transparency: 0.6) 61 | var detailCellBackground: UIColor = UIColor(hex: 0x171717) 62 | var detailCellText: UIColor = UIColor(hex: 0xffffff) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /CleanArchitecture/Application/View-ViewModel/Scene/000 - TabBar/TabBarCoordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TabBarCoordinator.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 1/16/19. 6 | // Copyright © 2019 mlsuho. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class TabBarCoordinator: Coordinate { 12 | 13 | weak var viewController: TabBarController? 14 | 15 | func showScreen(_ screen: TabBarCoordinator.Screen) {} 16 | 17 | func setupTabbar() { 18 | let today = todayNavi() 19 | let settings = settingsNavi() 20 | 21 | viewController?.viewControllers = [ 22 | today, 23 | settings 24 | ] 25 | } 26 | 27 | private func todayNavi() -> UINavigationController { 28 | let controller = TodayViewController() 29 | let repository = RealmRepository() 30 | let useCase = RealmTask(repository: repository) 31 | let coordinator = TodayCoordinator() 32 | coordinator.viewController = controller 33 | let viewModel = TodayViewModel(useCase: useCase, coordinator: coordinator) 34 | controller.viewModel = viewModel 35 | let navigationController = NavigationController(rootViewController: controller) 36 | navigationController.tabBarItem = UITabBarItem(title: App.String.today, 37 | image: App.Image.today, 38 | tag: 0) 39 | return navigationController 40 | } 41 | 42 | private func settingsNavi() -> UINavigationController { 43 | let controller = SettingsViewController() 44 | let router = Router() 45 | let useCase = InfoNetwork(router: router) 46 | let coordinator = SettingsCoordinator() 47 | coordinator.viewController = controller 48 | let viewModel = SettingsViewModel(useCase: useCase, coordinator: coordinator) 49 | controller.viewModel = viewModel 50 | let navigationController = NavigationController(rootViewController: controller) 51 | navigationController.tabBarItem = UITabBarItem(title: App.String.settings, 52 | image: App.Image.settings, 53 | tag: 1) 54 | return navigationController 55 | } 56 | } 57 | 58 | // MARK: - Screen 59 | extension TabBarCoordinator { 60 | enum Screen { 61 | case today 62 | case settings 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /CleanArchitecture/Application/View-ViewModel/Scene/003 - AddTask/AddTaskViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AddTaskViewController.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 2/2/19. 6 | // Copyright © 2019 mlsuho. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxSwift 11 | import RxCocoa 12 | 13 | final class AddTaskViewController: ViewController { 14 | 15 | @IBOutlet weak var timePicker: UIDatePicker! 16 | @IBOutlet weak var nameTextField: UITextField! 17 | weak var cancelButton: UIBarButtonItem! 18 | weak var saveButton: UIBarButtonItem! 19 | 20 | var viewModel: AddTaskViewModel! 21 | 22 | override func setupUI() { 23 | super.setupUI() 24 | setupBackground() 25 | setupNavi() 26 | setupTimePicker() 27 | } 28 | 29 | override func bindViewModel() { 30 | super.bindViewModel() 31 | let save = saveButton.rx.tap 32 | .do(onNext: { _ in 33 | self.view.endEditing(true) 34 | }) 35 | .emptyDriverIfError() 36 | let cancel = cancelButton.rx.tap.asDriver() 37 | let time = timePicker.rx.date.asDriver() 38 | let name = nameTextField.rx.text.orEmpty.asDriver() 39 | let input = AddTaskViewModel.Input(cancelTrigger: cancel, saveTrigger: save, time: time, name: name) 40 | let output = viewModel.transform(input: input) 41 | output.cancel.drive().disposed(by: bag) 42 | output.save.drive().disposed(by: bag) 43 | nameTextField.rx.controlEvent(.editingDidEndOnExit) 44 | .do(onNext: { _ in 45 | self.view.endEditing(true) 46 | }) 47 | .emptyDriverIfError() 48 | .drive() 49 | .disposed(by: bag) 50 | } 51 | } 52 | 53 | // MARK: - UI 54 | extension AddTaskViewController { 55 | private func setupBackground() { 56 | view.backgroundColor = App.Theme.current.package.viewBackground 57 | } 58 | 59 | private func setupNavi() { 60 | title = "Add Task Today" 61 | navi?.isProgressHidden = true 62 | navi?.navigationBar.prefersLargeTitles = false 63 | let leftButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: nil) 64 | navigationItem.leftBarButtonItem = leftButton 65 | cancelButton = leftButton 66 | let rightButton = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: nil) 67 | navigationItem.rightBarButtonItem = rightButton 68 | saveButton = rightButton 69 | } 70 | 71 | private func setupTimePicker() { 72 | timePicker.minimumDate = Date() 73 | timePicker.setValue(UIColor.white, forKey: "textColor") 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /CleanArchitecture/Application/View-ViewModel/Scene/001 - Today/TodayViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TodayViewController.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 1/16/19. 6 | // Copyright © 2019 mlsuho. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxCocoa 11 | import RxSwift 12 | 13 | final class TodayViewController: ViewController, View { 14 | @IBOutlet weak var tableView: UITableView! 15 | weak var addButton: UIBarButtonItem! 16 | 17 | var viewModel: TodayViewModel! 18 | let updateTask: PublishSubject = PublishSubject() 19 | 20 | override func setupUI() { 21 | super.setupUI() 22 | setupNavigationBar() 23 | setupTableView() 24 | } 25 | 26 | override func bindViewModel() { 27 | super.bindViewModel() 28 | let viewWillAppear = rx 29 | .sentMessage(#selector(UIViewController.viewWillAppear(_:))) 30 | .map { _ in return }.emptyDriverIfError() 31 | let addTask = addButton.rx.tap.emptyDriverIfError() 32 | let updateTask = self.updateTask.asObserver().emptyDriverIfError() 33 | let deleteTrigger = tableView.rx.itemDeleted.asDriver() 34 | let input = TodayViewModel.Input(loadTrigger: viewWillAppear, addTrigger: addTask, updateTrigger: updateTask, deleteTrigger: deleteTrigger) 35 | let output = viewModel.transform(input: input) 36 | output.addTask.drive().disposed(by: bag) 37 | output.updateTask.drive().disposed(by: bag) 38 | if let navi = navi { 39 | output.progress.drive(navi.progress.rx.progress).disposed(by: bag) 40 | } 41 | output.taskViewModels.drive(tableView.rx.items(cellIdentifier: TaskCell.identify, cellType: TaskCell.self)) { (_, viewModel, cell) in 42 | cell.viewModel = viewModel 43 | cell.doneButton.rx.tap.asDriver().debounce(0.4) 44 | .drive(onNext: { _ in 45 | self.updateTask.onNext(viewModel) 46 | }) 47 | .disposed(by: cell.bag) 48 | } 49 | .disposed(by: bag) 50 | output.delete.drive().disposed(by: bag) 51 | } 52 | } 53 | 54 | // MARK: - Setup UI 55 | extension TodayViewController { 56 | private func setupNavigationBar() { 57 | title = App.String.today 58 | let button = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: nil) 59 | navigationItem.rightBarButtonItem = button 60 | addButton = button 61 | navi?.isProgressHidden = false 62 | } 63 | 64 | private func setupTableView() { 65 | tableView.backgroundColor = App.Theme.current.package.viewBackground 66 | tableView.register(UINib(nibName: TaskCell.identify, bundle: nil), forCellReuseIdentifier: TaskCell.identify) 67 | tableView.rowHeight = 80 68 | tableView.tableFooterView = UIView() 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /CleanArchitecture/Resources/LaunchScreen/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /CleanArchitecture/Application/View-ViewModel/Scene/001 - Today/TodayViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /CleanArchitecture/Application/View-ViewModel/Scene/002 - Settings/SettingsViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /CleanArchitecture/Application/View-ViewModel/Control/Cell/TaskCell/TaskCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TaskCell.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 1/17/19. 6 | // Copyright © 2019 mlsuho. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxSwift 11 | import RxCocoa 12 | 13 | final class TaskCell: TableViewCell { 14 | @IBOutlet weak var verticalLineView: UIView! 15 | @IBOutlet weak var circleView: SView! 16 | @IBOutlet weak var titleLabel: UILabel! 17 | @IBOutlet weak var timeLabel: UILabel! 18 | @IBOutlet weak var doneView: SView! 19 | @IBOutlet weak var checkedImageView: UIImageView! 20 | @IBOutlet weak var doneButton: UIButton! 21 | @IBOutlet weak var coverView: UIView! 22 | var viewModel: TaskCellViewModel! { 23 | didSet { 24 | guard viewModel != nil else { return } 25 | self.bind(self.viewModel) 26 | } 27 | } 28 | 29 | override func setupUI() { 30 | super.setupUI() 31 | setupVerticalLine() 32 | setupText() 33 | setupDoneButton() 34 | setupCoverView() 35 | setupContent() 36 | } 37 | 38 | private func bind(_ viewModel: TaskCellViewModel) { 39 | coverView.isHidden = !viewModel.isFinish 40 | checkedImageView.isHidden = !viewModel.isFinish 41 | if viewModel.isFinish { 42 | setupInactiveCirleView() 43 | } else { 44 | setupActiveCirleView() 45 | } 46 | titleLabel.text = viewModel.name 47 | timeLabel.text = viewModel.time 48 | } 49 | } 50 | 51 | // MARK: - Setup UI 52 | extension TaskCell { 53 | private func setupVerticalLine() { 54 | verticalLineView.backgroundColor = App.Theme.current.package.taskCellVerticalLine 55 | } 56 | 57 | private func setupActiveCirleView() { 58 | circleView.fillColor = App.Theme.current.package.taskCellActiveCircle 59 | circleView.borderColor = App.Theme.current.package.taskCellActiveCircle 60 | circleView.shadowColor = App.Theme.current.package.taskCellActiveCircle 61 | circleView.setNeedsDisplay() 62 | } 63 | 64 | private func setupInactiveCirleView() { 65 | circleView.fillColor = App.Theme.current.package.taskCellInactiveCircle 66 | circleView.borderColor = App.Theme.current.package.taskCellInactiveCircle 67 | circleView.shadowColor = App.Theme.current.package.taskCellInactiveCircle 68 | circleView.setNeedsDisplay() 69 | } 70 | 71 | private func setupText() { 72 | titleLabel.textColor = App.Theme.current.package.taskCellTitle 73 | timeLabel.textColor = App.Theme.current.package.taskCellTime 74 | } 75 | 76 | private func setupDoneButton() { 77 | doneView.backgroundColor = App.Theme.current.package.taskCellDoneButton 78 | if let image = checkedImageView.image { 79 | checkedImageView.image = image.withRenderingMode(.alwaysTemplate) 80 | } 81 | checkedImageView.tintColor = App.Theme.current.package.taskCellTickImage 82 | } 83 | 84 | private func setupCoverView() { 85 | coverView.backgroundColor = App.Theme.current.package.taskCellCoverView 86 | } 87 | 88 | private func setupContent() { 89 | contentView.backgroundColor = App.Theme.current.package.taskCellBackground 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /CleanArchitecture/Application/View-ViewModel/Scene/001 - Today/TodayViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TodayViewModel.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 1/16/19. 6 | // Copyright © 2019 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | import RxCocoa 12 | 13 | final class TodayViewModel: ViewModel { 14 | 15 | struct Input { 16 | let loadTrigger: Driver 17 | let addTrigger: Driver 18 | let updateTrigger: Driver 19 | let deleteTrigger: Driver 20 | } 21 | 22 | struct Output { 23 | let taskViewModels: Driver<[TaskCellViewModel]> 24 | let addTask: Driver 25 | let updateTask: Driver 26 | let progress: Driver 27 | let delete: Driver 28 | } 29 | 30 | var coordinator: TodayCoordinator? 31 | private let useCase: TaskUseCase 32 | 33 | init(useCase: TaskUseCase, coordinator: TodayCoordinator) { 34 | self.useCase = useCase 35 | self.coordinator = coordinator 36 | } 37 | 38 | func transform(input: Input) -> Output { 39 | let taskViewModels: Driver<[TaskCellViewModel]> = input.loadTrigger 40 | .flatMapLatest { _ -> Driver<[Task]> in return self.useCase.today().emptyDriverIfError() } 41 | .map { tasks -> [TaskCellViewModel] in return tasks.map { TaskCellViewModel(with: $0) } } 42 | let addTask = input.addTrigger.do(onNext: { _ in self.coordinator?.showScreen(.addTask) }) 43 | let updateTask = input 44 | .updateTrigger 45 | .flatMapLatest { viewModel in return self.useCase.find(by: viewModel.taskId).take(1).emptyDriverIfError() } 46 | .unwrap() 47 | .map({ (task) -> Task in 48 | task.updateStatus() 49 | return task 50 | }) 51 | .flatMapLatest { (task) in return self.useCase.update(task).emptyDriverIfError() } 52 | .map({ _ in return }) 53 | let progress: Driver = input.loadTrigger 54 | .flatMapLatest { _ -> Driver<[Task]> in return self.useCase.today().emptyDriverIfError() } 55 | .map { (tasks) -> Float in 56 | let total = Float(tasks.count) 57 | if total == 0 { return 0 } 58 | let done = Float(tasks.filter { $0.isFinish }.count) 59 | return done/total 60 | } 61 | let delete: Driver = input 62 | .deleteTrigger 63 | .withLatestFrom(taskViewModels) { (indexPath, taskCellViewModels) -> TaskCellViewModel in 64 | let row = indexPath.row 65 | if row < 0 && row >= taskCellViewModels.count { fatalError() } 66 | let viewModel = taskCellViewModels[row] 67 | return viewModel 68 | } 69 | .flatMapLatest { viewModel in return self.useCase.find(by: viewModel.taskId).take(1).emptyDriverIfError() } 70 | .unwrap() 71 | .flatMapLatest { (task) in return self.useCase.delete(task).emptyDriverIfError() } 72 | .map({ _ in return }) 73 | let output = Output(taskViewModels: taskViewModels, addTask: addTask, updateTask: updateTask, progress: progress, delete: delete) 74 | return output 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /CleanArchitecture/Services/Realm/Core/RealmRepository.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RealmRepository.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 12/19/18. 6 | // Copyright © 2018 mlsuho. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Realm 11 | import RealmSwift 12 | import RxSwift 13 | import RxRealm 14 | 15 | protocol Repository { 16 | associatedtype Entity 17 | func findAll() -> Observable<[Entity]> 18 | func find(with predicate: NSPredicate, sortDescriptors: [NSSortDescriptor]) -> Observable<[Entity]> 19 | func save(_ entities: [Entity]) -> Observable<[Entity]> 20 | func save(_ entity: Entity) -> Observable 21 | func delete(_ entity: Entity) -> Observable 22 | } 23 | 24 | final class RealmRepository: Repository where T == T.RealmType.ModelType, T.RealmType: Object { 25 | 26 | private let configuration: Realm.Configuration 27 | private let concurrentScheduler: ConcurrentDispatchQueueScheduler 28 | private let serialScheduler: SerialDispatchQueueScheduler 29 | 30 | private var realm: Realm { 31 | guard let realm = try? Realm(configuration: configuration) else { 32 | fatalError() 33 | } 34 | return realm 35 | } 36 | 37 | init(configuration: Realm.Configuration = Realm.Configuration()) { 38 | self.configuration = configuration 39 | let concurrentQueue = DispatchQueue(label: "me.mlsuho.realm.concurrent", qos: .background) 40 | concurrentScheduler = ConcurrentDispatchQueueScheduler(queue: concurrentQueue) 41 | serialScheduler = SerialDispatchQueueScheduler(internalSerialQueueName: "me.mlsuho.realm.serial") 42 | print("📂\(RLMRealmPathForFile("default.realm"))") 43 | } 44 | 45 | func findAll() -> Observable<[T]> { 46 | return Observable.deferred { 47 | let realm = self.realm 48 | let objs = realm.objects(T.RealmType.self) 49 | return Observable.array(from: objs) 50 | .map { $0.map { $0.asModel() } } 51 | .observeOn(self.concurrentScheduler) 52 | } 53 | } 54 | 55 | func find(with predicate: NSPredicate, sortDescriptors: [NSSortDescriptor]) -> Observable<[T]> { 56 | return Observable.deferred { 57 | let realm = self.realm 58 | let objs = realm 59 | .objects(T.RealmType.self) 60 | .filter(predicate) 61 | .sorted(by: sortDescriptors.compactMap { (des) -> SortDescriptor? in 62 | guard let key = des.key else { return nil } 63 | return SortDescriptor(keyPath: key, ascending: des.ascending) 64 | }) 65 | return Observable.array(from: objs) 66 | .map { $0.map { $0.asModel() } } 67 | .observeOn(self.concurrentScheduler) 68 | } 69 | } 70 | 71 | func save(_ entities: [T]) -> Observable<[T]> { 72 | return Observable.deferred { 73 | let realm = self.realm 74 | return realm.rx.save(entities) 75 | }.observeOn(self.serialScheduler) 76 | } 77 | 78 | func save(_ entity: T) -> Observable { 79 | return Observable.deferred { 80 | let realm = self.realm 81 | return realm.rx.save(entity) 82 | }.observeOn(self.serialScheduler) 83 | } 84 | 85 | func delete(_ entity: T) -> Observable { 86 | return Observable.deferred { 87 | let realm = self.realm 88 | return realm.rx.delete(entity) 89 | }.observeOn(self.serialScheduler) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | reporter: xcode 2 | opt_in_rules: 3 | - force_unwrapping 4 | - empty_count 5 | - vertical_whitespace 6 | excluded: 7 | - Carthage 8 | - Pods 9 | - vendor 10 | - fastlane 11 | private_outlet: 12 | allow_private_set: true 13 | force_unwrapping: error 14 | line_length: 150 15 | identifier_name: 16 | min_length: 2 17 | custom_rules: 18 | disable: 19 | name: "Disable Rule In Code" 20 | regex: "(swiftlint:disable)" 21 | message: "Please do not disable rule in code. " 22 | severity: error 23 | match_kinds: comment 24 | delegate: 25 | name: "delegate" 26 | regex: '(\s{2,}var\s+delegate)' 27 | message: "Please use `weak` for `delegate`. " 28 | severity: error 29 | comments_space: 30 | name: "Space After Comment" 31 | regex: '(^ *//\w+)' 32 | message: "There should be a space after //" 33 | severity: warning 34 | match_kinds: comment 35 | multiple_empty_lines: 36 | name: "Multiple Empty Lines" 37 | regex: '((?: *\n){3,})' 38 | message: "There are too many line breaks" 39 | severity: warning 40 | empty_line: 41 | name: "Empty Line" 42 | regex: '(\}\n +( |class|static|dynamic|@objc|@IBAction|override|private|public|internal){0,}func)' 43 | message: "Must have an empty line between functions. " 44 | severity: warning 45 | extension_mark: 46 | name: "Extension Mark" 47 | regex: '(\}\n\nextension)' 48 | message: "Must have `// MARK:` before extension. " 49 | severity: warning 50 | switch_enum: 51 | name: "Switch Enum" 52 | regex: '(case\s\w+\.+rawValue:)' 53 | message: "Don't use enum.rawValue in switch-case. Please convert value to enum first. " 54 | severity: error 55 | compile_lookup: 56 | name: "Compile Lookup" 57 | regex: '((var|let)\s+\w+\s*=\s*\[)' 58 | message: "You need to give types to the array and dictionary. " 59 | severity: error 60 | weak_self_usage: 61 | name: "Weak Self Usage" 62 | regex: '(self\?\.)' 63 | message: "Explicitly extending lifetime is preferred to optional binding. Ex: `guard let this = self else { return }`. " 64 | severity: error 65 | weak_self_binding: 66 | name: "Weak Self Binding" 67 | regex: '(let\s+(?!this)\w+\s*=\s*self[,\s])' 68 | message: "Please use `let this = self` instead. " 69 | severity: error 70 | unowned: 71 | name: "Unowned" 72 | regex: "(unowned)" 73 | message: "Please use `weak` instead. " 74 | severity: error 75 | empty_string: 76 | name: "Empty String" 77 | regex: '((!\w+.isNotEmpty)|(length == 0))' 78 | message: "Please use `isEmpty` instead. " 79 | severity: error 80 | not_empty_string: 81 | name: "Not Empty String" 82 | regex: '((!\w+.isEmpty)|(length (!=|>) 0))' 83 | message: "Please use `isNotEmpty` instead. " 84 | severity: error 85 | bracket: 86 | name: "Bracket" 87 | regex: '(\}\n{2,}\}|\{\n{2,}\{)' 88 | message: "No empty line between two open or close brackets. " 89 | severity: warning 90 | query_string: 91 | name: "Query String" 92 | regex: '( [=!><]+ \\\(.*\))' 93 | message: "Do not use the string interpolation syntax for query string. " 94 | severity: warning 95 | append_array: 96 | name: "Append array" 97 | regex: '(\+ \[.*\].*)' 98 | message: "Do not use + for array, should use `append` instead" 99 | severity: warning 100 | optional_checking: 101 | name: "Optional checking" 102 | regex: '(\?\?.+)' 103 | message: "You should use `if let` or `guard let` or `if else` instead" 104 | severity: warning 105 | conditional: 106 | name: "Condition statement" 107 | regex: '( \? .+ : )' 108 | message: "Should use if else instead" 109 | severity: warning 110 | -------------------------------------------------------------------------------- /CleanArchitecture/Application/View-ViewModel/Control/View/SView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // View.swift 3 | // CleanArchitecture 4 | // 5 | // Created by Su Ho V. on 1/17/19. 6 | // Copyright © 2019 mlsuho. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @IBDesignable 12 | class SView: UIView { 13 | 14 | @IBInspectable var topLeft: Bool = false { 15 | didSet { 16 | if topLeft { 17 | rectCorner.insert(.topLeft) 18 | } else { 19 | rectCorner.remove(.topLeft) 20 | } 21 | } 22 | } 23 | 24 | @IBInspectable var topRight: Bool = false { 25 | didSet { 26 | if topRight { 27 | rectCorner.insert(.topRight) 28 | } else { 29 | rectCorner.remove(.topRight) 30 | } 31 | } 32 | } 33 | 34 | @IBInspectable var bottomLeft: Bool = false { 35 | didSet { 36 | if bottomLeft { 37 | rectCorner.insert(.bottomLeft) 38 | } else { 39 | rectCorner.remove(.bottomLeft) 40 | } 41 | } 42 | } 43 | 44 | @IBInspectable var bottomRight: Bool = false { 45 | didSet { 46 | if bottomRight { 47 | rectCorner.insert(.bottomRight) 48 | } else { 49 | rectCorner.remove(.bottomRight) 50 | } 51 | } 52 | } 53 | 54 | @IBInspectable var cornerRadius: CGFloat = 0 55 | @IBInspectable var borderColor: UIColor = .white 56 | @IBInspectable var fillColor: UIColor = .white 57 | @IBInspectable var isFill: Bool = false 58 | @IBInspectable var isShadow: Bool = true 59 | @IBInspectable var lineWidth: CGFloat = 0 60 | @IBInspectable var insetDx: CGFloat = 0 61 | @IBInspectable var insetDy: CGFloat = 0 62 | @IBInspectable var shadowOffset: CGSize = CGSize.zero 63 | @IBInspectable var shadowBlur: CGFloat = 10 64 | @IBInspectable var shadowColor: UIColor = .black 65 | 66 | private var rectCorner: UIRectCorner = [.allCorners] 67 | 68 | convenience init(cornerRadius: CGFloat, rectCorner: UIRectCorner) { 69 | self.init(frame: CGRect(origin: .zero, size: CGSize(width: 200, height: 200))) 70 | self.cornerRadius = cornerRadius 71 | self.rectCorner = rectCorner 72 | } 73 | 74 | override func draw(_ rect: CGRect) { 75 | super.draw(rect) 76 | drawBackground(rectangle: rect) 77 | } 78 | 79 | private func drawBackground(rectangle backgroundRectangle: CGRect) { 80 | let tileRectangle = backgroundRectangle.insetBy(dx: insetDx, dy: insetDy) 81 | let context = UIGraphicsGetCurrentContext() 82 | if let context = context { 83 | if cornerRadius < 0 { 84 | let minCorner = min(backgroundRectangle.size.width, backgroundRectangle.size.height) 85 | cornerRadius = minCorner / 2 86 | } 87 | context.beginTransparencyLayer(auxiliaryInfo: nil) 88 | let borderPath = UIBezierPath(roundedRect: tileRectangle, 89 | byRoundingCorners: rectCorner, 90 | cornerRadii: CGSize(width: cornerRadius, height: cornerRadius)) 91 | if isShadow { 92 | context.setShadow(offset: shadowOffset, blur: shadowBlur, color: shadowColor.withAlphaComponent(0.8).cgColor) 93 | } else { 94 | let maskLayer = CAShapeLayer() 95 | maskLayer.path = borderPath.cgPath 96 | layer.mask = maskLayer 97 | } 98 | borderColor.set() 99 | borderPath.lineWidth = lineWidth 100 | borderPath.lineCapStyle = .square 101 | borderPath.stroke() 102 | if isFill { 103 | fillColor.setFill() 104 | borderPath.fill() 105 | } 106 | context.endTransparencyLayer() 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /CleanArchitecture/Application/View-ViewModel/Control/Cell/DetailCell/DetailCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 27 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /CleanArchitecture/Application/View-ViewModel/Scene/003 - AddTask/AddTaskViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.0) 5 | activesupport (4.2.11) 6 | i18n (~> 0.7) 7 | minitest (~> 5.1) 8 | thread_safe (~> 0.3, >= 0.3.4) 9 | tzinfo (~> 1.1) 10 | addressable (2.5.2) 11 | public_suffix (>= 2.0.2, < 4.0) 12 | atomos (0.1.3) 13 | babosa (1.0.2) 14 | claide (1.0.2) 15 | cocoapods (1.5.3) 16 | activesupport (>= 4.0.2, < 5) 17 | claide (>= 1.0.2, < 2.0) 18 | cocoapods-core (= 1.5.3) 19 | cocoapods-deintegrate (>= 1.0.2, < 2.0) 20 | cocoapods-downloader (>= 1.2.0, < 2.0) 21 | cocoapods-plugins (>= 1.0.0, < 2.0) 22 | cocoapods-search (>= 1.0.0, < 2.0) 23 | cocoapods-stats (>= 1.0.0, < 2.0) 24 | cocoapods-trunk (>= 1.3.0, < 2.0) 25 | cocoapods-try (>= 1.1.0, < 2.0) 26 | colored2 (~> 3.1) 27 | escape (~> 0.0.4) 28 | fourflusher (~> 2.0.1) 29 | gh_inspector (~> 1.0) 30 | molinillo (~> 0.6.5) 31 | nap (~> 1.0) 32 | ruby-macho (~> 1.1) 33 | xcodeproj (>= 1.5.7, < 2.0) 34 | cocoapods-core (1.5.3) 35 | activesupport (>= 4.0.2, < 6) 36 | fuzzy_match (~> 2.0.4) 37 | nap (~> 1.0) 38 | cocoapods-deintegrate (1.0.2) 39 | cocoapods-downloader (1.2.2) 40 | cocoapods-plugins (1.0.0) 41 | nap 42 | cocoapods-search (1.0.0) 43 | cocoapods-stats (1.0.0) 44 | cocoapods-trunk (1.3.1) 45 | nap (>= 0.8, < 2.0) 46 | netrc (~> 0.11) 47 | cocoapods-try (1.1.0) 48 | colored (1.2) 49 | colored2 (3.1.2) 50 | commander (4.4.6) 51 | highline (~> 1.7.2) 52 | commander-fastlane (4.4.6) 53 | highline (~> 1.7.2) 54 | concurrent-ruby (1.1.4) 55 | declarative (0.0.10) 56 | declarative-option (0.1.0) 57 | digest-crc (0.4.1) 58 | domain_name (0.5.20180417) 59 | unf (>= 0.0.5, < 1.0.0) 60 | dotenv (2.5.0) 61 | emoji_regex (0.1.1) 62 | escape (0.0.4) 63 | excon (0.62.0) 64 | faraday (0.15.4) 65 | multipart-post (>= 1.2, < 3) 66 | faraday-cookie_jar (0.0.6) 67 | faraday (>= 0.7.4) 68 | http-cookie (~> 1.0.0) 69 | faraday_middleware (0.12.2) 70 | faraday (>= 0.7.4, < 1.0) 71 | fastimage (2.1.5) 72 | fastlane (2.111.0) 73 | CFPropertyList (>= 2.3, < 4.0.0) 74 | addressable (>= 2.3, < 3.0.0) 75 | babosa (>= 1.0.2, < 2.0.0) 76 | bundler (>= 1.12.0, < 2.0.0) 77 | colored 78 | commander-fastlane (>= 4.4.6, < 5.0.0) 79 | dotenv (>= 2.1.1, < 3.0.0) 80 | emoji_regex (~> 0.1) 81 | excon (>= 0.45.0, < 1.0.0) 82 | faraday (~> 0.9) 83 | faraday-cookie_jar (~> 0.0.6) 84 | faraday_middleware (~> 0.9) 85 | fastimage (>= 2.1.0, < 3.0.0) 86 | gh_inspector (>= 1.1.2, < 2.0.0) 87 | google-api-client (>= 0.21.2, < 0.24.0) 88 | google-cloud-storage (>= 1.15.0, < 2.0.0) 89 | highline (>= 1.7.2, < 2.0.0) 90 | json (< 3.0.0) 91 | mini_magick (~> 4.5.1) 92 | multi_json 93 | multi_xml (~> 0.5) 94 | multipart-post (~> 2.0.0) 95 | plist (>= 3.1.0, < 4.0.0) 96 | public_suffix (~> 2.0.0) 97 | rubyzip (>= 1.2.2, < 2.0.0) 98 | security (= 0.1.3) 99 | simctl (~> 1.6.3) 100 | slack-notifier (>= 2.0.0, < 3.0.0) 101 | terminal-notifier (>= 1.6.2, < 2.0.0) 102 | terminal-table (>= 1.4.5, < 2.0.0) 103 | tty-screen (>= 0.6.3, < 1.0.0) 104 | tty-spinner (>= 0.8.0, < 1.0.0) 105 | word_wrap (~> 1.0.0) 106 | xcodeproj (>= 1.6.0, < 2.0.0) 107 | xcpretty (~> 0.3.0) 108 | xcpretty-travis-formatter (>= 0.0.3) 109 | fourflusher (2.0.1) 110 | fuzzy_match (2.0.4) 111 | gh_inspector (1.1.3) 112 | google-api-client (0.23.9) 113 | addressable (~> 2.5, >= 2.5.1) 114 | googleauth (>= 0.5, < 0.7.0) 115 | httpclient (>= 2.8.1, < 3.0) 116 | mime-types (~> 3.0) 117 | representable (~> 3.0) 118 | retriable (>= 2.0, < 4.0) 119 | signet (~> 0.9) 120 | google-cloud-core (1.2.7) 121 | google-cloud-env (~> 1.0) 122 | google-cloud-env (1.0.5) 123 | faraday (~> 0.11) 124 | google-cloud-storage (1.15.0) 125 | digest-crc (~> 0.4) 126 | google-api-client (~> 0.23) 127 | google-cloud-core (~> 1.2) 128 | googleauth (~> 0.6.2) 129 | googleauth (0.6.7) 130 | faraday (~> 0.12) 131 | jwt (>= 1.4, < 3.0) 132 | memoist (~> 0.16) 133 | multi_json (~> 1.11) 134 | os (>= 0.9, < 2.0) 135 | signet (~> 0.7) 136 | highline (1.7.10) 137 | http-cookie (1.0.3) 138 | domain_name (~> 0.5) 139 | httpclient (2.8.3) 140 | i18n (0.9.5) 141 | concurrent-ruby (~> 1.0) 142 | json (2.1.0) 143 | jwt (2.1.0) 144 | linterbot (0.2.7) 145 | commander (~> 4.3) 146 | octokit (~> 4.2) 147 | memoist (0.16.0) 148 | mime-types (3.2.2) 149 | mime-types-data (~> 3.2015) 150 | mime-types-data (3.2018.0812) 151 | mini_magick (4.5.1) 152 | minitest (5.11.3) 153 | molinillo (0.6.6) 154 | multi_json (1.13.1) 155 | multi_xml (0.6.0) 156 | multipart-post (2.0.0) 157 | nanaimo (0.2.6) 158 | nap (1.1.0) 159 | naturally (2.2.0) 160 | netrc (0.11.0) 161 | octokit (4.13.0) 162 | sawyer (~> 0.8.0, >= 0.5.3) 163 | os (1.0.0) 164 | plist (3.4.0) 165 | public_suffix (2.0.5) 166 | representable (3.0.4) 167 | declarative (< 0.1.0) 168 | declarative-option (< 0.2.0) 169 | uber (< 0.2.0) 170 | retriable (3.1.2) 171 | rouge (2.0.7) 172 | ruby-macho (1.3.1) 173 | rubyzip (1.2.2) 174 | sawyer (0.8.1) 175 | addressable (>= 2.3.5, < 2.6) 176 | faraday (~> 0.8, < 1.0) 177 | security (0.1.3) 178 | signet (0.11.0) 179 | addressable (~> 2.3) 180 | faraday (~> 0.9) 181 | jwt (>= 1.5, < 3.0) 182 | multi_json (~> 1.10) 183 | simctl (1.6.5) 184 | CFPropertyList 185 | naturally 186 | slack-notifier (2.3.2) 187 | terminal-notifier (1.8.0) 188 | terminal-table (1.8.0) 189 | unicode-display_width (~> 1.1, >= 1.1.1) 190 | thread_safe (0.3.6) 191 | tty-cursor (0.6.0) 192 | tty-screen (0.6.5) 193 | tty-spinner (0.9.0) 194 | tty-cursor (~> 0.6.0) 195 | tzinfo (1.2.5) 196 | thread_safe (~> 0.1) 197 | uber (0.1.0) 198 | unf (0.1.4) 199 | unf_ext 200 | unf_ext (0.0.7.5) 201 | unicode-display_width (1.4.0) 202 | word_wrap (1.0.0) 203 | xcodeproj (1.7.0) 204 | CFPropertyList (>= 2.3.3, < 4.0) 205 | atomos (~> 0.1.3) 206 | claide (>= 1.0.2, < 2.0) 207 | colored2 (~> 3.1) 208 | nanaimo (~> 0.2.6) 209 | xcpretty (0.3.0) 210 | rouge (~> 2.0.7) 211 | xcpretty-travis-formatter (1.0.0) 212 | xcpretty (~> 0.2, >= 0.0.7) 213 | 214 | PLATFORMS 215 | ruby 216 | 217 | DEPENDENCIES 218 | cocoapods 219 | fastlane 220 | linterbot 221 | xcpretty 222 | 223 | BUNDLED WITH 224 | 1.16.5 225 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ios-clean-architecture 2 | [![Build Status][travis-img]][travis-url] 3 | 4 | This project contains documents and example of clean architecture of iOS application based on **MVVM-C** and **RxSwift** 5 | 6 | **Table of Contents** 7 | - [MVVM-Coordinator and Clean Architecture](#mvvm-coordinator-and-clean-architecture) 8 | - [Overview](#overview) 9 | - [Application](#application) 10 | - [Service](#service) 11 | - [Example](#example) 12 | - [Prerequisites](#prerequisites) 13 | - [Installing](#installing) 14 | - [Contributing](#contributing) 15 | - [Versioning](#versioning) 16 | - [Authors](#authors) 17 | - [License](#license) 18 | - [Acknowledgments](#acknowledgments) 19 | 20 | ## MVVM-Coordinator and Clean Architecture 21 | 22 | ### Overview 23 | 24 | ![Clean Architecture][image-4] 25 | 26 | ### Application 27 | 28 | **Application** is responsible for delivering information to the user and handling user input. This application implemented with MVVM-C. This is place for your **View**s, **ViewModel**s, **Model**s and **Coordinator**s. 29 | 30 | In the current example, **Application** is implemented with **MVVM-C** and use **RxSwift**. 31 | 32 | First of all, **Model** defines `Entity` and `UseCase` of the application. 33 | 34 | ```swift 35 | final class Task { 36 | let id: String 37 | var name: String 38 | var startAt: Date 39 | var createdAt: Date 40 | var updatedAt: Date 41 | var isFinish: Bool 42 | } 43 | ``` 44 | 45 | ```swift 46 | protocol TaskUseCase { 47 | func add(_ task: Task) -> Observable 48 | func update(_ task: Task) -> Observable 49 | func today() -> Observable<[Task]> 50 | func find(by id: String) -> Observable 51 | func delete(_ task: Task) -> Observable 52 | } 53 | ``` 54 | 55 | `View` contains `ViewModel` and `ViewModel` performs pure transformation of a user `Input` to the `Output` 56 | 57 | ```swift 58 | protocol View { 59 | associatedtype ViewModelType: ViewModel 60 | var viewModel: ViewModelType! { get set } 61 | } 62 | 63 | protocol ViewModel { 64 | associatedtype Input 65 | associatedtype Output 66 | associatedtype CoordinatorType: Coordinate 67 | 68 | var coordinator: CoordinatorType? { get set } 69 | 70 | func transform(input: Input) -> Output 71 | } 72 | ``` 73 | 74 | A `Model` (Entity, UseCase) can be injected/initializer into a `ViewModel` and a `ViewModel` also can be injected into a `ViewController` (View) via property injection or initializer. This is done by `Coordinator`. 75 | 76 | ```swift 77 | protocol Coordinate { 78 | associatedtype Screen 79 | associatedtype View: UIViewController 80 | 81 | var viewController: View? { get set } 82 | } 83 | ``` 84 | 85 | `Coordinator` also contains navigation logic for describing which screens are shown in which order. 86 | 87 | ```swift 88 | // View 89 | final class TodayViewController: ViewController, View { 90 | var viewModel: TodayViewModel! 91 | 92 | override func bindViewModel() { 93 | super.bindViewModel() 94 | // Magic here 95 | } 96 | } 97 | ``` 98 | 99 | ```swift 100 | // ViewModel 101 | final class TodayViewModel: ViewModel { 102 | struct Input { 103 | let loadTrigger: Driver 104 | let addTrigger: Driver 105 | let updateTrigger: Driver 106 | let deleteTrigger: Driver 107 | } 108 | 109 | struct Output { 110 | let taskViewModels: Driver<[TaskCellViewModel]> 111 | let addTask: Driver 112 | let updateTask: Driver 113 | let progress: Driver 114 | let delete: Driver 115 | } 116 | 117 | var coordinator: TodayCoordinator? 118 | private let useCase: TaskUseCase 119 | 120 | init(useCase: TaskUseCase, coordinator: TodayCoordinator) { 121 | self.useCase = useCase 122 | self.coordinator = coordinator 123 | } 124 | 125 | func transform(input: Input) -> Output { 126 | // Magic here 127 | } 128 | } 129 | ``` 130 | 131 | As I mentioned above, we will implement injections for models, viewmodels, views in `Coordinator` 132 | 133 | ```swift 134 | final class TabBarCoordinator: Coordinate { 135 | 136 | weak var viewController: TabBarController? 137 | 138 | func showScreen(_ screen: TabBarCoordinator.Screen) {} 139 | 140 | private func todayNavi() -> UINavigationController { 141 | // View 142 | let controller = TodayViewController() 143 | 144 | // Init UseCase for inject into ViewModel 145 | let repository = RealmRepository() 146 | let useCase = RealmTask(repository: repository) 147 | 148 | // Coordinator 149 | let coordinator = TodayCoordinator() 150 | coordinator.viewController = controller 151 | 152 | // ViewModel 153 | let viewModel = TodayViewModel(useCase: useCase, coordinator: coordinator) 154 | controller.viewModel = viewModel 155 | 156 | // 157 | let navigationController = NavigationController(rootViewController: controller) 158 | navigationController.tabBarItem = UITabBarItem(title: App.String.today, 159 | image: App.Image.today, 160 | tag: 0) 161 | return navigationController 162 | } 163 | 164 | // Magic here 165 | } 166 | ``` 167 | 168 | ### Service 169 | 170 | The **Service** is a concrete implementation of **Model** in a specific service. It does hide all implementation details. For example, database implementation whether it is Realm, CoreData, etc. 171 | 172 | Sometime, because of framework requirements (e.g. Realm, CoreData), we can't use `Model's Entity` to implement 173 | 174 | ```swift 175 | final class RTask: Object { 176 | @objc dynamic var id: String = "" 177 | @objc dynamic var name: String = "" 178 | @objc dynamic var startAt: Date = Date() 179 | @objc dynamic var createdAt: Date = Date() 180 | @objc dynamic var updatedAt: Date = Date() 181 | @objc dynamic var isFinish: Bool = false 182 | } 183 | ``` 184 | 185 | The `Service` also contains concrete implementations of `Model's UseCase`, `Repositories` or `Any Services` that are defined in `Model`. 186 | 187 | ```swift 188 | final class RealmTask: TaskUseCase where R.Entity == Task { 189 | private let repository: R 190 | 191 | init(repository: R) { 192 | self.repository = repository 193 | } 194 | 195 | func add(_ task: Task) -> Observable { 196 | return repository.save(task) 197 | } 198 | 199 | func update(_ task: Task) -> Observable { 200 | return repository.save(task) 201 | } 202 | 203 | // Another magic here 204 | } 205 | ``` 206 | 207 | After finish implement the `Service`, we will inject `Service`~`UseCase`(Model) into `ViewModel` by `Coordinator` 208 | 209 | ## Example 210 | 211 | The example application will show how I implemented Clean Architecture with MVVM-C 212 | The example application is `Task Todo App` which uses `Realm` and `Network` as a proof of concept that the `Application` is not dependant on the `Service` implementation detail. 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 |
221 | 222 | ### Prerequisites 223 | 224 | - Xcode 10+ 225 | - Swift 4.2 226 | - Ruby 2.5.1 ([rbenv][rbenv]) 227 | 228 | ```bash 229 | rbenv install 2.5.1 230 | ``` 231 | 232 | - [Bundler][bundler] (manage [cocoapods][cocoapods]) 233 | 234 | ```base 235 | gem install bundler 236 | ``` 237 | 238 | ### Installing 239 | 240 | After you install `ruby-2.5.1` and `bundler` 241 | 242 | Run this command to install `cocoapods` 243 | 244 | ```base 245 | bundle install 246 | ``` 247 | 248 | Then, install dependencies in this project via **Cocoapods** 249 | 250 | ```base 251 | bundle exec pod install 252 | ``` 253 | 254 | Now, run your project with Xcode and see the demo app 255 | 256 | ## Contributing 257 | 258 | Contributions are welcome 🎉🎊 259 | 260 | When contributing to this repository, please first discuss the change you wish to make via issue before making a change. 261 | 262 | You can also opening a PR if you want to fix bugs or improve something. 263 | 264 | ## Versioning 265 | 266 | For the versions available, see the [release on this repository][releases]. 267 | 268 | > We use [SemVer](http://semver.org/) for versioning 269 | 270 | ## Authors 271 | 272 | * [@suho][suho] 273 | 274 | See also the list of [contributors][contributors] who participated in this project. 275 | 276 | ## License 277 | 278 | This project is licensed under the MIT License - see the [LICENSE.md][license] file for details 279 | 280 | ## Acknowledgments 281 | 282 | * Thanks to [@sergdort][sergdort] with his example about clean architecture 283 | * [RxSwift][rxswift] 284 | 285 | [releases]: https://github.com/suho/ios-clean-architecture/releases 286 | [suho]: https://github.com/suho 287 | [contributors]: https://github.com/suho/ios-clean-architecture/contributors 288 | [license]: ./LICENSE.md 289 | [bundler]: https://bundler.io/ 290 | [rbenv]: https://github.com/rbenv/rbenv 291 | [cocoapods]: https://cocoapods.org/ 292 | [fastlane]: https://fastlane.tools/ 293 | [sergdort]: https://github.com/sergdort 294 | [rxswift]: https://github.com/ReactiveX/RxSwift 295 | [image-1]: ./img/ios/add_task_framed.png 296 | [image-2]: ./img/ios/settings_framed.png 297 | [image-3]: ./img/ios/today_tasks_framed.png 298 | [travis-img]: https://travis-ci.org/suho/ios-clean-architecture.svg?branch=master 299 | [travis-url]: https://travis-ci.org/suho/ios-clean-architecture 300 | [image-4]: ./img/draw/architecture.jpg 301 | 302 | -------------------------------------------------------------------------------- /CleanArchitecture/Application/View-ViewModel/Control/Cell/TaskCell/TaskCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /CleanArchitecture.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 37824278A1758B22FE302C2B /* Pods_CleanArchitectureTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 782273A712D21DC25C665C73 /* Pods_CleanArchitectureTests.framework */; }; 11 | 4307934121C9D80D003C5BF0 /* RxError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4307934021C9D80D003C5BF0 /* RxError.swift */; }; 12 | 4307934321C9DA6C003C5BF0 /* Driver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4307934221C9DA6C003C5BF0 /* Driver.swift */; }; 13 | 4307934521C9E393003C5BF0 /* Coordinate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4307934421C9E393003C5BF0 /* Coordinate.swift */; }; 14 | 4307934721C9ECB5003C5BF0 /* RxIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4307934621C9ECB5003C5BF0 /* RxIndicator.swift */; }; 15 | 4310635421ED9A6B0088BB14 /* Task.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4310635321ED9A6B0088BB14 /* Task.swift */; }; 16 | 4310635821ED9E940088BB14 /* TaskUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4310635721ED9E940088BB14 /* TaskUseCase.swift */; }; 17 | 4310635A21EDA5C40088BB14 /* RTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4310635921EDA5C40088BB14 /* RTask.swift */; }; 18 | 4310635C21EDA78F0088BB14 /* RealmTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4310635B21EDA78F0088BB14 /* RealmTask.swift */; }; 19 | 4310635E21EDB38F0088BB14 /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4310635D21EDB38F0088BB14 /* Date.swift */; }; 20 | 4310636221EDB8CC0088BB14 /* TabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4310636021EDB8CC0088BB14 /* TabBarController.swift */; }; 21 | 4310636521EDBCF90088BB14 /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4310636421EDBCF90088BB14 /* UIColor.swift */; }; 22 | 431B24AE2259CE5B00AD5C1C /* DetailCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431B24AD2259CE5B00AD5C1C /* DetailCellViewModel.swift */; }; 23 | 431B24B32259D56E00AD5C1C /* DetailCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 431B24B12259D56E00AD5C1C /* DetailCell.swift */; }; 24 | 431B24B42259D56E00AD5C1C /* DetailCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 431B24B22259D56E00AD5C1C /* DetailCell.xib */; }; 25 | 433BCADC21C7F0000098DCF8 /* TargetType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 433BCAD721C7F0000098DCF8 /* TargetType.swift */; }; 26 | 433BCADD21C7F0000098DCF8 /* Mapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = 433BCAD821C7F0000098DCF8 /* Mapping.swift */; }; 27 | 433BCADE21C7F0000098DCF8 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = 433BCAD921C7F0000098DCF8 /* Router.swift */; }; 28 | 433BCADF21C7F0000098DCF8 /* HTTPTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 433BCADA21C7F0000098DCF8 /* HTTPTask.swift */; }; 29 | 433BCAE821C7F0B50098DCF8 /* JSONDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 433BCAE321C7F0B50098DCF8 /* JSONDecoder.swift */; }; 30 | 433BCAE921C7F0B50098DCF8 /* DateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 433BCAE421C7F0B50098DCF8 /* DateFormatter.swift */; }; 31 | 433BCAED21C7F2CB0098DCF8 /* NetworkError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 433BCAEC21C7F2CB0098DCF8 /* NetworkError.swift */; }; 32 | 433BCAFA21C804FB0098DCF8 /* Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 433BCAF921C804FB0098DCF8 /* Dictionary.swift */; }; 33 | 433BCAFC21C808B10098DCF8 /* Sequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 433BCAFB21C808B10098DCF8 /* Sequence.swift */; }; 34 | 433BCB0E21C91FAD0098DCF8 /* App.Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 433BCB0D21C91FAD0098DCF8 /* App.Theme.swift */; }; 35 | 4353C67E21C6A3F100A824DD /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4353C67D21C6A3F100A824DD /* AppDelegate.swift */; }; 36 | 4353C68521C6A3F200A824DD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4353C68421C6A3F200A824DD /* Assets.xcassets */; }; 37 | 4353C68821C6A3F200A824DD /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4353C68621C6A3F200A824DD /* LaunchScreen.storyboard */; }; 38 | 4353C69321C6A3F200A824DD /* CleanArchitectureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4353C69221C6A3F200A824DD /* CleanArchitectureTests.swift */; }; 39 | 435A7FB021EF6B610014D01F /* App.Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435A7FAF21EF6B610014D01F /* App.Image.swift */; }; 40 | 435A7FB221EF70BE0014D01F /* RootCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435A7FB121EF70BE0014D01F /* RootCoordinator.swift */; }; 41 | 435A7FB421EF77070014D01F /* TabBarCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435A7FB321EF77070014D01F /* TabBarCoordinator.swift */; }; 42 | 435A7FB721EF78930014D01F /* TodayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435A7FB521EF78930014D01F /* TodayViewController.swift */; }; 43 | 435A7FB821EF78930014D01F /* TodayViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 435A7FB621EF78930014D01F /* TodayViewController.xib */; }; 44 | 435A7FBF21EF78AE0014D01F /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435A7FBD21EF78AE0014D01F /* SettingsViewController.swift */; }; 45 | 435A7FC021EF78AE0014D01F /* SettingsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 435A7FBE21EF78AE0014D01F /* SettingsViewController.xib */; }; 46 | 435A7FC221EF7A770014D01F /* TabBarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435A7FC121EF7A770014D01F /* TabBarViewModel.swift */; }; 47 | 435A7FC721EF888A0014D01F /* NavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435A7FC621EF888A0014D01F /* NavigationController.swift */; }; 48 | 435A7FC921EF8D9F0014D01F /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435A7FC821EF8D9F0014D01F /* Settings.swift */; }; 49 | 435A7FCB21EF8FEF0014D01F /* TodayViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435A7FCA21EF8FEF0014D01F /* TodayViewModel.swift */; }; 50 | 435A7FCD21EF900A0014D01F /* TodayCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435A7FCC21EF900A0014D01F /* TodayCoordinator.swift */; }; 51 | 435A7FD421EF92C20014D01F /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435A7FD221EF92C20014D01F /* SettingsViewModel.swift */; }; 52 | 435A7FDD21F008C50014D01F /* TaskCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435A7FDB21F008C50014D01F /* TaskCell.swift */; }; 53 | 435A7FDE21F008C50014D01F /* TaskCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 435A7FDC21F008C50014D01F /* TaskCell.xib */; }; 54 | 435A7FE021F009530014D01F /* TableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435A7FDF21F009530014D01F /* TableViewCell.swift */; }; 55 | 435A7FE221F009B10014D01F /* TaskCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435A7FE121F009B10014D01F /* TaskCellViewModel.swift */; }; 56 | 435A7FE421F00B130014D01F /* View.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435A7FE321F00B130014D01F /* View.swift */; }; 57 | 435A7FE621F00B180014D01F /* ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435A7FE521F00B180014D01F /* ViewModel.swift */; }; 58 | 435A7FE921F011530014D01F /* SView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435A7FE821F011530014D01F /* SView.swift */; }; 59 | 43682B1721C6A93F00716C2C /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43682B1621C6A93F00716C2C /* ViewController.swift */; }; 60 | 43682B1A21C6A9B900716C2C /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43682B1921C6A9B900716C2C /* Application.swift */; }; 61 | 43682B3921C6B28B00716C2C /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43682B3821C6B28B00716C2C /* App.swift */; }; 62 | 43682B3C21C6B38B00716C2C /* App.String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43682B3B21C6B38B00716C2C /* App.String.swift */; }; 63 | 438085102212EE0A00E7A0FC /* App.Key.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4380850F2212EE0A00E7A0FC /* App.Key.swift */; }; 64 | 439809F121CB9E7700E05C14 /* InfoUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 439809F021CB9E7700E05C14 /* InfoUseCase.swift */; }; 65 | 439E9DAD2256571D007AF95C /* AddTaskViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 439E9DAC2256571D007AF95C /* AddTaskViewModel.swift */; }; 66 | 439E9DAF22565747007AF95C /* AddTaskCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 439E9DAE22565747007AF95C /* AddTaskCoordinator.swift */; }; 67 | 43C2452421CFDDD000EEE203 /* ModelConvertibleType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C2451E21CFDDD000EEE203 /* ModelConvertibleType.swift */; }; 68 | 43C2452521CFDDD000EEE203 /* RealmError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C2451F21CFDDD000EEE203 /* RealmError.swift */; }; 69 | 43C2452621CFDDD000EEE203 /* RealmRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C2452021CFDDD000EEE203 /* RealmRepresentable.swift */; }; 70 | 43C2452721CFDDD000EEE203 /* Reactive.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C2452121CFDDD000EEE203 /* Reactive.swift */; }; 71 | 43C2452821CFDDD000EEE203 /* Object.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C2452221CFDDD000EEE203 /* Object.swift */; }; 72 | 43C2452921CFDDD000EEE203 /* RealmRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C2452321CFDDD000EEE203 /* RealmRepository.swift */; }; 73 | 43C6042022579FFA00792226 /* RxExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C6041F22579FFA00792226 /* RxExtension.swift */; }; 74 | 43C604222257CB3B00792226 /* Info.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C604212257CB3B00792226 /* Info.swift */; }; 75 | 43EB31602204A8B600555451 /* UITableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43EB315F2204A8B600555451 /* UITableViewCell.swift */; }; 76 | 43EB31642204B45100555451 /* AddTaskViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43EB31622204B45100555451 /* AddTaskViewController.swift */; }; 77 | 43EB31652204B45100555451 /* AddTaskViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 43EB31632204B45100555451 /* AddTaskViewController.xib */; }; 78 | 43FCC4E92257CC7A009B2375 /* InfoTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43FCC4E82257CC7A009B2375 /* InfoTarget.swift */; }; 79 | 43FCC4EB2257CD24009B2375 /* InfoNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43FCC4EA2257CD24009B2375 /* InfoNetwork.swift */; }; 80 | 43FCC4ED2257CE63009B2375 /* SettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43FCC4EC2257CE62009B2375 /* SettingsCoordinator.swift */; }; 81 | 956645E2564500EDB3CF387E /* Pods_CleanArchitecture.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C02D3B665AE9543DC884C9D2 /* Pods_CleanArchitecture.framework */; }; 82 | /* End PBXBuildFile section */ 83 | 84 | /* Begin PBXContainerItemProxy section */ 85 | 4353C68F21C6A3F200A824DD /* PBXContainerItemProxy */ = { 86 | isa = PBXContainerItemProxy; 87 | containerPortal = 4353C67221C6A3F100A824DD /* Project object */; 88 | proxyType = 1; 89 | remoteGlobalIDString = 4353C67921C6A3F100A824DD; 90 | remoteInfo = CleanArchitecture; 91 | }; 92 | /* End PBXContainerItemProxy section */ 93 | 94 | /* Begin PBXFileReference section */ 95 | 36A681191E9522F67040C5B4 /* Pods_CleanArchitectureUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_CleanArchitectureUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 96 | 4307934021C9D80D003C5BF0 /* RxError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RxError.swift; sourceTree = ""; }; 97 | 4307934221C9DA6C003C5BF0 /* Driver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Driver.swift; sourceTree = ""; }; 98 | 4307934421C9E393003C5BF0 /* Coordinate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coordinate.swift; sourceTree = ""; }; 99 | 4307934621C9ECB5003C5BF0 /* RxIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RxIndicator.swift; sourceTree = ""; }; 100 | 4310635321ED9A6B0088BB14 /* Task.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Task.swift; sourceTree = ""; }; 101 | 4310635721ED9E940088BB14 /* TaskUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskUseCase.swift; sourceTree = ""; }; 102 | 4310635921EDA5C40088BB14 /* RTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RTask.swift; sourceTree = ""; }; 103 | 4310635B21EDA78F0088BB14 /* RealmTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealmTask.swift; sourceTree = ""; }; 104 | 4310635D21EDB38F0088BB14 /* Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = ""; }; 105 | 4310636021EDB8CC0088BB14 /* TabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarController.swift; sourceTree = ""; }; 106 | 4310636421EDBCF90088BB14 /* UIColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = ""; }; 107 | 431B24AD2259CE5B00AD5C1C /* DetailCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailCellViewModel.swift; sourceTree = ""; }; 108 | 431B24B12259D56E00AD5C1C /* DetailCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailCell.swift; sourceTree = ""; }; 109 | 431B24B22259D56E00AD5C1C /* DetailCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DetailCell.xib; sourceTree = ""; }; 110 | 433BCAD721C7F0000098DCF8 /* TargetType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TargetType.swift; sourceTree = ""; }; 111 | 433BCAD821C7F0000098DCF8 /* Mapping.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Mapping.swift; sourceTree = ""; }; 112 | 433BCAD921C7F0000098DCF8 /* Router.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = ""; }; 113 | 433BCADA21C7F0000098DCF8 /* HTTPTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTTPTask.swift; sourceTree = ""; }; 114 | 433BCAE321C7F0B50098DCF8 /* JSONDecoder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONDecoder.swift; sourceTree = ""; }; 115 | 433BCAE421C7F0B50098DCF8 /* DateFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateFormatter.swift; sourceTree = ""; }; 116 | 433BCAEC21C7F2CB0098DCF8 /* NetworkError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkError.swift; sourceTree = ""; }; 117 | 433BCAF921C804FB0098DCF8 /* Dictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dictionary.swift; sourceTree = ""; }; 118 | 433BCAFB21C808B10098DCF8 /* Sequence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sequence.swift; sourceTree = ""; }; 119 | 433BCB0D21C91FAD0098DCF8 /* App.Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.Theme.swift; sourceTree = ""; }; 120 | 4353C67A21C6A3F100A824DD /* CleanArchitecture.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CleanArchitecture.app; sourceTree = BUILT_PRODUCTS_DIR; }; 121 | 4353C67D21C6A3F100A824DD /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 122 | 4353C68421C6A3F200A824DD /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 123 | 4353C68721C6A3F200A824DD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 124 | 4353C68921C6A3F200A824DD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 125 | 4353C68E21C6A3F200A824DD /* CleanArchitectureTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CleanArchitectureTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 126 | 4353C69221C6A3F200A824DD /* CleanArchitectureTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CleanArchitectureTests.swift; sourceTree = ""; }; 127 | 4353C69421C6A3F200A824DD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 128 | 435A7FAF21EF6B610014D01F /* App.Image.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.Image.swift; sourceTree = ""; }; 129 | 435A7FB121EF70BE0014D01F /* RootCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootCoordinator.swift; sourceTree = ""; }; 130 | 435A7FB321EF77070014D01F /* TabBarCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarCoordinator.swift; sourceTree = ""; }; 131 | 435A7FB521EF78930014D01F /* TodayViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodayViewController.swift; sourceTree = ""; }; 132 | 435A7FB621EF78930014D01F /* TodayViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TodayViewController.xib; sourceTree = ""; }; 133 | 435A7FBD21EF78AE0014D01F /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; 134 | 435A7FBE21EF78AE0014D01F /* SettingsViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SettingsViewController.xib; sourceTree = ""; }; 135 | 435A7FC121EF7A770014D01F /* TabBarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarViewModel.swift; sourceTree = ""; }; 136 | 435A7FC621EF888A0014D01F /* NavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationController.swift; sourceTree = ""; }; 137 | 435A7FC821EF8D9F0014D01F /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = ""; }; 138 | 435A7FCA21EF8FEF0014D01F /* TodayViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodayViewModel.swift; sourceTree = ""; }; 139 | 435A7FCC21EF900A0014D01F /* TodayCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TodayCoordinator.swift; sourceTree = ""; }; 140 | 435A7FD221EF92C20014D01F /* SettingsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = ""; }; 141 | 435A7FDB21F008C50014D01F /* TaskCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskCell.swift; sourceTree = ""; }; 142 | 435A7FDC21F008C50014D01F /* TaskCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TaskCell.xib; sourceTree = ""; }; 143 | 435A7FDF21F009530014D01F /* TableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewCell.swift; sourceTree = ""; }; 144 | 435A7FE121F009B10014D01F /* TaskCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskCellViewModel.swift; sourceTree = ""; }; 145 | 435A7FE321F00B130014D01F /* View.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = View.swift; sourceTree = ""; }; 146 | 435A7FE521F00B180014D01F /* ViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewModel.swift; sourceTree = ""; }; 147 | 435A7FE821F011530014D01F /* SView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SView.swift; sourceTree = ""; }; 148 | 43682B1621C6A93F00716C2C /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 149 | 43682B1921C6A9B900716C2C /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = ""; }; 150 | 43682B3821C6B28B00716C2C /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; 151 | 43682B3B21C6B38B00716C2C /* App.String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.String.swift; sourceTree = ""; }; 152 | 4380850F2212EE0A00E7A0FC /* App.Key.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.Key.swift; sourceTree = ""; }; 153 | 439809F021CB9E7700E05C14 /* InfoUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoUseCase.swift; sourceTree = ""; }; 154 | 439E9DAC2256571D007AF95C /* AddTaskViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddTaskViewModel.swift; sourceTree = ""; }; 155 | 439E9DAE22565747007AF95C /* AddTaskCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddTaskCoordinator.swift; sourceTree = ""; }; 156 | 43C2451E21CFDDD000EEE203 /* ModelConvertibleType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModelConvertibleType.swift; sourceTree = ""; }; 157 | 43C2451F21CFDDD000EEE203 /* RealmError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealmError.swift; sourceTree = ""; }; 158 | 43C2452021CFDDD000EEE203 /* RealmRepresentable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealmRepresentable.swift; sourceTree = ""; }; 159 | 43C2452121CFDDD000EEE203 /* Reactive.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Reactive.swift; sourceTree = ""; }; 160 | 43C2452221CFDDD000EEE203 /* Object.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Object.swift; sourceTree = ""; }; 161 | 43C2452321CFDDD000EEE203 /* RealmRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealmRepository.swift; sourceTree = ""; }; 162 | 43C6041F22579FFA00792226 /* RxExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RxExtension.swift; sourceTree = ""; }; 163 | 43C604212257CB3B00792226 /* Info.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Info.swift; sourceTree = ""; }; 164 | 43EB315F2204A8B600555451 /* UITableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITableViewCell.swift; sourceTree = ""; }; 165 | 43EB31622204B45100555451 /* AddTaskViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddTaskViewController.swift; sourceTree = ""; }; 166 | 43EB31632204B45100555451 /* AddTaskViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AddTaskViewController.xib; sourceTree = ""; }; 167 | 43FCC4E82257CC7A009B2375 /* InfoTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoTarget.swift; sourceTree = ""; }; 168 | 43FCC4EA2257CD24009B2375 /* InfoNetwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoNetwork.swift; sourceTree = ""; }; 169 | 43FCC4EC2257CE62009B2375 /* SettingsCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsCoordinator.swift; sourceTree = ""; }; 170 | 782273A712D21DC25C665C73 /* Pods_CleanArchitectureTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_CleanArchitectureTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 171 | 920C447378C5817458E5AE75 /* Pods-CleanArchitectureTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CleanArchitectureTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-CleanArchitectureTests/Pods-CleanArchitectureTests.debug.xcconfig"; sourceTree = ""; }; 172 | A985D34ABB22C9884803DFB1 /* Pods-CleanArchitecture.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CleanArchitecture.release.xcconfig"; path = "Pods/Target Support Files/Pods-CleanArchitecture/Pods-CleanArchitecture.release.xcconfig"; sourceTree = ""; }; 173 | B6ADDD66ADC02AFCEA8AB468 /* Pods-CleanArchitecture.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CleanArchitecture.debug.xcconfig"; path = "Pods/Target Support Files/Pods-CleanArchitecture/Pods-CleanArchitecture.debug.xcconfig"; sourceTree = ""; }; 174 | C02D3B665AE9543DC884C9D2 /* Pods_CleanArchitecture.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_CleanArchitecture.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 175 | E834C762AF7BE59A89A92D33 /* Pods-CleanArchitectureUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CleanArchitectureUITests.release.xcconfig"; path = "Pods/Target Support Files/Pods-CleanArchitectureUITests/Pods-CleanArchitectureUITests.release.xcconfig"; sourceTree = ""; }; 176 | ED427AF608C633973D2DC75E /* Pods-CleanArchitectureUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CleanArchitectureUITests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-CleanArchitectureUITests/Pods-CleanArchitectureUITests.debug.xcconfig"; sourceTree = ""; }; 177 | FDC4F71D3A668C5763ADD435 /* Pods-CleanArchitectureTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CleanArchitectureTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-CleanArchitectureTests/Pods-CleanArchitectureTests.release.xcconfig"; sourceTree = ""; }; 178 | /* End PBXFileReference section */ 179 | 180 | /* Begin PBXFrameworksBuildPhase section */ 181 | 4353C67721C6A3F100A824DD /* Frameworks */ = { 182 | isa = PBXFrameworksBuildPhase; 183 | buildActionMask = 2147483647; 184 | files = ( 185 | 956645E2564500EDB3CF387E /* Pods_CleanArchitecture.framework in Frameworks */, 186 | ); 187 | runOnlyForDeploymentPostprocessing = 0; 188 | }; 189 | 4353C68B21C6A3F200A824DD /* Frameworks */ = { 190 | isa = PBXFrameworksBuildPhase; 191 | buildActionMask = 2147483647; 192 | files = ( 193 | 37824278A1758B22FE302C2B /* Pods_CleanArchitectureTests.framework in Frameworks */, 194 | ); 195 | runOnlyForDeploymentPostprocessing = 0; 196 | }; 197 | /* End PBXFrameworksBuildPhase section */ 198 | 199 | /* Begin PBXGroup section */ 200 | 0A8590EDB8344B57477E7F20 /* Frameworks */ = { 201 | isa = PBXGroup; 202 | children = ( 203 | C02D3B665AE9543DC884C9D2 /* Pods_CleanArchitecture.framework */, 204 | 782273A712D21DC25C665C73 /* Pods_CleanArchitectureTests.framework */, 205 | 36A681191E9522F67040C5B4 /* Pods_CleanArchitectureUITests.framework */, 206 | ); 207 | name = Frameworks; 208 | sourceTree = ""; 209 | }; 210 | 4307933D21C9D7C6003C5BF0 /* Reactive */ = { 211 | isa = PBXGroup; 212 | children = ( 213 | 4307934221C9DA6C003C5BF0 /* Driver.swift */, 214 | 4307934021C9D80D003C5BF0 /* RxError.swift */, 215 | 4307934621C9ECB5003C5BF0 /* RxIndicator.swift */, 216 | 43C6041F22579FFA00792226 /* RxExtension.swift */, 217 | ); 218 | path = Reactive; 219 | sourceTree = ""; 220 | }; 221 | 4307934821CA0538003C5BF0 /* Entities */ = { 222 | isa = PBXGroup; 223 | children = ( 224 | 4310635921EDA5C40088BB14 /* RTask.swift */, 225 | ); 226 | path = Entities; 227 | sourceTree = ""; 228 | }; 229 | 4310635121ED05FE0088BB14 /* Modules */ = { 230 | isa = PBXGroup; 231 | children = ( 232 | 43FCC4E72257CC59009B2375 /* Info */, 233 | ); 234 | path = Modules; 235 | sourceTree = ""; 236 | }; 237 | 4310635221ED06120088BB14 /* Modules */ = { 238 | isa = PBXGroup; 239 | children = ( 240 | 4310635B21EDA78F0088BB14 /* RealmTask.swift */, 241 | ); 242 | path = Modules; 243 | sourceTree = ""; 244 | }; 245 | 431B24AC2259CE4500AD5C1C /* DetailCell */ = { 246 | isa = PBXGroup; 247 | children = ( 248 | 431B24B12259D56E00AD5C1C /* DetailCell.swift */, 249 | 431B24B22259D56E00AD5C1C /* DetailCell.xib */, 250 | 431B24AD2259CE5B00AD5C1C /* DetailCellViewModel.swift */, 251 | ); 252 | path = DetailCell; 253 | sourceTree = ""; 254 | }; 255 | 433BCAD421C7EDF60098DCF8 /* Scene */ = { 256 | isa = PBXGroup; 257 | children = ( 258 | 4386835C21EB58E200AF19BE /* 000 - TabBar */, 259 | 4386835D21EB599800AF19BE /* 001 - Today */, 260 | 4386835F21EB59B600AF19BE /* 002 - Settings */, 261 | 43EB31612204B25400555451 /* 003 - AddTask */, 262 | ); 263 | path = Scene; 264 | sourceTree = ""; 265 | }; 266 | 433BCAD521C7F0000098DCF8 /* Core */ = { 267 | isa = PBXGroup; 268 | children = ( 269 | 433BCAD821C7F0000098DCF8 /* Mapping.swift */, 270 | 433BCAEC21C7F2CB0098DCF8 /* NetworkError.swift */, 271 | 433BCAD921C7F0000098DCF8 /* Router.swift */, 272 | 433BCAD721C7F0000098DCF8 /* TargetType.swift */, 273 | 433BCADA21C7F0000098DCF8 /* HTTPTask.swift */, 274 | ); 275 | path = Core; 276 | sourceTree = ""; 277 | }; 278 | 433BCAE021C7F06A0098DCF8 /* Common */ = { 279 | isa = PBXGroup; 280 | children = ( 281 | 4307933D21C9D7C6003C5BF0 /* Reactive */, 282 | 433BCAE221C7F0B50098DCF8 /* Apple */, 283 | ); 284 | path = Common; 285 | sourceTree = ""; 286 | }; 287 | 433BCAE221C7F0B50098DCF8 /* Apple */ = { 288 | isa = PBXGroup; 289 | children = ( 290 | 433BCAE321C7F0B50098DCF8 /* JSONDecoder.swift */, 291 | 433BCAE421C7F0B50098DCF8 /* DateFormatter.swift */, 292 | 433BCAF921C804FB0098DCF8 /* Dictionary.swift */, 293 | 433BCAFB21C808B10098DCF8 /* Sequence.swift */, 294 | 4310635D21EDB38F0088BB14 /* Date.swift */, 295 | 4310636421EDBCF90088BB14 /* UIColor.swift */, 296 | 43EB315F2204A8B600555451 /* UITableViewCell.swift */, 297 | ); 298 | path = Apple; 299 | sourceTree = ""; 300 | }; 301 | 433BCAFF21C80F7A0098DCF8 /* UseCase */ = { 302 | isa = PBXGroup; 303 | children = ( 304 | 439809F021CB9E7700E05C14 /* InfoUseCase.swift */, 305 | 4310635721ED9E940088BB14 /* TaskUseCase.swift */, 306 | ); 307 | path = UseCase; 308 | sourceTree = ""; 309 | }; 310 | 4353C67121C6A3F100A824DD = { 311 | isa = PBXGroup; 312 | children = ( 313 | 4353C67C21C6A3F100A824DD /* CleanArchitecture */, 314 | 4353C69121C6A3F200A824DD /* CleanArchitectureTests */, 315 | 4353C67B21C6A3F100A824DD /* Products */, 316 | 51731A29D82242B01C173510 /* Pods */, 317 | 0A8590EDB8344B57477E7F20 /* Frameworks */, 318 | ); 319 | sourceTree = ""; 320 | }; 321 | 4353C67B21C6A3F100A824DD /* Products */ = { 322 | isa = PBXGroup; 323 | children = ( 324 | 4353C67A21C6A3F100A824DD /* CleanArchitecture.app */, 325 | 4353C68E21C6A3F200A824DD /* CleanArchitectureTests.xctest */, 326 | ); 327 | name = Products; 328 | sourceTree = ""; 329 | }; 330 | 4353C67C21C6A3F100A824DD /* CleanArchitecture */ = { 331 | isa = PBXGroup; 332 | children = ( 333 | 43682B0C21C6A89C00716C2C /* Application */, 334 | 433BCAE021C7F06A0098DCF8 /* Common */, 335 | 43682B0E21C6A8A900716C2C /* Define */, 336 | 43682B0F21C6A8B600716C2C /* Resources */, 337 | 43682B3F21C6B48B00716C2C /* Services */, 338 | 43682B1121C6A8C900716C2C /* Support */, 339 | ); 340 | path = CleanArchitecture; 341 | sourceTree = ""; 342 | }; 343 | 4353C69121C6A3F200A824DD /* CleanArchitectureTests */ = { 344 | isa = PBXGroup; 345 | children = ( 346 | 4353C69221C6A3F200A824DD /* CleanArchitectureTests.swift */, 347 | 4353C69421C6A3F200A824DD /* Info.plist */, 348 | ); 349 | path = CleanArchitectureTests; 350 | sourceTree = ""; 351 | }; 352 | 435A7FD721F0082B0014D01F /* Cell */ = { 353 | isa = PBXGroup; 354 | children = ( 355 | 431B24AC2259CE4500AD5C1C /* DetailCell */, 356 | 435A7FDA21F008AC0014D01F /* TaskCell */, 357 | ); 358 | path = Cell; 359 | sourceTree = ""; 360 | }; 361 | 435A7FDA21F008AC0014D01F /* TaskCell */ = { 362 | isa = PBXGroup; 363 | children = ( 364 | 435A7FDB21F008C50014D01F /* TaskCell.swift */, 365 | 435A7FDC21F008C50014D01F /* TaskCell.xib */, 366 | 435A7FE121F009B10014D01F /* TaskCellViewModel.swift */, 367 | ); 368 | path = TaskCell; 369 | sourceTree = ""; 370 | }; 371 | 435A7FE721F0114D0014D01F /* View */ = { 372 | isa = PBXGroup; 373 | children = ( 374 | 435A7FE821F011530014D01F /* SView.swift */, 375 | ); 376 | path = View; 377 | sourceTree = ""; 378 | }; 379 | 43682B0C21C6A89C00716C2C /* Application */ = { 380 | isa = PBXGroup; 381 | children = ( 382 | 4353C67D21C6A3F100A824DD /* AppDelegate.swift */, 383 | 43682B1921C6A9B900716C2C /* Application.swift */, 384 | 435A7FB121EF70BE0014D01F /* RootCoordinator.swift */, 385 | 43682B1521C6A93000716C2C /* Base */, 386 | 43682B3A21C6B2E600716C2C /* Model */, 387 | 43682B1021C6A8BD00716C2C /* View-ViewModel */, 388 | ); 389 | path = Application; 390 | sourceTree = ""; 391 | }; 392 | 43682B0D21C6A8A300716C2C /* Control */ = { 393 | isa = PBXGroup; 394 | children = ( 395 | 435A7FD721F0082B0014D01F /* Cell */, 396 | 435A7FE721F0114D0014D01F /* View */, 397 | ); 398 | path = Control; 399 | sourceTree = ""; 400 | }; 401 | 43682B0E21C6A8A900716C2C /* Define */ = { 402 | isa = PBXGroup; 403 | children = ( 404 | 43682B3821C6B28B00716C2C /* App.swift */, 405 | 433BCB0D21C91FAD0098DCF8 /* App.Theme.swift */, 406 | 43682B3B21C6B38B00716C2C /* App.String.swift */, 407 | 435A7FAF21EF6B610014D01F /* App.Image.swift */, 408 | 4380850F2212EE0A00E7A0FC /* App.Key.swift */, 409 | ); 410 | path = Define; 411 | sourceTree = ""; 412 | }; 413 | 43682B0F21C6A8B600716C2C /* Resources */ = { 414 | isa = PBXGroup; 415 | children = ( 416 | 43682B1321C6A8EF00716C2C /* Images */, 417 | 43682B1221C6A8E100716C2C /* LaunchScreen */, 418 | ); 419 | path = Resources; 420 | sourceTree = ""; 421 | }; 422 | 43682B1021C6A8BD00716C2C /* View-ViewModel */ = { 423 | isa = PBXGroup; 424 | children = ( 425 | 433BCAD421C7EDF60098DCF8 /* Scene */, 426 | 43682B0D21C6A8A300716C2C /* Control */, 427 | ); 428 | path = "View-ViewModel"; 429 | sourceTree = ""; 430 | }; 431 | 43682B1121C6A8C900716C2C /* Support */ = { 432 | isa = PBXGroup; 433 | children = ( 434 | 4353C68921C6A3F200A824DD /* Info.plist */, 435 | ); 436 | path = Support; 437 | sourceTree = ""; 438 | }; 439 | 43682B1221C6A8E100716C2C /* LaunchScreen */ = { 440 | isa = PBXGroup; 441 | children = ( 442 | 4353C68621C6A3F200A824DD /* LaunchScreen.storyboard */, 443 | ); 444 | path = LaunchScreen; 445 | sourceTree = ""; 446 | }; 447 | 43682B1321C6A8EF00716C2C /* Images */ = { 448 | isa = PBXGroup; 449 | children = ( 450 | 4353C68421C6A3F200A824DD /* Assets.xcassets */, 451 | ); 452 | path = Images; 453 | sourceTree = ""; 454 | }; 455 | 43682B1521C6A93000716C2C /* Base */ = { 456 | isa = PBXGroup; 457 | children = ( 458 | 4307934421C9E393003C5BF0 /* Coordinate.swift */, 459 | 435A7FC621EF888A0014D01F /* NavigationController.swift */, 460 | 435A7FDF21F009530014D01F /* TableViewCell.swift */, 461 | 435A7FE321F00B130014D01F /* View.swift */, 462 | 43682B1621C6A93F00716C2C /* ViewController.swift */, 463 | 435A7FE521F00B180014D01F /* ViewModel.swift */, 464 | ); 465 | path = Base; 466 | sourceTree = ""; 467 | }; 468 | 43682B3A21C6B2E600716C2C /* Model */ = { 469 | isa = PBXGroup; 470 | children = ( 471 | 43682B3D21C6B3D700716C2C /* Entity */, 472 | 433BCAFF21C80F7A0098DCF8 /* UseCase */, 473 | ); 474 | path = Model; 475 | sourceTree = ""; 476 | }; 477 | 43682B3D21C6B3D700716C2C /* Entity */ = { 478 | isa = PBXGroup; 479 | children = ( 480 | 4310635321ED9A6B0088BB14 /* Task.swift */, 481 | 435A7FC821EF8D9F0014D01F /* Settings.swift */, 482 | 43C604212257CB3B00792226 /* Info.swift */, 483 | ); 484 | path = Entity; 485 | sourceTree = ""; 486 | }; 487 | 43682B3F21C6B48B00716C2C /* Services */ = { 488 | isa = PBXGroup; 489 | children = ( 490 | 43682B4021C7596300716C2C /* Network */, 491 | 43682B4121C7596C00716C2C /* Realm */, 492 | ); 493 | path = Services; 494 | sourceTree = ""; 495 | }; 496 | 43682B4021C7596300716C2C /* Network */ = { 497 | isa = PBXGroup; 498 | children = ( 499 | 433BCAD521C7F0000098DCF8 /* Core */, 500 | 4310635121ED05FE0088BB14 /* Modules */, 501 | ); 502 | path = Network; 503 | sourceTree = ""; 504 | }; 505 | 43682B4121C7596C00716C2C /* Realm */ = { 506 | isa = PBXGroup; 507 | children = ( 508 | 43C2451D21CFDDD000EEE203 /* Core */, 509 | 4310635221ED06120088BB14 /* Modules */, 510 | 4307934821CA0538003C5BF0 /* Entities */, 511 | ); 512 | path = Realm; 513 | sourceTree = ""; 514 | }; 515 | 4386835C21EB58E200AF19BE /* 000 - TabBar */ = { 516 | isa = PBXGroup; 517 | children = ( 518 | 4310636021EDB8CC0088BB14 /* TabBarController.swift */, 519 | 435A7FB321EF77070014D01F /* TabBarCoordinator.swift */, 520 | 435A7FC121EF7A770014D01F /* TabBarViewModel.swift */, 521 | ); 522 | path = "000 - TabBar"; 523 | sourceTree = ""; 524 | }; 525 | 4386835D21EB599800AF19BE /* 001 - Today */ = { 526 | isa = PBXGroup; 527 | children = ( 528 | 435A7FCC21EF900A0014D01F /* TodayCoordinator.swift */, 529 | 435A7FB521EF78930014D01F /* TodayViewController.swift */, 530 | 435A7FCA21EF8FEF0014D01F /* TodayViewModel.swift */, 531 | 435A7FB621EF78930014D01F /* TodayViewController.xib */, 532 | ); 533 | path = "001 - Today"; 534 | sourceTree = ""; 535 | }; 536 | 4386835F21EB59B600AF19BE /* 002 - Settings */ = { 537 | isa = PBXGroup; 538 | children = ( 539 | 43FCC4EC2257CE62009B2375 /* SettingsCoordinator.swift */, 540 | 435A7FBD21EF78AE0014D01F /* SettingsViewController.swift */, 541 | 435A7FD221EF92C20014D01F /* SettingsViewModel.swift */, 542 | 435A7FBE21EF78AE0014D01F /* SettingsViewController.xib */, 543 | ); 544 | path = "002 - Settings"; 545 | sourceTree = ""; 546 | }; 547 | 43C2451D21CFDDD000EEE203 /* Core */ = { 548 | isa = PBXGroup; 549 | children = ( 550 | 43C2451E21CFDDD000EEE203 /* ModelConvertibleType.swift */, 551 | 43C2452221CFDDD000EEE203 /* Object.swift */, 552 | 43C2452121CFDDD000EEE203 /* Reactive.swift */, 553 | 43C2451F21CFDDD000EEE203 /* RealmError.swift */, 554 | 43C2452321CFDDD000EEE203 /* RealmRepository.swift */, 555 | 43C2452021CFDDD000EEE203 /* RealmRepresentable.swift */, 556 | ); 557 | path = Core; 558 | sourceTree = ""; 559 | }; 560 | 43EB31612204B25400555451 /* 003 - AddTask */ = { 561 | isa = PBXGroup; 562 | children = ( 563 | 43EB31622204B45100555451 /* AddTaskViewController.swift */, 564 | 43EB31632204B45100555451 /* AddTaskViewController.xib */, 565 | 439E9DAC2256571D007AF95C /* AddTaskViewModel.swift */, 566 | 439E9DAE22565747007AF95C /* AddTaskCoordinator.swift */, 567 | ); 568 | path = "003 - AddTask"; 569 | sourceTree = ""; 570 | }; 571 | 43FCC4E72257CC59009B2375 /* Info */ = { 572 | isa = PBXGroup; 573 | children = ( 574 | 43FCC4E82257CC7A009B2375 /* InfoTarget.swift */, 575 | 43FCC4EA2257CD24009B2375 /* InfoNetwork.swift */, 576 | ); 577 | path = Info; 578 | sourceTree = ""; 579 | }; 580 | 51731A29D82242B01C173510 /* Pods */ = { 581 | isa = PBXGroup; 582 | children = ( 583 | B6ADDD66ADC02AFCEA8AB468 /* Pods-CleanArchitecture.debug.xcconfig */, 584 | A985D34ABB22C9884803DFB1 /* Pods-CleanArchitecture.release.xcconfig */, 585 | 920C447378C5817458E5AE75 /* Pods-CleanArchitectureTests.debug.xcconfig */, 586 | FDC4F71D3A668C5763ADD435 /* Pods-CleanArchitectureTests.release.xcconfig */, 587 | ED427AF608C633973D2DC75E /* Pods-CleanArchitectureUITests.debug.xcconfig */, 588 | E834C762AF7BE59A89A92D33 /* Pods-CleanArchitectureUITests.release.xcconfig */, 589 | ); 590 | name = Pods; 591 | sourceTree = ""; 592 | }; 593 | /* End PBXGroup section */ 594 | 595 | /* Begin PBXNativeTarget section */ 596 | 4353C67921C6A3F100A824DD /* CleanArchitecture */ = { 597 | isa = PBXNativeTarget; 598 | buildConfigurationList = 4353C6A221C6A3F200A824DD /* Build configuration list for PBXNativeTarget "CleanArchitecture" */; 599 | buildPhases = ( 600 | BBE04EB16FBACA4B7A551FF7 /* [CP] Check Pods Manifest.lock */, 601 | 43682B1821C6A97C00716C2C /* SwiftLint */, 602 | 4353C67621C6A3F100A824DD /* Sources */, 603 | 4353C67721C6A3F100A824DD /* Frameworks */, 604 | 4353C67821C6A3F100A824DD /* Resources */, 605 | C30751DB01C62837CDB601DE /* [CP] Embed Pods Frameworks */, 606 | ); 607 | buildRules = ( 608 | ); 609 | dependencies = ( 610 | ); 611 | name = CleanArchitecture; 612 | productName = CleanArchitecture; 613 | productReference = 4353C67A21C6A3F100A824DD /* CleanArchitecture.app */; 614 | productType = "com.apple.product-type.application"; 615 | }; 616 | 4353C68D21C6A3F200A824DD /* CleanArchitectureTests */ = { 617 | isa = PBXNativeTarget; 618 | buildConfigurationList = 4353C6A521C6A3F200A824DD /* Build configuration list for PBXNativeTarget "CleanArchitectureTests" */; 619 | buildPhases = ( 620 | 6B3569297CA1A04067E6407B /* [CP] Check Pods Manifest.lock */, 621 | 4353C68A21C6A3F200A824DD /* Sources */, 622 | 4353C68B21C6A3F200A824DD /* Frameworks */, 623 | 4353C68C21C6A3F200A824DD /* Resources */, 624 | D1547D4D567A00BD669994B8 /* [CP] Embed Pods Frameworks */, 625 | ); 626 | buildRules = ( 627 | ); 628 | dependencies = ( 629 | 4353C69021C6A3F200A824DD /* PBXTargetDependency */, 630 | ); 631 | name = CleanArchitectureTests; 632 | productName = CleanArchitectureTests; 633 | productReference = 4353C68E21C6A3F200A824DD /* CleanArchitectureTests.xctest */; 634 | productType = "com.apple.product-type.bundle.unit-test"; 635 | }; 636 | /* End PBXNativeTarget section */ 637 | 638 | /* Begin PBXProject section */ 639 | 4353C67221C6A3F100A824DD /* Project object */ = { 640 | isa = PBXProject; 641 | attributes = { 642 | LastSwiftUpdateCheck = 1010; 643 | LastUpgradeCheck = 1010; 644 | ORGANIZATIONNAME = mlsuho; 645 | TargetAttributes = { 646 | 4353C67921C6A3F100A824DD = { 647 | CreatedOnToolsVersion = 10.1; 648 | }; 649 | 4353C68D21C6A3F200A824DD = { 650 | CreatedOnToolsVersion = 10.1; 651 | TestTargetID = 4353C67921C6A3F100A824DD; 652 | }; 653 | }; 654 | }; 655 | buildConfigurationList = 4353C67521C6A3F100A824DD /* Build configuration list for PBXProject "CleanArchitecture" */; 656 | compatibilityVersion = "Xcode 9.3"; 657 | developmentRegion = en; 658 | hasScannedForEncodings = 0; 659 | knownRegions = ( 660 | en, 661 | Base, 662 | ); 663 | mainGroup = 4353C67121C6A3F100A824DD; 664 | productRefGroup = 4353C67B21C6A3F100A824DD /* Products */; 665 | projectDirPath = ""; 666 | projectRoot = ""; 667 | targets = ( 668 | 4353C67921C6A3F100A824DD /* CleanArchitecture */, 669 | 4353C68D21C6A3F200A824DD /* CleanArchitectureTests */, 670 | ); 671 | }; 672 | /* End PBXProject section */ 673 | 674 | /* Begin PBXResourcesBuildPhase section */ 675 | 4353C67821C6A3F100A824DD /* Resources */ = { 676 | isa = PBXResourcesBuildPhase; 677 | buildActionMask = 2147483647; 678 | files = ( 679 | 4353C68821C6A3F200A824DD /* LaunchScreen.storyboard in Resources */, 680 | 4353C68521C6A3F200A824DD /* Assets.xcassets in Resources */, 681 | 435A7FB821EF78930014D01F /* TodayViewController.xib in Resources */, 682 | 43EB31652204B45100555451 /* AddTaskViewController.xib in Resources */, 683 | 431B24B42259D56E00AD5C1C /* DetailCell.xib in Resources */, 684 | 435A7FDE21F008C50014D01F /* TaskCell.xib in Resources */, 685 | 435A7FC021EF78AE0014D01F /* SettingsViewController.xib in Resources */, 686 | ); 687 | runOnlyForDeploymentPostprocessing = 0; 688 | }; 689 | 4353C68C21C6A3F200A824DD /* Resources */ = { 690 | isa = PBXResourcesBuildPhase; 691 | buildActionMask = 2147483647; 692 | files = ( 693 | ); 694 | runOnlyForDeploymentPostprocessing = 0; 695 | }; 696 | /* End PBXResourcesBuildPhase section */ 697 | 698 | /* Begin PBXShellScriptBuildPhase section */ 699 | 43682B1821C6A97C00716C2C /* SwiftLint */ = { 700 | isa = PBXShellScriptBuildPhase; 701 | buildActionMask = 2147483647; 702 | files = ( 703 | ); 704 | inputFileListPaths = ( 705 | ); 706 | inputPaths = ( 707 | ); 708 | name = SwiftLint; 709 | outputFileListPaths = ( 710 | ); 711 | outputPaths = ( 712 | ); 713 | runOnlyForDeploymentPostprocessing = 0; 714 | shellPath = /bin/sh; 715 | shellScript = "if [[ -s \"${PODS_ROOT}/SwiftLint/swiftlint\" ]]; then\n\"${PODS_ROOT}/SwiftLint/swiftlint\"\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nexit 1\nfi\n"; 716 | }; 717 | 6B3569297CA1A04067E6407B /* [CP] Check Pods Manifest.lock */ = { 718 | isa = PBXShellScriptBuildPhase; 719 | buildActionMask = 2147483647; 720 | files = ( 721 | ); 722 | inputFileListPaths = ( 723 | ); 724 | inputPaths = ( 725 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 726 | "${PODS_ROOT}/Manifest.lock", 727 | ); 728 | name = "[CP] Check Pods Manifest.lock"; 729 | outputFileListPaths = ( 730 | ); 731 | outputPaths = ( 732 | "$(DERIVED_FILE_DIR)/Pods-CleanArchitectureTests-checkManifestLockResult.txt", 733 | ); 734 | runOnlyForDeploymentPostprocessing = 0; 735 | shellPath = /bin/sh; 736 | 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"; 737 | showEnvVarsInLog = 0; 738 | }; 739 | BBE04EB16FBACA4B7A551FF7 /* [CP] Check Pods Manifest.lock */ = { 740 | isa = PBXShellScriptBuildPhase; 741 | buildActionMask = 2147483647; 742 | files = ( 743 | ); 744 | inputFileListPaths = ( 745 | ); 746 | inputPaths = ( 747 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 748 | "${PODS_ROOT}/Manifest.lock", 749 | ); 750 | name = "[CP] Check Pods Manifest.lock"; 751 | outputFileListPaths = ( 752 | ); 753 | outputPaths = ( 754 | "$(DERIVED_FILE_DIR)/Pods-CleanArchitecture-checkManifestLockResult.txt", 755 | ); 756 | runOnlyForDeploymentPostprocessing = 0; 757 | shellPath = /bin/sh; 758 | 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"; 759 | showEnvVarsInLog = 0; 760 | }; 761 | C30751DB01C62837CDB601DE /* [CP] Embed Pods Frameworks */ = { 762 | isa = PBXShellScriptBuildPhase; 763 | buildActionMask = 2147483647; 764 | files = ( 765 | ); 766 | inputFileListPaths = ( 767 | ); 768 | inputPaths = ( 769 | "${SRCROOT}/Pods/Target Support Files/Pods-CleanArchitecture/Pods-CleanArchitecture-frameworks.sh", 770 | "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework", 771 | "${BUILT_PRODUCTS_DIR}/Realm/Realm.framework", 772 | "${BUILT_PRODUCTS_DIR}/RealmSwift/RealmSwift.framework", 773 | "${BUILT_PRODUCTS_DIR}/RxAlamofire/RxAlamofire.framework", 774 | "${BUILT_PRODUCTS_DIR}/RxAtomic/RxAtomic.framework", 775 | "${BUILT_PRODUCTS_DIR}/RxCocoa/RxCocoa.framework", 776 | "${BUILT_PRODUCTS_DIR}/RxRealm/RxRealm.framework", 777 | "${BUILT_PRODUCTS_DIR}/RxSwift/RxSwift.framework", 778 | "${BUILT_PRODUCTS_DIR}/SVProgressHUD/SVProgressHUD.framework", 779 | ); 780 | name = "[CP] Embed Pods Frameworks"; 781 | outputFileListPaths = ( 782 | ); 783 | outputPaths = ( 784 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework", 785 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Realm.framework", 786 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RealmSwift.framework", 787 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxAlamofire.framework", 788 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxAtomic.framework", 789 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxCocoa.framework", 790 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxRealm.framework", 791 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxSwift.framework", 792 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SVProgressHUD.framework", 793 | ); 794 | runOnlyForDeploymentPostprocessing = 0; 795 | shellPath = /bin/sh; 796 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-CleanArchitecture/Pods-CleanArchitecture-frameworks.sh\"\n"; 797 | showEnvVarsInLog = 0; 798 | }; 799 | D1547D4D567A00BD669994B8 /* [CP] Embed Pods Frameworks */ = { 800 | isa = PBXShellScriptBuildPhase; 801 | buildActionMask = 2147483647; 802 | files = ( 803 | ); 804 | inputFileListPaths = ( 805 | ); 806 | inputPaths = ( 807 | "${SRCROOT}/Pods/Target Support Files/Pods-CleanArchitectureTests/Pods-CleanArchitectureTests-frameworks.sh", 808 | "${BUILT_PRODUCTS_DIR}/RxAtomic/RxAtomic.framework", 809 | "${BUILT_PRODUCTS_DIR}/RxSwift/RxSwift.framework", 810 | "${BUILT_PRODUCTS_DIR}/RxBlocking/RxBlocking.framework", 811 | "${BUILT_PRODUCTS_DIR}/RxTest/RxTest.framework", 812 | ); 813 | name = "[CP] Embed Pods Frameworks"; 814 | outputFileListPaths = ( 815 | ); 816 | outputPaths = ( 817 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxAtomic.framework", 818 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxSwift.framework", 819 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxBlocking.framework", 820 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxTest.framework", 821 | ); 822 | runOnlyForDeploymentPostprocessing = 0; 823 | shellPath = /bin/sh; 824 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-CleanArchitectureTests/Pods-CleanArchitectureTests-frameworks.sh\"\n"; 825 | showEnvVarsInLog = 0; 826 | }; 827 | /* End PBXShellScriptBuildPhase section */ 828 | 829 | /* Begin PBXSourcesBuildPhase section */ 830 | 4353C67621C6A3F100A824DD /* Sources */ = { 831 | isa = PBXSourcesBuildPhase; 832 | buildActionMask = 2147483647; 833 | files = ( 834 | 43C2452421CFDDD000EEE203 /* ModelConvertibleType.swift in Sources */, 835 | 4307934521C9E393003C5BF0 /* Coordinate.swift in Sources */, 836 | 4310635C21EDA78F0088BB14 /* RealmTask.swift in Sources */, 837 | 433BCADC21C7F0000098DCF8 /* TargetType.swift in Sources */, 838 | 43682B1A21C6A9B900716C2C /* Application.swift in Sources */, 839 | 435A7FE921F011530014D01F /* SView.swift in Sources */, 840 | 435A7FE621F00B180014D01F /* ViewModel.swift in Sources */, 841 | 43C2452621CFDDD000EEE203 /* RealmRepresentable.swift in Sources */, 842 | 433BCB0E21C91FAD0098DCF8 /* App.Theme.swift in Sources */, 843 | 43EB31602204A8B600555451 /* UITableViewCell.swift in Sources */, 844 | 433BCAFA21C804FB0098DCF8 /* Dictionary.swift in Sources */, 845 | 43682B3C21C6B38B00716C2C /* App.String.swift in Sources */, 846 | 435A7FB421EF77070014D01F /* TabBarCoordinator.swift in Sources */, 847 | 435A7FB021EF6B610014D01F /* App.Image.swift in Sources */, 848 | 433BCAE921C7F0B50098DCF8 /* DateFormatter.swift in Sources */, 849 | 435A7FBF21EF78AE0014D01F /* SettingsViewController.swift in Sources */, 850 | 433BCADD21C7F0000098DCF8 /* Mapping.swift in Sources */, 851 | 43C2452721CFDDD000EEE203 /* Reactive.swift in Sources */, 852 | 433BCAFC21C808B10098DCF8 /* Sequence.swift in Sources */, 853 | 43682B3921C6B28B00716C2C /* App.swift in Sources */, 854 | 43C2452921CFDDD000EEE203 /* RealmRepository.swift in Sources */, 855 | 433BCAE821C7F0B50098DCF8 /* JSONDecoder.swift in Sources */, 856 | 4307934321C9DA6C003C5BF0 /* Driver.swift in Sources */, 857 | 43C2452521CFDDD000EEE203 /* RealmError.swift in Sources */, 858 | 4307934721C9ECB5003C5BF0 /* RxIndicator.swift in Sources */, 859 | 4307934121C9D80D003C5BF0 /* RxError.swift in Sources */, 860 | 435A7FD421EF92C20014D01F /* SettingsViewModel.swift in Sources */, 861 | 435A7FCB21EF8FEF0014D01F /* TodayViewModel.swift in Sources */, 862 | 431B24AE2259CE5B00AD5C1C /* DetailCellViewModel.swift in Sources */, 863 | 4310635821ED9E940088BB14 /* TaskUseCase.swift in Sources */, 864 | 4310636221EDB8CC0088BB14 /* TabBarController.swift in Sources */, 865 | 43FCC4ED2257CE63009B2375 /* SettingsCoordinator.swift in Sources */, 866 | 43682B1721C6A93F00716C2C /* ViewController.swift in Sources */, 867 | 438085102212EE0A00E7A0FC /* App.Key.swift in Sources */, 868 | 435A7FDD21F008C50014D01F /* TaskCell.swift in Sources */, 869 | 4310636521EDBCF90088BB14 /* UIColor.swift in Sources */, 870 | 435A7FB721EF78930014D01F /* TodayViewController.swift in Sources */, 871 | 435A7FE221F009B10014D01F /* TaskCellViewModel.swift in Sources */, 872 | 439E9DAF22565747007AF95C /* AddTaskCoordinator.swift in Sources */, 873 | 4310635A21EDA5C40088BB14 /* RTask.swift in Sources */, 874 | 435A7FC921EF8D9F0014D01F /* Settings.swift in Sources */, 875 | 439E9DAD2256571D007AF95C /* AddTaskViewModel.swift in Sources */, 876 | 435A7FE021F009530014D01F /* TableViewCell.swift in Sources */, 877 | 433BCADE21C7F0000098DCF8 /* Router.swift in Sources */, 878 | 435A7FE421F00B130014D01F /* View.swift in Sources */, 879 | 4353C67E21C6A3F100A824DD /* AppDelegate.swift in Sources */, 880 | 43C6042022579FFA00792226 /* RxExtension.swift in Sources */, 881 | 435A7FB221EF70BE0014D01F /* RootCoordinator.swift in Sources */, 882 | 43EB31642204B45100555451 /* AddTaskViewController.swift in Sources */, 883 | 43C604222257CB3B00792226 /* Info.swift in Sources */, 884 | 435A7FCD21EF900A0014D01F /* TodayCoordinator.swift in Sources */, 885 | 435A7FC721EF888A0014D01F /* NavigationController.swift in Sources */, 886 | 439809F121CB9E7700E05C14 /* InfoUseCase.swift in Sources */, 887 | 43FCC4E92257CC7A009B2375 /* InfoTarget.swift in Sources */, 888 | 433BCADF21C7F0000098DCF8 /* HTTPTask.swift in Sources */, 889 | 43C2452821CFDDD000EEE203 /* Object.swift in Sources */, 890 | 4310635E21EDB38F0088BB14 /* Date.swift in Sources */, 891 | 43FCC4EB2257CD24009B2375 /* InfoNetwork.swift in Sources */, 892 | 4310635421ED9A6B0088BB14 /* Task.swift in Sources */, 893 | 435A7FC221EF7A770014D01F /* TabBarViewModel.swift in Sources */, 894 | 431B24B32259D56E00AD5C1C /* DetailCell.swift in Sources */, 895 | 433BCAED21C7F2CB0098DCF8 /* NetworkError.swift in Sources */, 896 | ); 897 | runOnlyForDeploymentPostprocessing = 0; 898 | }; 899 | 4353C68A21C6A3F200A824DD /* Sources */ = { 900 | isa = PBXSourcesBuildPhase; 901 | buildActionMask = 2147483647; 902 | files = ( 903 | 4353C69321C6A3F200A824DD /* CleanArchitectureTests.swift in Sources */, 904 | ); 905 | runOnlyForDeploymentPostprocessing = 0; 906 | }; 907 | /* End PBXSourcesBuildPhase section */ 908 | 909 | /* Begin PBXTargetDependency section */ 910 | 4353C69021C6A3F200A824DD /* PBXTargetDependency */ = { 911 | isa = PBXTargetDependency; 912 | target = 4353C67921C6A3F100A824DD /* CleanArchitecture */; 913 | targetProxy = 4353C68F21C6A3F200A824DD /* PBXContainerItemProxy */; 914 | }; 915 | /* End PBXTargetDependency section */ 916 | 917 | /* Begin PBXVariantGroup section */ 918 | 4353C68621C6A3F200A824DD /* LaunchScreen.storyboard */ = { 919 | isa = PBXVariantGroup; 920 | children = ( 921 | 4353C68721C6A3F200A824DD /* Base */, 922 | ); 923 | name = LaunchScreen.storyboard; 924 | sourceTree = ""; 925 | }; 926 | /* End PBXVariantGroup section */ 927 | 928 | /* Begin XCBuildConfiguration section */ 929 | 4353C6A021C6A3F200A824DD /* Debug */ = { 930 | isa = XCBuildConfiguration; 931 | buildSettings = { 932 | ALWAYS_SEARCH_USER_PATHS = NO; 933 | CLANG_ANALYZER_NONNULL = YES; 934 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 935 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 936 | CLANG_CXX_LIBRARY = "libc++"; 937 | CLANG_ENABLE_MODULES = YES; 938 | CLANG_ENABLE_OBJC_ARC = YES; 939 | CLANG_ENABLE_OBJC_WEAK = YES; 940 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 941 | CLANG_WARN_BOOL_CONVERSION = YES; 942 | CLANG_WARN_COMMA = YES; 943 | CLANG_WARN_CONSTANT_CONVERSION = YES; 944 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 945 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 946 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 947 | CLANG_WARN_EMPTY_BODY = YES; 948 | CLANG_WARN_ENUM_CONVERSION = YES; 949 | CLANG_WARN_INFINITE_RECURSION = YES; 950 | CLANG_WARN_INT_CONVERSION = YES; 951 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 952 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 953 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 954 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 955 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 956 | CLANG_WARN_STRICT_PROTOTYPES = YES; 957 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 958 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 959 | CLANG_WARN_UNREACHABLE_CODE = YES; 960 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 961 | CODE_SIGN_IDENTITY = "iPhone Developer"; 962 | COPY_PHASE_STRIP = NO; 963 | DEBUG_INFORMATION_FORMAT = dwarf; 964 | ENABLE_STRICT_OBJC_MSGSEND = YES; 965 | ENABLE_TESTABILITY = YES; 966 | GCC_C_LANGUAGE_STANDARD = gnu11; 967 | GCC_DYNAMIC_NO_PIC = NO; 968 | GCC_NO_COMMON_BLOCKS = YES; 969 | GCC_OPTIMIZATION_LEVEL = 0; 970 | GCC_PREPROCESSOR_DEFINITIONS = ( 971 | "DEBUG=1", 972 | "$(inherited)", 973 | ); 974 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 975 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 976 | GCC_WARN_UNDECLARED_SELECTOR = YES; 977 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 978 | GCC_WARN_UNUSED_FUNCTION = YES; 979 | GCC_WARN_UNUSED_VARIABLE = YES; 980 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 981 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 982 | MTL_FAST_MATH = YES; 983 | ONLY_ACTIVE_ARCH = YES; 984 | SDKROOT = iphoneos; 985 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 986 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 987 | }; 988 | name = Debug; 989 | }; 990 | 4353C6A121C6A3F200A824DD /* Release */ = { 991 | isa = XCBuildConfiguration; 992 | buildSettings = { 993 | ALWAYS_SEARCH_USER_PATHS = NO; 994 | CLANG_ANALYZER_NONNULL = YES; 995 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 996 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 997 | CLANG_CXX_LIBRARY = "libc++"; 998 | CLANG_ENABLE_MODULES = YES; 999 | CLANG_ENABLE_OBJC_ARC = YES; 1000 | CLANG_ENABLE_OBJC_WEAK = YES; 1001 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 1002 | CLANG_WARN_BOOL_CONVERSION = YES; 1003 | CLANG_WARN_COMMA = YES; 1004 | CLANG_WARN_CONSTANT_CONVERSION = YES; 1005 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 1006 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 1007 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 1008 | CLANG_WARN_EMPTY_BODY = YES; 1009 | CLANG_WARN_ENUM_CONVERSION = YES; 1010 | CLANG_WARN_INFINITE_RECURSION = YES; 1011 | CLANG_WARN_INT_CONVERSION = YES; 1012 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 1013 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 1014 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 1015 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 1016 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 1017 | CLANG_WARN_STRICT_PROTOTYPES = YES; 1018 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 1019 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 1020 | CLANG_WARN_UNREACHABLE_CODE = YES; 1021 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 1022 | CODE_SIGN_IDENTITY = "iPhone Developer"; 1023 | COPY_PHASE_STRIP = NO; 1024 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 1025 | ENABLE_NS_ASSERTIONS = NO; 1026 | ENABLE_STRICT_OBJC_MSGSEND = YES; 1027 | GCC_C_LANGUAGE_STANDARD = gnu11; 1028 | GCC_NO_COMMON_BLOCKS = YES; 1029 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 1030 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 1031 | GCC_WARN_UNDECLARED_SELECTOR = YES; 1032 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 1033 | GCC_WARN_UNUSED_FUNCTION = YES; 1034 | GCC_WARN_UNUSED_VARIABLE = YES; 1035 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 1036 | MTL_ENABLE_DEBUG_INFO = NO; 1037 | MTL_FAST_MATH = YES; 1038 | SDKROOT = iphoneos; 1039 | SWIFT_COMPILATION_MODE = wholemodule; 1040 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 1041 | VALIDATE_PRODUCT = YES; 1042 | }; 1043 | name = Release; 1044 | }; 1045 | 4353C6A321C6A3F200A824DD /* Debug */ = { 1046 | isa = XCBuildConfiguration; 1047 | baseConfigurationReference = B6ADDD66ADC02AFCEA8AB468 /* Pods-CleanArchitecture.debug.xcconfig */; 1048 | buildSettings = { 1049 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 1050 | CODE_SIGN_STYLE = Automatic; 1051 | DEVELOPMENT_TEAM = W6VWBYHU5C; 1052 | INFOPLIST_FILE = "$(SRCROOT)/CleanArchitecture/Support/Info.plist"; 1053 | LD_RUNPATH_SEARCH_PATHS = ( 1054 | "$(inherited)", 1055 | "@executable_path/Frameworks", 1056 | ); 1057 | PRODUCT_BUNDLE_IDENTIFIER = me.mlsuho.CleanArchitecture; 1058 | PRODUCT_NAME = "$(TARGET_NAME)"; 1059 | SWIFT_VERSION = 4.2; 1060 | TARGETED_DEVICE_FAMILY = "1,2"; 1061 | }; 1062 | name = Debug; 1063 | }; 1064 | 4353C6A421C6A3F200A824DD /* Release */ = { 1065 | isa = XCBuildConfiguration; 1066 | baseConfigurationReference = A985D34ABB22C9884803DFB1 /* Pods-CleanArchitecture.release.xcconfig */; 1067 | buildSettings = { 1068 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 1069 | CODE_SIGN_STYLE = Automatic; 1070 | DEVELOPMENT_TEAM = W6VWBYHU5C; 1071 | INFOPLIST_FILE = "$(SRCROOT)/CleanArchitecture/Support/Info.plist"; 1072 | LD_RUNPATH_SEARCH_PATHS = ( 1073 | "$(inherited)", 1074 | "@executable_path/Frameworks", 1075 | ); 1076 | PRODUCT_BUNDLE_IDENTIFIER = me.mlsuho.CleanArchitecture; 1077 | PRODUCT_NAME = "$(TARGET_NAME)"; 1078 | SWIFT_VERSION = 4.2; 1079 | TARGETED_DEVICE_FAMILY = "1,2"; 1080 | }; 1081 | name = Release; 1082 | }; 1083 | 4353C6A621C6A3F200A824DD /* Debug */ = { 1084 | isa = XCBuildConfiguration; 1085 | baseConfigurationReference = 920C447378C5817458E5AE75 /* Pods-CleanArchitectureTests.debug.xcconfig */; 1086 | buildSettings = { 1087 | BUNDLE_LOADER = "$(TEST_HOST)"; 1088 | CODE_SIGN_STYLE = Automatic; 1089 | INFOPLIST_FILE = CleanArchitectureTests/Info.plist; 1090 | LD_RUNPATH_SEARCH_PATHS = ( 1091 | "$(inherited)", 1092 | "@executable_path/Frameworks", 1093 | "@loader_path/Frameworks", 1094 | ); 1095 | PRODUCT_BUNDLE_IDENTIFIER = me.mlsuho.CleanArchitectureTests; 1096 | PRODUCT_NAME = "$(TARGET_NAME)"; 1097 | SWIFT_VERSION = 4.2; 1098 | TARGETED_DEVICE_FAMILY = "1,2"; 1099 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CleanArchitecture.app/CleanArchitecture"; 1100 | }; 1101 | name = Debug; 1102 | }; 1103 | 4353C6A721C6A3F200A824DD /* Release */ = { 1104 | isa = XCBuildConfiguration; 1105 | baseConfigurationReference = FDC4F71D3A668C5763ADD435 /* Pods-CleanArchitectureTests.release.xcconfig */; 1106 | buildSettings = { 1107 | BUNDLE_LOADER = "$(TEST_HOST)"; 1108 | CODE_SIGN_STYLE = Automatic; 1109 | INFOPLIST_FILE = CleanArchitectureTests/Info.plist; 1110 | LD_RUNPATH_SEARCH_PATHS = ( 1111 | "$(inherited)", 1112 | "@executable_path/Frameworks", 1113 | "@loader_path/Frameworks", 1114 | ); 1115 | PRODUCT_BUNDLE_IDENTIFIER = me.mlsuho.CleanArchitectureTests; 1116 | PRODUCT_NAME = "$(TARGET_NAME)"; 1117 | SWIFT_VERSION = 4.2; 1118 | TARGETED_DEVICE_FAMILY = "1,2"; 1119 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CleanArchitecture.app/CleanArchitecture"; 1120 | }; 1121 | name = Release; 1122 | }; 1123 | /* End XCBuildConfiguration section */ 1124 | 1125 | /* Begin XCConfigurationList section */ 1126 | 4353C67521C6A3F100A824DD /* Build configuration list for PBXProject "CleanArchitecture" */ = { 1127 | isa = XCConfigurationList; 1128 | buildConfigurations = ( 1129 | 4353C6A021C6A3F200A824DD /* Debug */, 1130 | 4353C6A121C6A3F200A824DD /* Release */, 1131 | ); 1132 | defaultConfigurationIsVisible = 0; 1133 | defaultConfigurationName = Release; 1134 | }; 1135 | 4353C6A221C6A3F200A824DD /* Build configuration list for PBXNativeTarget "CleanArchitecture" */ = { 1136 | isa = XCConfigurationList; 1137 | buildConfigurations = ( 1138 | 4353C6A321C6A3F200A824DD /* Debug */, 1139 | 4353C6A421C6A3F200A824DD /* Release */, 1140 | ); 1141 | defaultConfigurationIsVisible = 0; 1142 | defaultConfigurationName = Release; 1143 | }; 1144 | 4353C6A521C6A3F200A824DD /* Build configuration list for PBXNativeTarget "CleanArchitectureTests" */ = { 1145 | isa = XCConfigurationList; 1146 | buildConfigurations = ( 1147 | 4353C6A621C6A3F200A824DD /* Debug */, 1148 | 4353C6A721C6A3F200A824DD /* Release */, 1149 | ); 1150 | defaultConfigurationIsVisible = 0; 1151 | defaultConfigurationName = Release; 1152 | }; 1153 | /* End XCConfigurationList section */ 1154 | }; 1155 | rootObject = 4353C67221C6A3F100A824DD /* Project object */; 1156 | } 1157 | --------------------------------------------------------------------------------