├── .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 |
--------------------------------------------------------------------------------