├── iOS-MVVM-Template
├── Api
│ ├── Models
│ │ ├── VoidEnvelope.swift
│ │ ├── ErrorCode.swift
│ │ ├── Templates
│ │ │ └── AccessTokenTemplates.swift
│ │ ├── AccessToken.swift
│ │ └── ErrorEnvelope.swift
│ ├── EnvironmentType.swift
│ ├── Lib
│ │ ├── RequestMethod.swift
│ │ └── Route.swift
│ ├── Service.swift
│ ├── MockService.swift
│ ├── Service+RequestHelpers.swift
│ ├── ServerConfig.swift
│ ├── Extensions
│ │ └── URLSession+RequestHelpers.swift
│ └── ServiceType.swift
├── Resources
│ └── Assets.xcassets
│ │ ├── Contents.json
│ │ └── AppIcon.appiconset
│ │ └── Contents.json
├── ViewController.swift
├── Configs
│ ├── Global.swift
│ └── Secrets.swift
├── Library
│ ├── Extensions
│ │ ├── DictionaryExtensions.swift
│ │ ├── OptionalExtensions.swift
│ │ ├── UITableViewExtensions.swift
│ │ ├── UIScreenExtensions.swift
│ │ ├── EncodableExtensions.swift
│ │ ├── UIFontExtensions.swift
│ │ ├── DecodableExtensions.swift
│ │ ├── StringExtensions.swift
│ │ ├── UIAlertControllerExtensions.swift
│ │ ├── ErrorExtensions.swift
│ │ ├── UIViewController+Preparation.swift
│ │ ├── UIColorExtensions.swift
│ │ ├── UIViewExtensions.swift
│ │ └── UIViewControllerExtensions.swift
│ ├── PushRegistrationType.swift
│ ├── App.swift
│ ├── Storyboard.swift
│ ├── Notifications.swift
│ ├── Environment.swift
│ ├── Nib.swift
│ ├── PushRegistration.swift
│ ├── Keyboard.swift
│ └── AppEnvironment.swift
├── DataModels
│ ├── Templates
│ │ └── UserTemplates.swift
│ └── User.swift
├── DataSources
│ └── Base
│ │ ├── ValueCell.swift
│ │ └── ValueCellDataSource.swift
├── Base.lproj
│ ├── Main.storyboard
│ └── LaunchScreen.storyboard
├── Info.plist
├── ViewModels
│ └── AppDelegateViewModel.swift
└── AppDelegate.swift
├── iOS-MVVM-Template.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── xcuserdata
│ └── lebron.xcuserdatad
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
├── xcshareddata
│ └── xcschemes
│ │ ├── iOS-MVVM-Template_Staging.xcscheme
│ │ └── iOS-MVVM-Template.xcscheme
└── project.pbxproj
├── Podfile
├── iOS-MVVM-Template.xcworkspace
├── xcshareddata
│ └── IDEWorkspaceChecks.plist
└── contents.xcworkspacedata
├── .swiftlint.yml
├── iOS-MVVM-TemplateTests
├── Info.plist
├── Api
│ ├── ServiceTests.swift
│ └── ServiceTypeTests.swift
└── DataSources
│ └── ValueCellDataSourceTest.swift
├── Podfile.lock
├── .gitignore
└── README.md
/iOS-MVVM-Template/Api/Models/VoidEnvelope.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | struct VoidEnvelope: Codable {}
4 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Api/EnvironmentType.swift:
--------------------------------------------------------------------------------
1 | enum EnvironmentType {
2 | case production
3 | case staging
4 | }
5 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Api/Models/ErrorCode.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | enum ErrorCode: Int {
4 | case needUpdate = 10011
5 | }
6 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Resources/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Api/Lib/RequestMethod.swift:
--------------------------------------------------------------------------------
1 | enum RequestMethod: String {
2 | case GET
3 | case POST
4 | case PUT
5 | case DELETE
6 | }
7 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Api/Models/Templates/AccessTokenTemplates.swift:
--------------------------------------------------------------------------------
1 | extension AccessToken {
2 | static let template = AccessToken(token: "template")
3 | }
4 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/ViewController.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | class ViewController: UIViewController {
4 |
5 | override func viewDidLoad() {
6 | super.viewDidLoad()
7 | }
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Configs/Global.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | extension UIColor {
4 | static let appBackgroundColor: UIColor = .hex("#202020")
5 | }
6 |
7 | enum Constant {
8 | static let jsonDecoder = JSONDecoder()
9 | }
10 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Library/Extensions/DictionaryExtensions.swift:
--------------------------------------------------------------------------------
1 | extension Dictionary {
2 | func withAllValuesFrom(_ other: Dictionary) -> Dictionary {
3 | var result = self
4 | other.forEach { result[$0] = $1 }
5 | return result
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Library/Extensions/OptionalExtensions.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | extension Optional {
4 | func doIfSome(_ body: (Wrapped) -> Void) {
5 | if let value = self.optional {
6 | body(value)
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Library/Extensions/UITableViewExtensions.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | extension UITableView {
4 | func registerCellClass(_ cellClass: UITableViewCell.Type) {
5 | register(cellClass, forCellReuseIdentifier: cellClass.defaultReusableId)
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/Podfile:
--------------------------------------------------------------------------------
1 | source 'https://github.com/CocoaPods/Specs.git'
2 |
3 | platform :ios, '11.4'
4 | inhibit_all_warnings!
5 |
6 | target 'iOS-MVVM-Template' do
7 | use_frameworks!
8 |
9 | pod 'JWT'
10 | pod 'Lebron-ReactiveExtensions'
11 | pod 'PKHUD'
12 | pod 'ReactiveSwift', '~> 6.0'
13 |
14 | end
15 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Library/Extensions/UIScreenExtensions.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | extension UIScreen {
4 | static var width: CGFloat {
5 | return UIScreen.main.bounds.width
6 | }
7 |
8 | static var height: CGFloat {
9 | return UIScreen.main.bounds.height
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Api/Lib/Route.swift:
--------------------------------------------------------------------------------
1 | enum Route {
2 | case yourRoute
3 |
4 | var requestProperties: (method: RequestMethod, path: String, query: [String: Any]) {
5 | switch self {
6 | case .yourRoute:
7 | return (.GET, "/users/me", [:])
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Library/PushRegistrationType.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import UserNotifications
3 | import ReactiveSwift
4 |
5 | protocol PushRegistrationType {
6 | static func register(for options: UNAuthorizationOptions) -> SignalProducer
7 | static func hasAuthorizedNotifications() -> SignalProducer
8 | }
9 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Library/Extensions/EncodableExtensions.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | private let jsonEncoder = JSONEncoder()
4 |
5 | extension Encodable {
6 | var dictionary: [String: Any] {
7 | let obj = try? JSONSerialization.jsonObject(with: jsonEncoder.encode(self))
8 | return obj as? [String: Any] ?? [:]
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Library/App.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | enum App {
4 | static let versionNumber = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
5 | static let buildNumber = Bundle.main.infoDictionary?["CFBundleVersion"] as? String
6 | static let currentVersionDescription = "\(versionNumber ?? "")(\(buildNumber ?? ""))"
7 | }
8 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Library/Extensions/UIFontExtensions.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | extension UIFont {
4 | enum SFProTextFontStyle: String {
5 | case regular = "SFProText-Regular"
6 | }
7 |
8 | static func SFProText(style: SFProTextFontStyle, size: CGFloat) -> UIFont {
9 | return UIFont(name: style.rawValue, size: size)!
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Library/Extensions/DecodableExtensions.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public extension Decodable {
4 | static func decodeJSONDictionary(_ json: Any) -> Self? {
5 | if let data = try? JSONSerialization.data(withJSONObject: json, options: []),
6 | let value = try? JSONDecoder().decode(Self.self, from: data) {
7 | return value
8 | }
9 | return nil
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Configs/Secrets.swift:
--------------------------------------------------------------------------------
1 | enum Secrets {
2 | static let isMockService = false
3 | static let jwtSecret = "YourSecret"
4 |
5 | enum Api {
6 | // swiftlint:disable nesting
7 | enum Endpoint {
8 | static let production = "https://api.example.com/api"
9 | static let staging = "https://api-staging.example.com/api"
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Library/Storyboard.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | enum Storyboard: String {
4 | case YourStoryboard
5 |
6 | func instantiate(_ viewController: VC.Type) -> VC {
7 | guard let vc = UIStoryboard(name: rawValue, bundle: nil)
8 | .instantiateViewController(withIdentifier: VC.typeName) as? VC else {
9 | fatalError("Couldn't instantiate \(VC.typeName) from \(rawValue)")
10 | }
11 | return vc
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | included:
2 | - ./
3 | excluded: # paths to ignore during linting. Takes precedence over `included`.
4 | - Pods
5 | - Carthage
6 | disabled_rules: # rule identifiers to exclude from running
7 | - type_name
8 | - large_tuple
9 | - todo
10 | - identifier_name
11 | - function_parameter_count
12 | cyclomatic_complexity:
13 | warning: 20
14 | error: 30
15 | function_body_length:
16 | warning: 75
17 | error: 100
18 | file_length:
19 | warning: 800
20 | error: 1000
21 | type_body_length:
22 | warning: 400
23 | error: 500
24 | identifier_name:
25 | excluded: id # id is allowed
26 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Library/Notifications.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | enum CurrentUserNotifications {
4 | static let sessionStarted = "CurrentUserNotifications.sessionStarted"
5 | static let sessionEnded = "CurrentUserNotifications.sessionEnded"
6 | static let currentUserUpdated = "CurrentUserNotifications.sessionUpdated"
7 | }
8 |
9 | extension Notification.Name {
10 | static let sessionStarted = Notification.Name(rawValue: CurrentUserNotifications.sessionStarted)
11 | static let sessionEnded = Notification.Name(rawValue: CurrentUserNotifications.sessionEnded)
12 | static let currentUserUpdated = Notification.Name(rawValue: CurrentUserNotifications.currentUserUpdated)
13 | }
14 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/DataModels/Templates/UserTemplates.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | extension User {
4 | static let template: User = {
5 | do {
6 | let data = Data(userJson.utf8)
7 | let user = try Constant.jsonDecoder.decode(User.self, from: data)
8 | return user
9 | } catch {
10 | fatalError("decode userJson error: \(error.localizedDescription)")
11 | }
12 | }()
13 |
14 | private static let userJson = """
15 | {
16 | "token": "xxxxxxxxxxxxxxxxxxxxx",
17 | "user_id": "xxxxxxxxxxxxxxxxxxxxx",
18 | "username": "lebronjames",
19 | "first_name": "Lebron",
20 | "last_name": "James"
21 | }
22 | """
23 | }
24 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/DataSources/Base/ValueCell.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | protocol ValueCell: AnyObject {
4 | associatedtype Value
5 | static var defaultReusableId: String { get }
6 | func configureWith(value: Value)
7 | }
8 |
9 | extension UITableViewCell {
10 | static var defaultReusableId: String {
11 | return description()
12 | .components(separatedBy: ".")
13 | .dropFirst()
14 | .joined(separator: ".")
15 | }
16 | }
17 |
18 | extension UICollectionViewCell {
19 | static var defaultReusableId: String {
20 | return description()
21 | .components(separatedBy: ".")
22 | .dropFirst()
23 | .joined(separator: ".")
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Library/Extensions/StringExtensions.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | // MARK: - Properties
4 | extension String {
5 | var isEmail: Bool {
6 | let emailRegEx = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"
7 | let emailPred = NSPredicate(format: "SELF MATCHES %@", emailRegEx)
8 | return emailPred.evaluate(with: self)
9 | }
10 | }
11 |
12 | // MARK: - Methods
13 | extension String {
14 | func trim() -> String {
15 | return trimmingCharacters(in: .whitespacesAndNewlines)
16 | }
17 | }
18 |
19 | // MARK: - Optional
20 | extension Optional where Wrapped == String {
21 | var isEmpty: Bool {
22 | if let str = self {
23 | return str.isEmpty
24 | }
25 | return true
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Library/Extensions/UIAlertControllerExtensions.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | extension UIAlertController {
4 | static func alert(
5 | title: String? = nil,
6 | message: String? = nil,
7 | confirm: String? = nil,
8 | handler: ((UIAlertAction) -> Void)? = nil
9 | ) -> UIAlertController {
10 | let alertController = UIAlertController(
11 | title: title,
12 | message: message,
13 | preferredStyle: .alert
14 | )
15 | alertController.addAction(
16 | UIAlertAction(
17 | title: confirm ?? "OK",
18 | style: .cancel,
19 | handler: handler
20 | )
21 | )
22 | return alertController
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Api/Service.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import ReactiveSwift
3 |
4 | struct Service: ServiceType {
5 | let serverConfig: ServerConfigType
6 | let accessToken: AccessTokenType?
7 |
8 | init(serverConfig: ServerConfigType = ServerConfig.production,
9 | accessToken: AccessTokenType? = nil) {
10 | self.serverConfig = serverConfig
11 | self.accessToken = accessToken
12 | }
13 |
14 | func login(accessToken: AccessTokenType) -> Service {
15 | return Service(
16 | serverConfig: serverConfig,
17 | accessToken: accessToken
18 | )
19 | }
20 |
21 | func logout() -> Service {
22 | return Service(
23 | serverConfig: serverConfig,
24 | accessToken: nil
25 | )
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Api/MockService.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import ReactiveSwift
3 |
4 | struct MockService: ServiceType {
5 | let serverConfig: ServerConfigType
6 | let accessToken: AccessTokenType?
7 |
8 | init(serverConfig: ServerConfigType = ServerConfig.production,
9 | accessToken: AccessTokenType? = nil) {
10 | self.serverConfig = serverConfig
11 | self.accessToken = accessToken
12 | }
13 |
14 | func login(accessToken: AccessTokenType) -> MockService {
15 | return MockService(
16 | serverConfig: self.serverConfig,
17 | accessToken: accessToken
18 | )
19 | }
20 |
21 | func logout() -> MockService {
22 | return MockService(
23 | serverConfig: self.serverConfig,
24 | accessToken: nil
25 | )
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/iOS-MVVM-TemplateTests/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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Base64 (1.1.2)
3 | - JWT (2.2.0):
4 | - Base64 (~> 1.1.2)
5 | - Lebron-ReactiveExtensions (0.0.1):
6 | - ReactiveSwift (~> 6.1)
7 | - PKHUD (5.3.0)
8 | - ReactiveSwift (6.1.0)
9 |
10 | DEPENDENCIES:
11 | - JWT
12 | - Lebron-ReactiveExtensions
13 | - PKHUD
14 | - ReactiveSwift (~> 6.0)
15 |
16 | SPEC REPOS:
17 | https://github.com/CocoaPods/Specs.git:
18 | - Base64
19 | - JWT
20 | - Lebron-ReactiveExtensions
21 | - PKHUD
22 | - ReactiveSwift
23 |
24 | SPEC CHECKSUMS:
25 | Base64: cecfb41a004124895a7bcee567a89bae5a89d49b
26 | JWT: cf73c0ab0dac762df3de8a3f1164352089d97780
27 | Lebron-ReactiveExtensions: f915659ef5d898faee0622c33c93bd65d68e4f0b
28 | PKHUD: 98f3e4bc904b9c916f1c5bb6d765365b5357291b
29 | ReactiveSwift: b78c7259f8ca46368238c895ae33a6a3bf9f811c
30 |
31 | PODFILE CHECKSUM: 03f3f5895c230e3e1c1b5b21484fb027f0441411
32 |
33 | COCOAPODS: 1.10.0
34 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template.xcodeproj/xcuserdata/lebron.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | iOS-MVVM-Template.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 6
11 |
12 | iOS-MVVM-Template_Staging.xcscheme_^#shared#^_
13 |
14 | orderHint
15 | 7
16 |
17 |
18 | SuppressBuildableAutocreation
19 |
20 | FDD85AAB23B4BB6B00D7B8DD
21 |
22 | primary
23 |
24 |
25 | FDD85AC123B4BB6D00D7B8DD
26 |
27 | primary
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/iOS-MVVM-TemplateTests/Api/ServiceTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import iOS_MVVM_Template
3 |
4 | final class ServiceTests: XCTestCase {
5 |
6 | func testDefaults() {
7 | XCTAssertTrue(Service().serverConfig == ServerConfig.production)
8 | }
9 |
10 | func testEquals() {
11 | let s1 = Service()
12 | let s2 = Service(serverConfig: ServerConfig.staging)
13 |
14 | XCTAssertTrue(s1 == s1)
15 | XCTAssertTrue(s2 == s2)
16 |
17 | XCTAssertFalse(s1 == s2)
18 | }
19 |
20 | func testLogin() {
21 | let loggedOut = Service()
22 | let loggedIn = loggedOut.login(accessToken: AccessToken.template)
23 |
24 | XCTAssertTrue(loggedIn == Service(accessToken: AccessToken.template))
25 | }
26 |
27 | func testLogout() {
28 | let loggedIn = Service(accessToken: AccessToken.template)
29 | let loggedOut = loggedIn.logout()
30 |
31 | XCTAssertTrue(loggedOut == Service())
32 |
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Api/Models/AccessToken.swift:
--------------------------------------------------------------------------------
1 | import JWT
2 |
3 | protocol AccessTokenType {
4 | var token: String { get }
5 | var jwtToken: String { get }
6 | }
7 |
8 | func == (lhs: AccessTokenType, rhs: AccessTokenType) -> Bool {
9 | return type(of: lhs) == type(of: rhs) &&
10 | lhs.token == rhs.token
11 | }
12 |
13 | func == (lhs: AccessTokenType?, rhs: AccessTokenType?) -> Bool {
14 | return type(of: lhs) == type(of: rhs) &&
15 | lhs?.token == rhs?.token
16 | }
17 |
18 | struct AccessToken: AccessTokenType, Codable {
19 | let token: String
20 |
21 | init(token: String) {
22 | self.token = token
23 | }
24 |
25 | var jwtToken: String {
26 | let algorithm = JWTAlgorithmFactory.algorithm(byName: "HS256")
27 | let payload: [String: Any] = ["token": token]
28 | let jwtToken = JWT.encodePayload(
29 | payload,
30 | withSecret: Secrets.jwtSecret,
31 | algorithm: algorithm
32 | ) ?? ""
33 | return jwtToken
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Library/Extensions/ErrorExtensions.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | extension NSError {
4 | convenience init(code: Int = -1, reason: String) {
5 | let userInfo = [NSLocalizedDescriptionKey: reason,
6 | NSLocalizedFailureReasonErrorKey: reason]
7 | self.init(domain: "", code: code, userInfo: userInfo)
8 | }
9 |
10 | static func error(fromCode code: Int) -> Error {
11 | let userInfo = [
12 | NSLocalizedDescriptionKey: errorReasonString(fromCode: code),
13 | NSLocalizedFailureReasonErrorKey: errorReasonString(fromCode: code)
14 | ]
15 | return NSError(domain: "", code: code, userInfo: userInfo)
16 | }
17 |
18 | private static func errorReasonString(fromCode code: Int) -> String {
19 | var errorString = ""
20 | if code >= 400 {
21 | errorString = "service unavailable"
22 | } else {
23 | errorString = "failed to connect to server"
24 | }
25 | return errorString
26 | }
27 |
28 | static var failedToDecodeJson: Error {
29 | return NSError(reason: "failed to decode json")
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Library/Environment.swift:
--------------------------------------------------------------------------------
1 | import ReactiveSwift
2 |
3 | struct Environment {
4 | let apiService: ServiceType
5 | let currentUser: User?
6 |
7 | /// A scheduler to use for all time-based RAC operators. Default value is
8 | /// `QueueScheduler.mainQueueScheduler`.
9 | let scheduler: DateScheduler
10 |
11 | /// A type that manages registration for push notifications.
12 | let pushRegistrationType: PushRegistrationType.Type
13 |
14 | init(
15 | apiService: ServiceType = Service(),
16 | currentUser: User? = nil,
17 | pushRegistrationType: PushRegistrationType.Type = PushRegistration.self,
18 | scheduler: DateScheduler = QueueScheduler.main
19 | ) {
20 | self.apiService = apiService
21 | self.currentUser = currentUser
22 | self.pushRegistrationType = pushRegistrationType
23 | self.scheduler = scheduler
24 | }
25 |
26 | // MARK: - Getters
27 |
28 | var environmentType: EnvironmentType {
29 | return apiService.serverConfig.environment
30 | }
31 |
32 | var isStaging: Bool {
33 | return apiService.serverConfig.environment == .staging
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Library/Nib.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | enum Nib: String {
4 | case YourCustomView
5 | }
6 |
7 | extension UITableView {
8 | func register(nib: Nib) {
9 | register(UINib(nibName: nib.rawValue, bundle: .main), forCellReuseIdentifier: nib.rawValue)
10 | }
11 |
12 | func registerHeaderFooter(nib: Nib) {
13 | register(UINib(nibName: nib.rawValue, bundle: .main),
14 | forHeaderFooterViewReuseIdentifier: nib.rawValue)
15 | }
16 | }
17 |
18 | protocol NibLoading {
19 | associatedtype CustomNibType
20 |
21 | static func fromNib(nib: Nib) -> CustomNibType?
22 | }
23 |
24 | extension NibLoading {
25 | static func fromNib(nib: Nib) -> Self? {
26 | guard let view = UINib(nibName: nib.rawValue, bundle: .main)
27 | .instantiate(withOwner: self, options: nil)
28 | .first as? Self else {
29 | assertionFailure("Nib not found")
30 | return nil
31 | }
32 |
33 | return view
34 | }
35 |
36 | func view(fromNib nib: Nib) -> UIView? {
37 | return UINib(nibName: nib.rawValue, bundle: .main).instantiate(withOwner: self, options: nil).first
38 | as? UIView
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Api/Service+RequestHelpers.swift:
--------------------------------------------------------------------------------
1 | import ReactiveSwift
2 |
3 | extension Service {
4 | private static let session = URLSession(configuration: .default)
5 |
6 | func request(_ route: Route) -> SignalProducer {
7 | let properties = route.requestProperties
8 | let url = serverConfig.apiBaseUrl
9 | .appendingPathComponent(properties.path)
10 | let request = preparedRequest(
11 | forURL: url,
12 | method: properties.method,
13 | query: properties.query
14 | )
15 | return Service.session.rac_dataResponse(request)
16 | .flatMap(decodeModel)
17 | }
18 |
19 | // MARK: - Private
20 |
21 | private func decodeModel(data: Data) -> SignalProducer {
22 | return SignalProducer(value: data)
23 | .flatMap { (data) -> SignalProducer in
24 | do {
25 | let model = try Constant.jsonDecoder.decode(T.self, from: data)
26 | return .init(value: model)
27 | } catch {
28 | return .init(error: .couldNotDecodeJSON(error))
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Api/ServerConfig.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | protocol ServerConfigType {
4 | var apiBaseUrl: URL { get }
5 | var environment: EnvironmentType { get }
6 | }
7 |
8 | func == (lhs: ServerConfigType, rhs: ServerConfigType) -> Bool {
9 | return type(of: lhs) == type(of: rhs) &&
10 | lhs.apiBaseUrl == rhs.apiBaseUrl &&
11 | lhs.environment == rhs.environment
12 | }
13 |
14 | struct ServerConfig: ServerConfigType {
15 | private(set) var apiBaseUrl: URL
16 | private(set) var environment: EnvironmentType
17 |
18 | static let production: ServerConfig = ServerConfig(
19 | apiBaseUrl: URL(string: Secrets.Api.Endpoint.production)!,
20 | environment: .production
21 | )
22 |
23 | static let staging: ServerConfig = ServerConfig(
24 | apiBaseUrl: URL(string: Secrets.Api.Endpoint.staging)!,
25 | environment: .staging
26 | )
27 |
28 | init(apiBaseUrl: URL,
29 | environment: EnvironmentType = .production) {
30 | self.apiBaseUrl = apiBaseUrl
31 | self.environment = environment
32 | }
33 |
34 | static func config(for environment: EnvironmentType) -> ServerConfigType {
35 | switch environment {
36 | case .production:
37 | return ServerConfig.production
38 | case .staging:
39 | return ServerConfig.staging
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Library/Extensions/UIViewController+Preparation.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | private func swizzle(_ vc: UIViewController.Type) {
4 | [(#selector(vc.viewDidLoad), #selector(vc.custom_ViewDidLoad))]
5 | .forEach { (original, swizzled) in
6 | guard let originalMethod = class_getInstanceMethod(vc, original),
7 | let swizzledMethod = class_getInstanceMethod(vc, swizzled) else {
8 | return
9 | }
10 |
11 | let didAddViewDidLoadMethod = class_addMethod(
12 | vc,
13 | original,
14 | method_getImplementation(swizzledMethod),
15 | method_getTypeEncoding(swizzledMethod)
16 | )
17 | if didAddViewDidLoadMethod {
18 | class_replaceMethod(
19 | vc,
20 | swizzled, method_getImplementation(originalMethod),
21 | method_getTypeEncoding(originalMethod)
22 | )
23 | } else {
24 | method_exchangeImplementations(originalMethod, swizzledMethod)
25 | }
26 | }
27 | }
28 |
29 | private var hasSwizzled = false
30 |
31 | extension UIViewController {
32 | static func doBadSwizzleStuff() {
33 | guard !hasSwizzled else { return }
34 |
35 | hasSwizzled = true
36 | swizzle(self)
37 | }
38 |
39 | @objc internal func custom_ViewDidLoad() {
40 | custom_ViewDidLoad()
41 | bindViewModel()
42 | }
43 |
44 | /// The entry point to bind all view model outputs. Called just before `viewDidLoad`.
45 | @objc func bindViewModel() {
46 |
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Base.lproj/Main.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 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/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 |
--------------------------------------------------------------------------------
/.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 | *.DS_Store
25 |
26 | ## Obj-C/Swift specific
27 | *.hmap
28 | *.ipa
29 | *.dSYM.zip
30 | *.dSYM
31 |
32 | ## Playgrounds
33 | timeline.xctimeline
34 | playground.xcworkspace
35 |
36 | # Swift Package Manager
37 | #
38 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
39 | # Packages/
40 | # Package.pins
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 | Carthage/Checkout
58 | Carthage/
59 | Carthage/Cachefile
60 |
61 | # fastlane
62 | #
63 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
64 | # screenshots whenever they are needed.
65 | # For more information about the recommended setup visit:
66 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
67 |
68 | fastlane/report.xml
69 | fastlane/Preview.html
70 | fastlane/screenshots
71 | fastlane/test_output
72 | Gemfile
73 | Gemfile.lock
74 | fastlane/Pluginfile
75 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Library/PushRegistration.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import UserNotifications
3 | import ReactiveSwift
4 |
5 | struct PushRegistration: PushRegistrationType {
6 |
7 | /// Returns a signal producer that emits an option `Bool` value representing whether or not the user
8 | /// granted the requested push notification permissions. This value is not returned on iOS versions < 10.0.
9 | /// The returned producer emits once and completes.
10 | /// - Parameter options: The types to register that we will request permissions for.
11 | /// - returns: A signal producer.
12 | static func register(for options: UNAuthorizationOptions) -> SignalProducer {
13 | return SignalProducer { observer, _ in
14 | UNUserNotificationCenter.current()
15 | .requestAuthorization(options: options, completionHandler: { isGranted, _ in
16 | if isGranted {
17 | DispatchQueue.main.async {
18 | UIApplication.shared.registerForRemoteNotifications()
19 | }
20 | }
21 |
22 | observer.send(value: isGranted)
23 | observer.sendCompleted()
24 | })
25 | }
26 | }
27 |
28 | /// Returns a signal producer that emits a `Bool` value representing whether the user has allowed push
29 | /// notification permissions in the past.
30 | /// The returned producer emits once and completes
31 | /// - returns: A signal producer.
32 | static func hasAuthorizedNotifications() -> SignalProducer {
33 | return SignalProducer { observer, _ in
34 | UNUserNotificationCenter.current().getNotificationSettings { settings in
35 | observer.send(value: settings.authorizationStatus == .authorized)
36 | observer.sendCompleted()
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Library/Extensions/UIColorExtensions.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | extension UIColor {
4 | static func RGB(
5 | _ r: CGFloat,
6 | _ g: CGFloat,
7 | _ b: CGFloat,
8 | _ alpha: CGFloat = 1) -> UIColor {
9 | return UIColor(
10 | displayP3Red: r / 255,
11 | green: g / 255,
12 | blue: b / 255,
13 | alpha: alpha
14 | )
15 | }
16 |
17 | static func random() -> UIColor {
18 | let red: CGFloat = CGFloat(drand48())
19 | let green: CGFloat = CGFloat(drand48())
20 | let blue: CGFloat = CGFloat(drand48())
21 | return UIColor(
22 | red: red,
23 | green: green,
24 | blue: blue,
25 | alpha: 1.0
26 | )
27 | }
28 |
29 | static func hex(_ hexStr: String) -> UIColor {
30 | var cString: String = hexStr.trimmingCharacters(in: .whitespacesAndNewlines).uppercased()
31 |
32 | if cString.hasPrefix("#") {
33 | cString.remove(at: cString.startIndex)
34 | }
35 |
36 | if cString.count != 6 {
37 | fatalError("hexStr must be similar to `#2F2F2F` or `2F2F2F`")
38 | }
39 |
40 | var rgbValue: UInt32 = 0
41 | Scanner(string: cString).scanHexInt32(&rgbValue)
42 |
43 | return UIColor(
44 | red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0,
45 | green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0,
46 | blue: CGFloat(rgbValue & 0x0000FF) / 255.0,
47 | alpha: CGFloat(1.0)
48 | )
49 | }
50 |
51 | func toImage() -> UIImage? {
52 | let rect = CGRect(x: 0, y: 0, width: 1, height: 1)
53 | UIGraphicsBeginImageContextWithOptions(rect.size, false, 0)
54 | setFill()
55 | UIRectFill(rect)
56 | let image = UIGraphicsGetImageFromCurrentImageContext()
57 | UIGraphicsEndImageContext()
58 | return image
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Api/Models/ErrorEnvelope.swift:
--------------------------------------------------------------------------------
1 | struct ErrorEnvelopeResult: Codable {
2 | let error: ErrorEnvelope
3 | }
4 |
5 | struct ErrorEnvelope: Error {
6 | let code: Int
7 | let reason: String
8 | let detail: String
9 | let moreInfo: String
10 |
11 | // MARK: - Overrides
12 |
13 | var localizedDescription: String {
14 | return reason
15 | }
16 | }
17 |
18 | // MARK: Codable
19 | extension ErrorEnvelope: Codable {
20 | init(from decoder: Decoder) throws {
21 | let values = try decoder.container(keyedBy: CodingKeys.self)
22 | code = try values.decode(Int.self, forKey: .code)
23 | reason = try values.decode(String.self, forKey: .reason)
24 | detail = try values.decode(String.self, forKey: .detail)
25 | moreInfo = try values.decode(String.self, forKey: .moreInfo)
26 | }
27 |
28 | enum CodingKeys: String, CodingKey {
29 | case code
30 | case reason
31 | case detail
32 | case moreInfo = "more_info"
33 | }
34 | }
35 |
36 | // MARK: - Errors
37 | extension ErrorEnvelope {
38 |
39 | static let couldNotParseErrorEnvelopeJSON = ErrorEnvelope(
40 | code: 400,
41 | reason: "could not parse error envelope json",
42 | detail: "could not parse error envelope json",
43 | moreInfo: ""
44 | )
45 |
46 | static let couldNotParseJSON = ErrorEnvelope(
47 | code: 400,
48 | reason: "could not parse json",
49 | detail: "could not parse json",
50 | moreInfo: ""
51 | )
52 |
53 | static let internalServerError = ErrorEnvelope(
54 | code: 500,
55 | reason: "Internal Server Error",
56 | detail: "Internal Server Error",
57 | moreInfo: ""
58 | )
59 |
60 | static func couldNotDecodeJSON(_ error: Error) -> ErrorEnvelope {
61 | return ErrorEnvelope(
62 | code: 400,
63 | reason: error.localizedDescription,
64 | detail: error.localizedDescription,
65 | moreInfo: ""
66 | )
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Resources/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 | }
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Library/Extensions/UIViewExtensions.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | extension UIView {
4 |
5 | // MARK: - Constraints
6 |
7 | func constraintEdges(to superview: UIView, useSafeArea: Bool = true) {
8 | translatesAutoresizingMaskIntoConstraints = false
9 | if #available(iOS 11.0, *), useSafeArea {
10 | NSLayoutConstraint.activate([
11 | topAnchor.constraint(equalTo: superview.safeAreaLayoutGuide.topAnchor),
12 | bottomAnchor.constraint(equalTo: superview.safeAreaLayoutGuide.bottomAnchor),
13 | leadingAnchor.constraint(equalTo: superview.safeAreaLayoutGuide.leadingAnchor),
14 | trailingAnchor.constraint(equalTo: superview.safeAreaLayoutGuide.trailingAnchor)
15 | ])
16 | } else {
17 | NSLayoutConstraint.activate([
18 | topAnchor.constraint(equalTo: superview.topAnchor),
19 | bottomAnchor.constraint(equalTo: superview.bottomAnchor),
20 | leadingAnchor.constraint(equalTo: superview.leadingAnchor),
21 | trailingAnchor.constraint(equalTo: superview.trailingAnchor)
22 | ])
23 | }
24 | }
25 |
26 | func setSubviewsTranslatingMasksToConstraints(to value: Bool, _ except: UIView? = nil) {
27 | subviews.forEach { (subview) in
28 | if subview === except {
29 | return
30 | }
31 | subview.translatesAutoresizingMaskIntoConstraints = value
32 | if subview.subviews.count > 0 {
33 | subview.setSubviewsTranslatingMasksToConstraints(to: value, except)
34 | }
35 | }
36 | }
37 |
38 | // MARK: - Corner
39 |
40 | func roundCorners(corners: UIRectCorner, radius: CGFloat) {
41 | let path = UIBezierPath(
42 | roundedRect: bounds,
43 | byRoundingCorners: corners,
44 | cornerRadii: CGSize(width: radius, height: radius)
45 | )
46 | let mask = CAShapeLayer()
47 | mask.path = path.cgPath
48 | layer.mask = mask
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/DataModels/User.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | struct User: Codable {
4 | let id: String
5 | let email: String
6 | let username: String?
7 | let firstName: String?
8 | let lastName: String?
9 | let avatar: String?
10 | let token: String?
11 |
12 | init(from decoder: Decoder) throws {
13 | let values = try decoder.container(keyedBy: CodingKeys.self)
14 | id = try values.decode(String.self, forKey: .id)
15 | email = try values.decode(String.self, forKey: .email)
16 | username = try values.decodeIfPresent(String.self, forKey: .username)
17 | firstName = try values.decodeIfPresent(String.self, forKey: .firstName)
18 | lastName = try values.decodeIfPresent(String.self, forKey: .lastName)
19 | avatar = try values.decodeIfPresent(String.self, forKey: .avatar)
20 | token = try values.decodeIfPresent(String.self, forKey: .token)
21 | }
22 |
23 | enum CodingKeys: String, CodingKey {
24 | case id = "user_id"
25 | case email
26 | case username
27 | case firstName = "first_name"
28 | case lastName = "last_name"
29 | case avatar
30 | case token
31 | }
32 | }
33 |
34 | // MARK: Encode & Decode
35 | extension User {
36 | func encode() -> [String: Any] {
37 | var result: [String: Any] = [:]
38 |
39 | result["user_id"] = id
40 | result["email"] = email
41 | result["username"] = username
42 | result["first_name"] = firstName
43 | result["last_name"] = lastName
44 | result["avatar"] = avatar
45 | result["token"] = token
46 |
47 | return result
48 | }
49 |
50 | static func decode(from json: [String: Any]?) -> User? {
51 | do {
52 | guard let json = json else { return nil }
53 | let data = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)
54 | return try Constant.jsonDecoder.decode(User.self, from: data)
55 | } catch {
56 | print("Decode user error: \(error.localizedDescription)")
57 | return nil
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Library/Keyboard.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import ReactiveSwift
3 | import UIKit
4 |
5 | final class Keyboard {
6 | enum KeyboardStatus {
7 | case show
8 | case hide
9 |
10 | var isHidden: Bool {
11 | return self == .hide
12 | }
13 | }
14 |
15 | typealias Change = (
16 | status: KeyboardStatus,
17 | frame: CGRect,
18 | duration: TimeInterval,
19 | options: UIView.AnimationOptions,
20 | notificationName: Notification.Name
21 | )
22 |
23 | static let shared = Keyboard()
24 | private let (changeSignal, changeObserver) = Signal.pipe()
25 |
26 | static var change: Signal {
27 | return shared.changeSignal
28 | }
29 |
30 | private init() {
31 | NotificationCenter.default.addObserver(
32 | self,
33 | selector: #selector(change(_:)),
34 | name: UIResponder.keyboardWillShowNotification,
35 | object: nil
36 | )
37 | NotificationCenter.default.addObserver(
38 | self,
39 | selector: #selector(change(_:)),
40 | name: UIResponder.keyboardWillHideNotification,
41 | object: nil
42 | )
43 | }
44 |
45 | @objc private func change(_ notification: Notification) {
46 | guard let userInfo = notification.userInfo,
47 | let frame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue,
48 | let duration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber,
49 | let curveNumber = userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber,
50 | let curve = UIView.AnimationCurve(rawValue: curveNumber.intValue)
51 | else {
52 | return
53 | }
54 | let isHidden = frame.cgRectValue.origin.y == UIScreen.main.bounds.height
55 | changeObserver.send(value: (
56 | status: isHidden ? .hide : .show,
57 | frame.cgRectValue,
58 | duration.doubleValue,
59 | UIView.AnimationOptions(rawValue: UInt(curve.rawValue)),
60 | notificationName: notification.name
61 | ))
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IS_STAGING
6 | $(IS_STAGING)
7 | CFBundleDevelopmentRegion
8 | $(DEVELOPMENT_LANGUAGE)
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UIApplicationSceneManifest
26 |
27 | UIApplicationSupportsMultipleScenes
28 |
29 | UISceneConfigurations
30 |
31 | UIWindowSceneSessionRoleApplication
32 |
33 |
34 | UISceneConfigurationName
35 | Default Configuration
36 | UISceneDelegateClassName
37 | $(PRODUCT_MODULE_NAME).SceneDelegate
38 | UISceneStoryboardFile
39 | Main
40 |
41 |
42 |
43 |
44 | UILaunchStoryboardName
45 | LaunchScreen
46 | UIMainStoryboardFile
47 | Main
48 | UIRequiredDeviceCapabilities
49 |
50 | armv7
51 |
52 | UISupportedInterfaceOrientations
53 |
54 | UIInterfaceOrientationPortrait
55 | UIInterfaceOrientationLandscapeLeft
56 | UIInterfaceOrientationLandscapeRight
57 |
58 | UISupportedInterfaceOrientations~ipad
59 |
60 | UIInterfaceOrientationPortrait
61 | UIInterfaceOrientationPortraitUpsideDown
62 | UIInterfaceOrientationLandscapeLeft
63 | UIInterfaceOrientationLandscapeRight
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/iOS-MVVM-TemplateTests/Api/ServiceTypeTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import iOS_MVVM_Template
3 |
4 | final class ServiceTypeTests: XCTestCase {
5 |
6 | let service = Service(
7 | serverConfig: ServerConfig(apiBaseUrl: URL(string: "http://api.example.com")!),
8 | accessToken: AccessToken.template
9 | )
10 |
11 | func testEquals() {
12 | XCTAssertTrue(Service() != MockService())
13 | }
14 |
15 | func testPreparedRequest() {
16 | let url = URL(string: "http://api.example.com/v1/test?key=value")!
17 | let request = service.preparedRequest(forRequest: .init(url: url))
18 |
19 | XCTAssertEqual(["Authorization": "Bearer \(AccessToken.template.jwtToken)"], request.allHTTPHeaderFields)
20 | }
21 |
22 | func testPreparedGetURL() {
23 | let url = URL(string: "http://api.example.com/v1/test?key=value")!
24 | let request = service.preparedRequest(forURL: url, query: ["extra": "1"])
25 |
26 | XCTAssertEqual("http://api.example.com/v1/test?extra=1&key=value", request.url?.absoluteString)
27 | XCTAssertEqual("GET", request.httpMethod)
28 | XCTAssertEqual(["Authorization": "Bearer \(AccessToken.template.jwtToken)"], request.allHTTPHeaderFields)
29 | }
30 |
31 | func testPreparedDeleteURL() {
32 | let url = URL(string: "http://api.example.com/v1/test?key=value")!
33 | let request = service.preparedRequest(forURL: url, method: .DELETE, query: ["extra": "1"])
34 |
35 | XCTAssertEqual("http://api.example.com/v1/test?extra=1&key=value", request.url?.absoluteString)
36 | XCTAssertEqual("DELETE", request.httpMethod)
37 | XCTAssertEqual(["Authorization": "Bearer \(AccessToken.template.jwtToken)"], request.allHTTPHeaderFields)
38 | }
39 |
40 | func testPreparedPostURL() {
41 | let url = URL(string: "http://api.example.com/v1/test?key=value")!
42 | let request = service.preparedRequest(forURL: url, method: .POST, query: ["extra": "1"])
43 |
44 | XCTAssertEqual("http://api.example.com/v1/test?key=value", request.url?.absoluteString)
45 | XCTAssertEqual("POST", request.httpMethod)
46 | XCTAssertEqual([
47 | "Authorization": "Bearer \(AccessToken.template.jwtToken)",
48 | "Content-Type": "application/json; charset=utf-8"
49 | ], request.allHTTPHeaderFields)
50 | XCTAssertEqual("{\"extra\":\"1\"}", String(data: request.httpBody ?? Data(), encoding: .utf8))
51 | }
52 |
53 | func testPreparedPutURL() {
54 | let url = URL(string: "http://api.example.com/v1/test?key=value")!
55 | let request = service.preparedRequest(forURL: url, method: .PUT, query: ["extra": "1"])
56 |
57 | XCTAssertEqual("http://api.example.com/v1/test?key=value", request.url?.absoluteString)
58 | XCTAssertEqual("PUT", request.httpMethod)
59 | XCTAssertEqual([
60 | "Authorization": "Bearer \(AccessToken.template.jwtToken)",
61 | "Content-Type": "application/json; charset=utf-8"
62 | ], request.allHTTPHeaderFields)
63 | XCTAssertEqual("{\"extra\":\"1\"}", String(data: request.httpBody ?? Data(), encoding: .utf8))
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Library/Extensions/UIViewControllerExtensions.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import PKHUD
3 |
4 | extension UIViewController {
5 |
6 | // MARK: - Type Name
7 |
8 | static var typeName: String {
9 | return String(describing: self)
10 | }
11 |
12 | // MARK: - Hide Keyboard
13 |
14 | func hideKeyboardWhenTappedAround(cancelTouches: Bool = false) {
15 | let tap = UITapGestureRecognizer(
16 | target: self,
17 | action: #selector(viewDidTapped)
18 | )
19 | tap.cancelsTouchesInView = cancelTouches
20 | view.addGestureRecognizer(tap)
21 | }
22 |
23 | @objc func viewDidTapped() {
24 | view.endEditing(true)
25 | }
26 |
27 | // MARK: - Navigation Items
28 |
29 | var backButtonItem: UIBarButtonItem {
30 | let item = UIBarButtonItem(
31 | image: UIImage(named: "left-arrow"),
32 | style: .plain,
33 | target: self,
34 | action: #selector(backButtonItemTapped)
35 | )
36 | return item
37 | }
38 |
39 | @objc private func backButtonItemTapped() {
40 | navigationController?.popViewController(animated: true)
41 | }
42 |
43 | var closeButtonItem: UIBarButtonItem {
44 | let item = UIBarButtonItem(
45 | image: UIImage(named: "close"),
46 | style: .plain,
47 | target: self,
48 | action: #selector(closeButtonItemTapped)
49 | )
50 | return item
51 | }
52 |
53 | @objc func closeButtonItemTapped() {
54 | dismiss(animated: true, completion: nil)
55 | }
56 |
57 | // MARK: - HUD
58 |
59 | func hideHUD() {
60 | DispatchQueue.main.async {
61 | HUD.hide()
62 | }
63 | }
64 |
65 | func showProgress(title: String?, subtitle: String? = nil) {
66 | DispatchQueue.main.async {
67 | HUD.show(.labeledProgress(title: title, subtitle: subtitle))
68 | }
69 | }
70 |
71 | func showLoading() {
72 | DispatchQueue.main.async {
73 | HUD.show(.progress)
74 | }
75 | }
76 |
77 | // MARK: - Alert
78 |
79 | func alertUser(
80 | title: String = "",
81 | message: String,
82 | confirm: String = "Close",
83 | handler: ((UIAlertAction) -> Void)? = nil
84 | ) {
85 | let alert = UIAlertController.alert(
86 | title: title,
87 | message: message,
88 | confirm: confirm,
89 | handler: handler
90 | )
91 | customPresent(alert, animated: true)
92 | }
93 | }
94 |
95 | extension UIViewController {
96 | func customPresent(
97 | _ viewControllerToPresent: UIViewController,
98 | animated flag: Bool,
99 | completion: (() -> Void)? = nil
100 | ) {
101 | if let presented = presentedViewController {
102 | presented.customPresent(
103 | viewControllerToPresent,
104 | animated: flag,
105 | completion: completion
106 | )
107 | } else {
108 | present(viewControllerToPresent, animated: flag, completion: completion)
109 | }
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template.xcodeproj/xcshareddata/xcschemes/iOS-MVVM-Template_Staging.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
45 |
51 |
52 |
53 |
54 |
60 |
62 |
68 |
69 |
70 |
71 |
73 |
74 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/iOS-MVVM-TemplateTests/DataSources/ValueCellDataSourceTest.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | import UIKit
3 | @testable import iOS_MVVM_Template
4 |
5 | final class IntTableCell: UITableViewCell, ValueCell {
6 | var value: Int = 0
7 | func configureWith(value: Int) {
8 | self.value = value
9 | }
10 | }
11 |
12 | final class IntDataSource: ValueCellDataSource {
13 | override func registerClasses(tableView: UITableView?) {
14 | tableView?.registerCellClass(IntTableCell.self)
15 | }
16 | }
17 |
18 | final class ValueCellDataSourceTest: XCTestCase {
19 | private let dataSource = IntDataSource()
20 | private let tableView = UITableView()
21 |
22 | override func setUp() {
23 | super.setUp()
24 |
25 | dataSource.registerClasses(tableView: tableView)
26 |
27 | dataSource.appendRow(value: 1, cellClass: IntTableCell.self, toSection: 0)
28 | dataSource.appendRow(value: 2, cellClass: IntTableCell.self, toSection: 0)
29 | dataSource.set(values: [1, 2, 3], cellClass: IntTableCell.self, inSection: 2)
30 | }
31 |
32 | func testTableViewDataSourceMethods() {
33 | XCTAssertEqual(3, dataSource.numberOfSections(in: tableView))
34 | XCTAssertEqual(2, dataSource.tableView(tableView, numberOfRowsInSection: 0))
35 | XCTAssertEqual(0, dataSource.tableView(tableView, numberOfRowsInSection: 1))
36 | XCTAssertEqual(3, dataSource.tableView(tableView, numberOfRowsInSection: 2))
37 | }
38 |
39 | func testClearValues() {
40 | dataSource.clearValues()
41 | XCTAssertEqual(0, dataSource.numberOfItems())
42 | }
43 |
44 | func testClearValuesInSection() {
45 | dataSource.clearValues(inSection: 0)
46 | XCTAssertEqual(3, dataSource.numberOfSections(in: tableView))
47 | XCTAssertEqual(0, dataSource.tableView(tableView, numberOfRowsInSection: 0))
48 | XCTAssertEqual(0, dataSource.tableView(tableView, numberOfRowsInSection: 1))
49 | XCTAssertEqual(3, dataSource.tableView(tableView, numberOfRowsInSection: 2))
50 | }
51 |
52 | func testMoveItem() {
53 | dataSource.moveItem(
54 | value: 3,
55 | cellClass: IntTableCell.self,
56 | at: 2,
57 | to: 0,
58 | inSection: 2
59 | )
60 | // swiftlint:disable force_cast
61 | XCTAssertTrue(dataSource[IndexPath(item: 0, section: 2)] as! Int == 3)
62 |
63 | dataSource.moveItem(
64 | value: 3,
65 | cellClass: IntTableCell.self,
66 | at: 0,
67 | to: 1,
68 | inSection: 2
69 | )
70 | // swiftlint:disable force_cast
71 | XCTAssertTrue(dataSource[IndexPath(item: 1, section: 2)] as! Int == 3)
72 |
73 | dataSource.moveItem(
74 | value: 3,
75 | cellClass: IntTableCell.self,
76 | at: 1,
77 | to: 2,
78 | inSection: 2
79 | )
80 | // swiftlint:disable force_cast
81 | XCTAssertTrue(dataSource[IndexPath(item: 2, section: 2)] as! Int == 3)
82 | }
83 |
84 | func testSetValue() {
85 | XCTAssertTrue(dataSource[IndexPath(item: 1, section: 0)] as! Int == 2)
86 | dataSource.set(value: 3, cellClass: IntTableCell.self, atIndex: 1, inSection: 0)
87 | XCTAssertTrue(dataSource[IndexPath(item: 1, section: 0)] as! Int == 3)
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/ViewModels/AppDelegateViewModel.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import ReactiveSwift
3 | import Lebron_ReactiveExtensions
4 |
5 | protocol AppDelegateViewModelInputs {
6 | /// Call when the application finishes launching.
7 | func applicationDidFinishLaunching(
8 | application: UIApplication?,
9 | launchOptions: [UIApplication.LaunchOptionsKey: Any]?
10 | )
11 |
12 | /// Call when the application did become active.
13 | func applicationDidBecomeActive()
14 |
15 | /// Call when the application will enter foreground.
16 | func applicationWillEnterForeground()
17 |
18 | /// Call when receive remote notificaiton
19 | func didReceiveRemoteNotification(_ notification: UNNotification)
20 | }
21 |
22 | protocol AppDelegateViewModelOutputs {
23 | /// Emits when the push token registration request begins.
24 | var pushTokenRegistrationRequestStarted: Signal<(), Never> { get }
25 |
26 | /// Emits the push token that has been successfully generated.
27 | var pushTokenSuccessfullyGenerated: Signal { get }
28 | }
29 |
30 | protocol AppDelegateViewModelType {
31 | var inputs: AppDelegateViewModelInputs { get }
32 | var outputs: AppDelegateViewModelOutputs { get }
33 | }
34 |
35 | final class AppDelegateViewModel: AppDelegateViewModelType, AppDelegateViewModelInputs, AppDelegateViewModelOutputs {
36 |
37 | init() {
38 | let pushTokenRegistrationRequestStartedEvents = applicationLaunchOptionsProperty.signal.ignoreValues()
39 | .flatMap {
40 | AppEnvironment.current.pushRegistrationType
41 | .register(for: [.alert, .badge, .sound])
42 | .materialize()
43 | }
44 |
45 | pushTokenRegistrationRequestStarted = pushTokenRegistrationRequestStartedEvents.values().ignoreValues()
46 |
47 | pushTokenSuccessfullyGenerated = deviceTokenStringProperty.signal.skipNil()
48 | }
49 |
50 | private typealias ApplicationWithOptions = (
51 | application: UIApplication?,
52 | options: [UIApplication.LaunchOptionsKey: Any]?
53 | )
54 | private let applicationLaunchOptionsProperty = MutableProperty(nil)
55 | func applicationDidFinishLaunching(
56 | application: UIApplication?,
57 | launchOptions: [UIApplication.LaunchOptionsKey: Any]?
58 | ) {
59 | applicationLaunchOptionsProperty.value = (application, launchOptions)
60 | }
61 |
62 | private let applicationDidBecomeActiveProperty = MutableProperty(())
63 | func applicationDidBecomeActive() {
64 | applicationDidBecomeActiveProperty.value = ()
65 | }
66 |
67 | private let applicationWillEnterForegroundProperty = MutableProperty(())
68 | func applicationWillEnterForeground() {
69 | applicationWillEnterForegroundProperty.value = ()
70 | }
71 |
72 | let deviceTokenStringProperty = MutableProperty(nil)
73 | func didRegisterForRemoteNotifications(withFcmToken token: String) {
74 | deviceTokenStringProperty.value = token
75 | }
76 |
77 | func didReceiveRemoteNotification(_ notification: UNNotification) {
78 |
79 | }
80 |
81 | let pushTokenRegistrationRequestStarted: Signal<(), Never>
82 | let pushTokenSuccessfullyGenerated: Signal
83 |
84 | var inputs: AppDelegateViewModelInputs { return self }
85 | var outputs: AppDelegateViewModelOutputs { return self }
86 | }
87 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Api/Extensions/URLSession+RequestHelpers.swift:
--------------------------------------------------------------------------------
1 | import ReactiveSwift
2 |
3 | private let scheduler = QueueScheduler(
4 | qos: .background,
5 | name: "com.example.api",
6 | targeting: nil
7 | )
8 |
9 | extension URLSession {
10 |
11 | func rac_dataResponse(_ request: URLRequest) -> SignalProducer {
12 |
13 | let producer = reactive.data(with: request)
14 |
15 | print("⚪️ [Api] Starting request \(self.sanitized(request))")
16 |
17 | return producer
18 | .start(on: scheduler)
19 | .flatMapError { _ in SignalProducer(error: .couldNotParseErrorEnvelopeJSON) }
20 | .flatMap { (data, response) -> SignalProducer in
21 |
22 | if AppEnvironment.current.isStaging {
23 | print("json response: \(String(data: data, encoding: .utf8) ?? "")")
24 | }
25 |
26 | guard let res = response as? HTTPURLResponse else {
27 | fatalError()
28 | }
29 |
30 | guard (200..<300).contains(res.statusCode),
31 | let headers = res.allHeaderFields as? [String: String],
32 | let contentType = headers["Content-Type"], contentType.hasPrefix("application/json") else {
33 |
34 | if res.statusCode == 500 {
35 | return .init(error: .internalServerError)
36 | }
37 |
38 | do {
39 | let error = try Constant.jsonDecoder.decode(ErrorEnvelope.self, from: data)
40 | print("🔴 [Api] Failure \(self.sanitized(request)) \n Error - \(error)")
41 |
42 | return .init(error: error)
43 | } catch {
44 | print("🔴 [Api] Failure \(self.sanitized(request)) \n decoding error - \(error)")
45 | return .init(error: .couldNotDecodeJSON(error))
46 | }
47 | }
48 |
49 | if let errorResult = try? Constant.jsonDecoder.decode(ErrorEnvelopeResult.self, from: data) {
50 | let error = errorResult.error
51 | print("🔴 [Api] Failure \(self.sanitized(request)) \n Error - \(error)")
52 | return .init(error: error)
53 | }
54 |
55 | print("🔵 [Api] Success \(self.sanitized(request))")
56 | return .init(value: data)
57 | }
58 | }
59 |
60 | // MARK: - Private
61 |
62 | // swiftlint:disable force_try
63 | private static let sanitationRules = [
64 | "access_token=[REDACTED]":
65 | try! NSRegularExpression(pattern: "access_token=([a-zA-Z0-9]*)", options: .caseInsensitive)
66 | ]
67 |
68 | private func sanitized(_ request: URLRequest) -> String {
69 | guard let urlString = request.url?.absoluteString else {
70 | return ""
71 | }
72 |
73 | return URLSession.sanitationRules.reduce(urlString) { accum, templateAndRule in
74 | let (template, rule) = templateAndRule
75 | let range = NSRange(location: 0, length: accum.count)
76 | return rule.stringByReplacingMatches(
77 | in: accum,
78 | options: .withTransparentBounds,
79 | range: range,
80 | withTemplate: template
81 | )
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Api/ServiceType.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import ReactiveSwift
3 |
4 | protocol ServiceType {
5 | var serverConfig: ServerConfigType { get }
6 | var accessToken: AccessTokenType? { get }
7 |
8 | init(serverConfig: ServerConfigType, accessToken: AccessTokenType?)
9 |
10 | func login(accessToken: AccessTokenType) -> Self
11 |
12 | func logout() -> Self
13 | }
14 |
15 | func == (lhs: ServiceType, rhs: ServiceType) -> Bool {
16 | return type(of: lhs) == type(of: rhs) &&
17 | lhs.serverConfig == rhs.serverConfig &&
18 | lhs.accessToken == rhs.accessToken
19 | }
20 |
21 | func != (lhs: ServiceType, rhs: ServiceType) -> Bool {
22 | return !(lhs == rhs)
23 | }
24 |
25 | // MARK: - Prepare Request
26 | extension ServiceType {
27 | func preparedRequest(forURL url: URL,
28 | method: RequestMethod = .GET,
29 | query: [String: Any] = [:]) -> URLRequest {
30 |
31 | var request = URLRequest(url: url)
32 | request.httpMethod = method.rawValue
33 | return preparedRequest(forRequest: request, query: query)
34 | }
35 |
36 | func preparedRequest(forRequest originalRequest: URLRequest,
37 | query: [String: Any] = [:]) -> URLRequest {
38 |
39 | var request = originalRequest
40 | guard let url = request.url else {
41 | return originalRequest
42 | }
43 |
44 | var headers = defaultHeaders
45 |
46 | let method = request.httpMethod?.uppercased()
47 | var components = URLComponents(url: url, resolvingAgainstBaseURL: false)!
48 | var queryItems = components.queryItems ?? []
49 |
50 | if method == "POST" || method == "PUT" {
51 | if request.httpBody == nil {
52 | headers["Content-Type"] = "application/json; charset=utf-8"
53 | request.httpBody = try? JSONSerialization.data(withJSONObject: query, options: [])
54 | }
55 | } else {
56 | queryItems.append(
57 | contentsOf: query
58 | .flatMap(queryComponents)
59 | .map(URLQueryItem.init(name:value:))
60 | )
61 | }
62 | components.queryItems = queryItems.sorted { $0.name < $1.name }
63 | request.url = components.url
64 |
65 | let currentHeaders = request.allHTTPHeaderFields ?? [:]
66 | request.allHTTPHeaderFields = currentHeaders.withAllValuesFrom(headers)
67 |
68 | return request
69 | }
70 |
71 | private func queryComponents(_ key: String, _ value: Any) -> [(String, String)] {
72 | var components: [(String, String)] = []
73 |
74 | if let dictionary = value as? [String: Any] {
75 | for (nestedKey, value) in dictionary {
76 | components += queryComponents("\(key)[\(nestedKey)]", value)
77 | }
78 | } else if let array = value as? [Any] {
79 | for value in array {
80 | components += queryComponents("\(key)[]", value)
81 | }
82 | } else {
83 | components.append((key, String(describing: value)))
84 | }
85 |
86 | return components
87 | }
88 |
89 | private var defaultHeaders: [String: String] {
90 | var headers: [String: String] = [:]
91 | headers["Authorization"] = authorizationHeader
92 | return headers
93 | }
94 |
95 | private var authorizationHeader: String? {
96 | if let token = accessToken?.jwtToken {
97 | return "Bearer \(token)"
98 | }
99 | return nil
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template.xcodeproj/xcshareddata/xcschemes/iOS-MVVM-Template.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
53 |
55 |
61 |
62 |
63 |
64 |
70 |
72 |
78 |
79 |
80 |
81 |
83 |
84 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | @UIApplicationMain
4 | class AppDelegate: UIResponder, UIApplicationDelegate {
5 |
6 | var window: UIWindow?
7 |
8 | private let viewModel: AppDelegateViewModelType = AppDelegateViewModel()
9 |
10 | func application(
11 | _ application: UIApplication,
12 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
13 | ) -> Bool {
14 |
15 | let restoredEnv = AppEnvironment.environmentFormStorage()
16 | AppEnvironment.replaceCurrentEnvironment(restoredEnv)
17 |
18 | #if DEBUG
19 | if Secrets.isMockService {
20 | AppEnvironment.replaceCurrentEnvironment(apiService: MockService())
21 | }
22 | #endif
23 |
24 | UIViewController.doBadSwizzleStuff()
25 | UNUserNotificationCenter.current().delegate = self
26 |
27 | bindViewModel()
28 | viewModel.inputs.applicationDidFinishLaunching(
29 | application: application,
30 | launchOptions: launchOptions
31 | )
32 |
33 | return true
34 | }
35 |
36 | func applicationWillEnterForeground(_ application: UIApplication) {
37 | viewModel.inputs.applicationWillEnterForeground()
38 | }
39 |
40 | func applicationDidBecomeActive(_ application: UIApplication) {
41 | viewModel.inputs.applicationDidBecomeActive()
42 | }
43 |
44 | // MARK: - Remote Notification
45 |
46 | func application(
47 | _: UIApplication,
48 | didFailToRegisterForRemoteNotificationsWithError error: Error
49 | ) {
50 | print("🔴 Failed to register for remote notifications: \(error.localizedDescription)")
51 | }
52 |
53 | }
54 |
55 | // MARK: - Bind View Model
56 | extension AppDelegate {
57 | private func bindViewModel() {
58 | viewModel.outputs.pushTokenRegistrationRequestStarted
59 | .observeForUI()
60 | .observeValues {
61 | print("📲 [Push Registration] Push token registration started 🚀")
62 | }
63 |
64 | viewModel.outputs.pushTokenSuccessfullyGenerated
65 | .observeForUI()
66 | .observeValues { token in
67 | print("📲 [Push Registration] Push token successfully generated (\(token)) ✨")
68 | }
69 | }
70 | }
71 |
72 | // MARK: - UNUserNotificationCenterDelegate
73 | extension AppDelegate: UNUserNotificationCenterDelegate {
74 | func userNotificationCenter(
75 | _: UNUserNotificationCenter,
76 | willPresent notification: UNNotification,
77 | withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
78 | ) {
79 | completionHandler([.alert, .sound])
80 | }
81 |
82 | func userNotificationCenter(
83 | _: UNUserNotificationCenter,
84 | didReceive response: UNNotificationResponse,
85 | withCompletionHandler completion: @escaping () -> Void
86 | ) {
87 | viewModel.inputs.didReceiveRemoteNotification(response.notification)
88 | completion()
89 | }
90 | }
91 |
92 | // MARK: - Style App
93 | extension AppDelegate {
94 | private func styleApp() {
95 | let navigationBar = UINavigationBar
96 | .appearance(whenContainedInInstancesOf: [UINavigationController.self])
97 | navigationBar.barTintColor = .appBackgroundColor
98 | navigationBar.isTranslucent = false
99 | let titleTextAttributes: [NSAttributedString.Key: Any] = [
100 | .foregroundColor: UIColor.white as Any,
101 | .font: UIFont.SFProText(style: .regular, size: 18) as Any
102 | ]
103 | navigationBar.titleTextAttributes = titleTextAttributes
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # iOS-MVVM-Template
2 |
3 | 本项目是参考 [kickstarter/ios-oss](https://github.com/kickstarter/ios-oss) 编写的一个 iOS 模板项目,目的是为了快速创建新的项目。这里感谢 Kickstarter 的开源,让我们学习到了如此优秀的 iOS 项目结构。
4 |
5 | 本项目是根据我自己的项目需求、对 Kickstarter 源码中一些不必要的功能进行精简,并且在一个实际项目中应用后整理出的一个模板。如果想要详细了解 Kickstarter 的源码,请自己去仔细阅读。本人之前在阅读 Kickstarter 项目源码时,也整理了一份笔记,有需要的同学也可以去看下。[Kickstarter-iOS 源码分析 - 文集 - 简书](https://www.jianshu.com/nb/36807985)
6 |
7 | ## 目录结构
8 |
9 | ```
10 | iOS-MVVM-Template
11 | ├── Api : 存放网络请求相关的文件
12 | │ ├── Models
13 | │ │ ├── AccessToken.swift : AccessToken 模型
14 | │ │ ├── ErrorCode.swift:错误代码
15 | │ │ ├── ErrorEnvelope.swift : 后台返回的 Error 数据模型
16 | │ │ ├── Templates : 存放数据模型的模板
17 | │ │ └── VoidEnvelope.swift : 后台返回空 json `{}` 数据时的数据模型
18 | │ ├── Extensions
19 | │ │ └── URLSession+RequestHelpers.swift : 使用 ReactiveSwift 对 URLSession 的扩展方法
20 | │ ├── Lib
21 | │ │ ├── RequestMethod.swift : 请求方法
22 | │ │ └── Route.swift : 所有的网络请求
23 | │ ├── EnvironmentType.swift : 应用环境类型
24 | │ ├── MockService.swift : 模拟的 Service
25 | │ ├── ServerConfig.swift : 服务器配置的数据模型
26 | │ ├── Service+RequestHelpers.swift : 对 Service 的扩展,发送网络请求和解析数据的方法
27 | │ ├── Service.swift : Service 类型,负责整个应用的网络请求
28 | │ └── ServiceType.swift : ServiceType 协议,Service 和 MockService 都遵循这个协议
29 | ├── Configs
30 | │ ├── Global.swift : 存放全局的常量
31 | │ └── Secrets.swift : 存放应用的一些配置
32 | ├── DataModels : 存放数据模型
33 | │ ├── Templates : 存放数据模型的模板
34 | │ └── User.swift : User 数据模型
35 | ├── DataSources : 存放 DataSource
36 | │ └── Base
37 | │ ├── ValueCell.swift : ValueCell 协议。想要使用 ValueCellDataSource 作为 UITableView 和 UICollectionView 的数据源,他们对应的 Cell 都需要实现这个协议
38 | │ └── ValueCellDataSource.swift : 实现了 UITableViewDataSource 和 UICollectionViewDataSource,只整理了一些常用的数据处理的方法,有其他需求的可以继续往这里添加
39 | ├── Library : 存放在整个应用中比较通用的类型
40 | │ ├── Extensions : 存放对类型的扩展
41 | │ ├── App.swift : App 的相关信息
42 | │ ├── AppEnvironment.swift : 管理应用环境
43 | │ ├── Environment.swift : 应用环境的数据模型
44 | │ ├── Keyboard.swift : 使用 ReactiveSwift 对键盘的弹出和隐藏进行处理
45 | │ ├── Nib.swift : 方便初始化使用 xib 自定义的 View
46 | │ ├── Notifications.swift : 管理应用中用到的通知类型
47 | │ ├── PushRegistration.swift : 使用 ReactiveSwift 对 Push 通知的注册进行处理,实现了 PushRegistrationType 协议
48 | │ ├── PushRegistrationType.swift : PushRegistrationType 协议
49 | │ └── Storyboard.swift : 方便初始化使用 Storyboard 自定义的 ViewController
50 | ├── Resources : 存放应用中用到的字体、图标等文件
51 | ├── ViewModels : 存放 View Model
52 | └── Views&Controllers : 存放 View 和 Controller
53 | ```
54 |
55 | ## 使用到的第三方库
56 |
57 | - [ReactiveSwift](https://github.com/ReactiveCocoa/ReactiveSwift)
58 | - [ReactiveExtensions](https://github.com/Lebron1992/Kickstarter-ReactiveExtensions)
59 | - [PKHUD](https://github.com/pkluz/PKHUD)
60 | - [JWT](https://github.com/yourkarma/JWT)
61 |
62 | ## License
63 |
64 | ```
65 | MIT License
66 |
67 | Copyright (c) 2019 Lebron
68 |
69 | Permission is hereby granted, free of charge, to any person obtaining a copy
70 | of this software and associated documentation files (the “Software”), to deal
71 | in the Software without restriction, including without limitation the rights
72 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
73 | copies of the Software, and to permit persons to whom the Software is
74 | furnished to do so, subject to the following conditions:
75 |
76 | The above copyright notice and this permission notice shall be included in all
77 | copies or substantial portions of the Software.
78 |
79 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
80 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
81 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
82 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
83 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
84 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
85 | SOFTWARE.
86 | ```
87 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/Library/AppEnvironment.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | struct AppEnvironment {
4 |
5 | private static var stack: [Environment] = {
6 | let isStaging: Bool = {
7 | if let isStagingStr = Bundle.main.infoDictionary?["IS_STAGING"] as? String {
8 | return isStagingStr == "YES"
9 | }
10 | return false
11 | }()
12 | let serverConfig: ServerConfig = isStaging ? .staging : .production
13 | let service = Service(serverConfig: serverConfig)
14 | let env = Environment(apiService: service)
15 | return [env]
16 | }()
17 |
18 | static var current: Environment! {
19 | return stack.last
20 | }
21 |
22 | // MARK: - Manage User Sessions
23 |
24 | static func login(token: String, user: User) {
25 | let accessToken = AccessToken(token: token)
26 | replaceCurrentEnvironment(
27 | apiService: current.apiService.login(accessToken: accessToken),
28 | currentUser: user
29 | )
30 | }
31 |
32 | static func logout() {
33 | replaceCurrentEnvironment(
34 | apiService: current.apiService.logout(),
35 | currentUser: nil
36 | )
37 | }
38 |
39 | static func updateCurrentUser(_ user: User) {
40 | replaceCurrentEnvironment(
41 | currentUser: user
42 | )
43 | }
44 |
45 | static var isUserLogin: Bool {
46 | return current.currentUser != nil
47 | }
48 |
49 | // MARK: - Manage Environments
50 |
51 | static func pushEnvironment(_ env: Environment) {
52 | saveEnvironment(env)
53 | stack.append(env)
54 | }
55 |
56 | static func popEnvironment() -> Environment? {
57 | let last = stack.popLast()
58 | let next = current ?? Environment()
59 | saveEnvironment(next)
60 | return last
61 | }
62 |
63 | static func replaceCurrentEnvironment(_ env: Environment) {
64 | pushEnvironment(env)
65 | stack.remove(at: stack.count - 2)
66 | }
67 |
68 | static func replaceCurrentEnvironment(
69 | apiService: ServiceType = AppEnvironment.current.apiService,
70 | currentUser: User? = AppEnvironment.current.currentUser) {
71 | replaceCurrentEnvironment(
72 | Environment(
73 | apiService: apiService,
74 | currentUser: currentUser
75 | )
76 | )
77 | }
78 |
79 | static func pushEnvironment(
80 | apiService: ServiceType = AppEnvironment.current.apiService,
81 | currentUser: User? = AppEnvironment.current.currentUser) {
82 | pushEnvironment(
83 | Environment(
84 | apiService: apiService,
85 | currentUser: currentUser
86 | )
87 | )
88 | }
89 |
90 | // MARK: Save & Restore
91 |
92 | static let environmentStorageKey = "com.example.AppEnvironment.current"
93 |
94 | static func saveEnvironment(_ env: Environment = AppEnvironment.current) {
95 |
96 | var data: [String: Any] = [:]
97 |
98 | data["apiService.accessToken.token"] = env.apiService.accessToken?.token
99 | data["currentUser"] = env.currentUser?.encode()
100 |
101 | UserDefaults.standard.set(data, forKey: environmentStorageKey)
102 | UserDefaults.standard.synchronize()
103 | }
104 |
105 | static func environmentFormStorage() -> Environment {
106 | let data = UserDefaults.standard.dictionary(forKey: environmentStorageKey) ?? [:]
107 |
108 | var service = current.apiService
109 | var currentUser: User?
110 |
111 | if let token = data["apiService.accessToken.token"] as? String {
112 | service = service.login(accessToken: AccessToken(token: token))
113 | }
114 |
115 | if service.accessToken != nil {
116 | currentUser = User.decode(from: data["currentUser"] as? [String: Any])
117 | }
118 |
119 | return Environment(
120 | apiService: service,
121 | currentUser: currentUser
122 | )
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template/DataSources/Base/ValueCellDataSource.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | class ValueCellDataSource: NSObject {
4 |
5 | private(set) var values: [[(value: Any, reusableId: String)]] = []
6 |
7 | // MARK: - Methods to be Overridden
8 |
9 | func registerClasses(tableView: UITableView?) {
10 | }
11 |
12 | func configureTableCell(_ cell: UITableViewCell, with value: Any) {
13 | }
14 |
15 | func registerClasses(collectionView: UICollectionView?) {
16 | }
17 |
18 | func configureCollectionCell(_ cell: UICollectionViewCell, with value: Any) {
19 | }
20 |
21 | // MARK: - Public Methods
22 |
23 | final func prependRow(
24 | value: Value,
25 | cellClass: Cell.Type,
26 | toSection section: Int
27 | ) where Cell.Value == Value {
28 |
29 | padValues(forSection: section)
30 | values[section].insert((value, cellClass.defaultReusableId), at: 0)
31 | }
32 |
33 | final func appendRow
34 | (value: Value,
35 | cellClass: Cell.Type,
36 | toSection section: Int)
37 | where Cell.Value == Value {
38 |
39 | padValues(forSection: section)
40 | values[section].append((value, cellClass.defaultReusableId))
41 | }
42 |
43 | final func deleteRow(
44 | value: Value,
45 | cellClass: Cell.Type,
46 | atIndex index: Int,
47 | inSection section: Int
48 | ) where Cell.Value == Value {
49 |
50 | padValues(forSection: section)
51 | values[section].remove(at: index)
52 | }
53 |
54 | final func set
55 | (values: [Value],
56 | cellClass: Cell.Type,
57 | inSection section: Int)
58 | where Cell.Value == Value {
59 |
60 | padValues(forSection: section)
61 | self.values[section] = values.map { ($0, cellClass.defaultReusableId) }
62 | }
63 |
64 | final func set
65 | (value: Value,
66 | cellClass _: Cell.Type,
67 | atIndex index: Int,
68 | inSection section: Int
69 | ) where Cell.Value == Value {
70 | values[section][index] = (value, Cell.defaultReusableId)
71 | }
72 |
73 | final func moveItem(
74 | value _: Value,
75 | cellClass _: Cell.Type,
76 | at oldIndex: Int,
77 | to newIndex: Int,
78 | inSection section: Int
79 | ) where Cell.Value == Value {
80 | var newSection = values[section]
81 | let removed = newSection.remove(at: oldIndex)
82 | newSection.insert(removed, at: newIndex)
83 | values[section] = newSection
84 | }
85 |
86 | final func clearValues() {
87 | values = [[]]
88 | }
89 |
90 | final func clearValues(inSection section: Int) {
91 | padValues(forSection: section)
92 | values[section] = []
93 | }
94 |
95 | final func numberOfItems() -> Int {
96 | return values.reduce(0) { accum, section in accum + section.count }
97 | }
98 |
99 | final subscript(indexPath: IndexPath) -> Any {
100 | return values[indexPath.section][indexPath.item].value
101 | }
102 |
103 | // MARK: - Private Methods
104 |
105 | private func padValues(forSection section: Int) {
106 | guard values.count <= section else { return }
107 |
108 | (values.count...section).forEach { _ in
109 | self.values.append([])
110 | }
111 | }
112 | }
113 |
114 | // MARK: - UITableViewDataSource
115 | extension ValueCellDataSource: UITableViewDataSource {
116 |
117 | final func numberOfSections(in tableView: UITableView) -> Int {
118 | return values.count
119 | }
120 |
121 | final func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
122 | return values[section].count
123 | }
124 |
125 | final func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
126 | let (value, reusableId) = values[indexPath.section][indexPath.row]
127 | let cell = tableView.dequeueReusableCell(
128 | withIdentifier: reusableId,
129 | for: indexPath
130 | )
131 | configureTableCell(cell, with: value)
132 | return cell
133 | }
134 | }
135 |
136 | // MARK: - UICollectionViewDataSource
137 | extension ValueCellDataSource: UICollectionViewDataSource {
138 | final func numberOfSections(in collectionView: UICollectionView) -> Int {
139 | return values.count
140 | }
141 |
142 | final func collectionView(_ collectionView: UICollectionView,
143 | numberOfItemsInSection section: Int) -> Int {
144 | return values[section].count
145 | }
146 |
147 | final func collectionView(_ collectionView: UICollectionView,
148 | cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
149 | let (value, reusableId) = values[indexPath.section][indexPath.row]
150 | let cell = collectionView.dequeueReusableCell(
151 | withReuseIdentifier: reusableId,
152 | for: indexPath
153 | )
154 | configureCollectionCell(cell, with: value)
155 | return cell
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/iOS-MVVM-Template.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 51;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | AD8FA66F098FC2C2B5DD72AB /* Pods_iOS_MVVM_Template.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62F501160D9CF3394B0C531D /* Pods_iOS_MVVM_Template.framework */; };
11 | FD7D4EB4249DBF5C00246661 /* ErrorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7D4EB3249DBF5C00246661 /* ErrorExtensions.swift */; };
12 | FD7E543426677F0800ABFE61 /* EncodableExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7E543326677F0800ABFE61 /* EncodableExtensions.swift */; };
13 | FD7E543626677F1D00ABFE61 /* DecodableExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7E543526677F1D00ABFE61 /* DecodableExtensions.swift */; };
14 | FDD85AB023B4BB6B00D7B8DD /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85AAF23B4BB6B00D7B8DD /* AppDelegate.swift */; };
15 | FDD85AB423B4BB6B00D7B8DD /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85AB323B4BB6B00D7B8DD /* ViewController.swift */; };
16 | FDD85AB723B4BB6B00D7B8DD /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FDD85AB523B4BB6B00D7B8DD /* Main.storyboard */; };
17 | FDD85AB923B4BB6C00D7B8DD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FDD85AB823B4BB6C00D7B8DD /* Assets.xcassets */; };
18 | FDD85ABC23B4BB6C00D7B8DD /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FDD85ABA23B4BB6C00D7B8DD /* LaunchScreen.storyboard */; };
19 | FDD85AD623B4F2BD00D7B8DD /* Secrets.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85AD423B4F2BC00D7B8DD /* Secrets.swift */; };
20 | FDD85AD723B4F2BD00D7B8DD /* Global.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85AD523B4F2BC00D7B8DD /* Global.swift */; };
21 | FDD85AE623B4F77600D7B8DD /* OptionalExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85ADA23B4F77500D7B8DD /* OptionalExtensions.swift */; };
22 | FDD85AE723B4F77600D7B8DD /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85ADB23B4F77500D7B8DD /* StringExtensions.swift */; };
23 | FDD85AE823B4F77600D7B8DD /* UIColorExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85ADC23B4F77500D7B8DD /* UIColorExtensions.swift */; };
24 | FDD85AE923B4F77600D7B8DD /* UIViewControllerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85ADD23B4F77500D7B8DD /* UIViewControllerExtensions.swift */; };
25 | FDD85AEA23B4F77600D7B8DD /* UIScreenExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85ADE23B4F77500D7B8DD /* UIScreenExtensions.swift */; };
26 | FDD85AEB23B4F77600D7B8DD /* UIFontExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85ADF23B4F77600D7B8DD /* UIFontExtensions.swift */; };
27 | FDD85AEE23B4F77600D7B8DD /* DictionaryExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85AE223B4F77600D7B8DD /* DictionaryExtensions.swift */; };
28 | FDD85AEF23B4F77600D7B8DD /* UIViewController+Preparation.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85AE323B4F77600D7B8DD /* UIViewController+Preparation.swift */; };
29 | FDD85AF023B4F77600D7B8DD /* UIViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85AE423B4F77600D7B8DD /* UIViewExtensions.swift */; };
30 | FDD85AF123B4F77600D7B8DD /* UIAlertControllerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85AE523B4F77600D7B8DD /* UIAlertControllerExtensions.swift */; };
31 | FDD85AFB23B5985D00D7B8DD /* Keyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85AF223B5985D00D7B8DD /* Keyboard.swift */; };
32 | FDD85AFC23B5985D00D7B8DD /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85AF323B5985D00D7B8DD /* Notifications.swift */; };
33 | FDD85AFD23B5985D00D7B8DD /* Storyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85AF423B5985D00D7B8DD /* Storyboard.swift */; };
34 | FDD85AFE23B5985D00D7B8DD /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85AF523B5985D00D7B8DD /* App.swift */; };
35 | FDD85AFF23B5985D00D7B8DD /* PushRegistrationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85AF623B5985D00D7B8DD /* PushRegistrationType.swift */; };
36 | FDD85B0023B5985D00D7B8DD /* AppEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85AF723B5985D00D7B8DD /* AppEnvironment.swift */; };
37 | FDD85B0123B5985D00D7B8DD /* Nib.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85AF823B5985D00D7B8DD /* Nib.swift */; };
38 | FDD85B0223B5985D00D7B8DD /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85AF923B5985D00D7B8DD /* Environment.swift */; };
39 | FDD85B0323B5985D00D7B8DD /* PushRegistration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85AFA23B5985D00D7B8DD /* PushRegistration.swift */; };
40 | FDD85B2323B8C49000D7B8DD /* ServiceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85B1123B8C49000D7B8DD /* ServiceType.swift */; };
41 | FDD85B2423B8C49000D7B8DD /* EnvironmentType.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85B1223B8C49000D7B8DD /* EnvironmentType.swift */; };
42 | FDD85B2523B8C49000D7B8DD /* Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85B1323B8C49000D7B8DD /* Service.swift */; };
43 | FDD85B2623B8C49000D7B8DD /* Service+RequestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85B1423B8C49000D7B8DD /* Service+RequestHelpers.swift */; };
44 | FDD85B2723B8C49000D7B8DD /* VoidEnvelope.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85B1623B8C49000D7B8DD /* VoidEnvelope.swift */; };
45 | FDD85B2823B8C49000D7B8DD /* AccessToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85B1723B8C49000D7B8DD /* AccessToken.swift */; };
46 | FDD85B2923B8C49000D7B8DD /* ErrorCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85B1823B8C49000D7B8DD /* ErrorCode.swift */; };
47 | FDD85B2A23B8C49000D7B8DD /* AccessTokenTemplates.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85B1A23B8C49000D7B8DD /* AccessTokenTemplates.swift */; };
48 | FDD85B2B23B8C49000D7B8DD /* ErrorEnvelope.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85B1B23B8C49000D7B8DD /* ErrorEnvelope.swift */; };
49 | FDD85B2C23B8C49000D7B8DD /* URLSession+RequestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85B1D23B8C49000D7B8DD /* URLSession+RequestHelpers.swift */; };
50 | FDD85B2D23B8C49000D7B8DD /* ServerConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85B1E23B8C49000D7B8DD /* ServerConfig.swift */; };
51 | FDD85B2E23B8C49000D7B8DD /* RequestMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85B2023B8C49000D7B8DD /* RequestMethod.swift */; };
52 | FDD85B2F23B8C49000D7B8DD /* Route.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85B2123B8C49000D7B8DD /* Route.swift */; };
53 | FDD85B3023B8C49000D7B8DD /* MockService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85B2223B8C49000D7B8DD /* MockService.swift */; };
54 | FDD85B3923B8CF4A00D7B8DD /* ValueCellDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85B3523B8CF4A00D7B8DD /* ValueCellDataSource.swift */; };
55 | FDD85B3A23B8CF4A00D7B8DD /* ValueCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85B3623B8CF4A00D7B8DD /* ValueCell.swift */; };
56 | FDD85B4923B8CFD600D7B8DD /* AppDelegateViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85B4023B8CFD600D7B8DD /* AppDelegateViewModel.swift */; };
57 | FDD85B5B23B8D13B00D7B8DD /* ValueCellDataSourceTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85B5423B8D13A00D7B8DD /* ValueCellDataSourceTest.swift */; };
58 | FDD85B5C23B8D13B00D7B8DD /* ServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85B5623B8D13A00D7B8DD /* ServiceTests.swift */; };
59 | FDD85B5D23B8D13B00D7B8DD /* ServiceTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85B5723B8D13A00D7B8DD /* ServiceTypeTests.swift */; };
60 | FDD85B8023B8DD0600D7B8DD /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85B6F23B8DD0500D7B8DD /* User.swift */; };
61 | FDD85B8623B8DD0600D7B8DD /* UserTemplates.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85B7623B8DD0500D7B8DD /* UserTemplates.swift */; };
62 | FDD85B8E23B8E44000D7B8DD /* UITableViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDD85B8D23B8E44000D7B8DD /* UITableViewExtensions.swift */; };
63 | /* End PBXBuildFile section */
64 |
65 | /* Begin PBXContainerItemProxy section */
66 | FDD85AC323B4BB6D00D7B8DD /* PBXContainerItemProxy */ = {
67 | isa = PBXContainerItemProxy;
68 | containerPortal = FDD85AA423B4BB6B00D7B8DD /* Project object */;
69 | proxyType = 1;
70 | remoteGlobalIDString = FDD85AAB23B4BB6B00D7B8DD;
71 | remoteInfo = "iOS-MVVM-Template";
72 | };
73 | /* End PBXContainerItemProxy section */
74 |
75 | /* Begin PBXFileReference section */
76 | 344DE535F1B529B9C384CB87 /* Pods-iOS-MVVM-Template.debug staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-MVVM-Template.debug staging.xcconfig"; path = "Target Support Files/Pods-iOS-MVVM-Template/Pods-iOS-MVVM-Template.debug staging.xcconfig"; sourceTree = ""; };
77 | 62F501160D9CF3394B0C531D /* Pods_iOS_MVVM_Template.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iOS_MVVM_Template.framework; sourceTree = BUILT_PRODUCTS_DIR; };
78 | 826C91F05D2CAE3FF0BF34EE /* Pods-iOS-MVVM-Template.release staging.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-MVVM-Template.release staging.xcconfig"; path = "Target Support Files/Pods-iOS-MVVM-Template/Pods-iOS-MVVM-Template.release staging.xcconfig"; sourceTree = ""; };
79 | 827D6180C69E25BA54D3B059 /* Pods-iOS-MVVM-Template.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-MVVM-Template.release.xcconfig"; path = "Target Support Files/Pods-iOS-MVVM-Template/Pods-iOS-MVVM-Template.release.xcconfig"; sourceTree = ""; };
80 | D8410366F6464789AD774A60 /* Pods-iOS-MVVM-Template.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iOS-MVVM-Template.debug.xcconfig"; path = "Target Support Files/Pods-iOS-MVVM-Template/Pods-iOS-MVVM-Template.debug.xcconfig"; sourceTree = ""; };
81 | FD7D4EB3249DBF5C00246661 /* ErrorExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorExtensions.swift; sourceTree = ""; };
82 | FD7E543326677F0800ABFE61 /* EncodableExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncodableExtensions.swift; sourceTree = ""; };
83 | FD7E543526677F1D00ABFE61 /* DecodableExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecodableExtensions.swift; sourceTree = ""; };
84 | FDD85AAC23B4BB6B00D7B8DD /* iOS-MVVM-Template.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "iOS-MVVM-Template.app"; sourceTree = BUILT_PRODUCTS_DIR; };
85 | FDD85AAF23B4BB6B00D7B8DD /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
86 | FDD85AB323B4BB6B00D7B8DD /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
87 | FDD85AB623B4BB6B00D7B8DD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
88 | FDD85AB823B4BB6C00D7B8DD /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
89 | FDD85ABB23B4BB6C00D7B8DD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
90 | FDD85ABD23B4BB6C00D7B8DD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
91 | FDD85AC223B4BB6D00D7B8DD /* iOS-MVVM-TemplateTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "iOS-MVVM-TemplateTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
92 | FDD85AC823B4BB6D00D7B8DD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
93 | FDD85AD423B4F2BC00D7B8DD /* Secrets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Secrets.swift; sourceTree = ""; };
94 | FDD85AD523B4F2BC00D7B8DD /* Global.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Global.swift; sourceTree = ""; };
95 | FDD85ADA23B4F77500D7B8DD /* OptionalExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OptionalExtensions.swift; sourceTree = ""; };
96 | FDD85ADB23B4F77500D7B8DD /* StringExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringExtensions.swift; sourceTree = ""; };
97 | FDD85ADC23B4F77500D7B8DD /* UIColorExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIColorExtensions.swift; sourceTree = ""; };
98 | FDD85ADD23B4F77500D7B8DD /* UIViewControllerExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewControllerExtensions.swift; sourceTree = ""; };
99 | FDD85ADE23B4F77500D7B8DD /* UIScreenExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIScreenExtensions.swift; sourceTree = ""; };
100 | FDD85ADF23B4F77600D7B8DD /* UIFontExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIFontExtensions.swift; sourceTree = ""; };
101 | FDD85AE223B4F77600D7B8DD /* DictionaryExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DictionaryExtensions.swift; sourceTree = ""; };
102 | FDD85AE323B4F77600D7B8DD /* UIViewController+Preparation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIViewController+Preparation.swift"; sourceTree = ""; };
103 | FDD85AE423B4F77600D7B8DD /* UIViewExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewExtensions.swift; sourceTree = ""; };
104 | FDD85AE523B4F77600D7B8DD /* UIAlertControllerExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIAlertControllerExtensions.swift; sourceTree = ""; };
105 | FDD85AF223B5985D00D7B8DD /* Keyboard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Keyboard.swift; sourceTree = ""; };
106 | FDD85AF323B5985D00D7B8DD /* Notifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Notifications.swift; sourceTree = ""; };
107 | FDD85AF423B5985D00D7B8DD /* Storyboard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Storyboard.swift; sourceTree = ""; };
108 | FDD85AF523B5985D00D7B8DD /* App.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; };
109 | FDD85AF623B5985D00D7B8DD /* PushRegistrationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushRegistrationType.swift; sourceTree = ""; };
110 | FDD85AF723B5985D00D7B8DD /* AppEnvironment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppEnvironment.swift; sourceTree = ""; };
111 | FDD85AF823B5985D00D7B8DD /* Nib.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Nib.swift; sourceTree = ""; };
112 | FDD85AF923B5985D00D7B8DD /* Environment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Environment.swift; sourceTree = ""; };
113 | FDD85AFA23B5985D00D7B8DD /* PushRegistration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushRegistration.swift; sourceTree = ""; };
114 | FDD85B1123B8C49000D7B8DD /* ServiceType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceType.swift; sourceTree = ""; };
115 | FDD85B1223B8C49000D7B8DD /* EnvironmentType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnvironmentType.swift; sourceTree = ""; };
116 | FDD85B1323B8C49000D7B8DD /* Service.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Service.swift; sourceTree = ""; };
117 | FDD85B1423B8C49000D7B8DD /* Service+RequestHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Service+RequestHelpers.swift"; sourceTree = ""; };
118 | FDD85B1623B8C49000D7B8DD /* VoidEnvelope.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VoidEnvelope.swift; sourceTree = ""; };
119 | FDD85B1723B8C49000D7B8DD /* AccessToken.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessToken.swift; sourceTree = ""; };
120 | FDD85B1823B8C49000D7B8DD /* ErrorCode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ErrorCode.swift; sourceTree = ""; };
121 | FDD85B1A23B8C49000D7B8DD /* AccessTokenTemplates.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccessTokenTemplates.swift; sourceTree = ""; };
122 | FDD85B1B23B8C49000D7B8DD /* ErrorEnvelope.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ErrorEnvelope.swift; sourceTree = ""; };
123 | FDD85B1D23B8C49000D7B8DD /* URLSession+RequestHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URLSession+RequestHelpers.swift"; sourceTree = ""; };
124 | FDD85B1E23B8C49000D7B8DD /* ServerConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerConfig.swift; sourceTree = ""; };
125 | FDD85B2023B8C49000D7B8DD /* RequestMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestMethod.swift; sourceTree = ""; };
126 | FDD85B2123B8C49000D7B8DD /* Route.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Route.swift; sourceTree = ""; };
127 | FDD85B2223B8C49000D7B8DD /* MockService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockService.swift; sourceTree = ""; };
128 | FDD85B3523B8CF4A00D7B8DD /* ValueCellDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValueCellDataSource.swift; sourceTree = ""; };
129 | FDD85B3623B8CF4A00D7B8DD /* ValueCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValueCell.swift; sourceTree = ""; };
130 | FDD85B4023B8CFD600D7B8DD /* AppDelegateViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegateViewModel.swift; sourceTree = ""; };
131 | FDD85B5423B8D13A00D7B8DD /* ValueCellDataSourceTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValueCellDataSourceTest.swift; sourceTree = ""; };
132 | FDD85B5623B8D13A00D7B8DD /* ServiceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceTests.swift; sourceTree = ""; };
133 | FDD85B5723B8D13A00D7B8DD /* ServiceTypeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceTypeTests.swift; sourceTree = ""; };
134 | FDD85B6023B8D5DE00D7B8DD /* PKHUD.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PKHUD.framework; path = Carthage/Build/iOS/PKHUD.framework; sourceTree = ""; };
135 | FDD85B6123B8D5DE00D7B8DD /* ReactiveExtensions.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReactiveExtensions.framework; path = Carthage/Build/iOS/ReactiveExtensions.framework; sourceTree = ""; };
136 | FDD85B6223B8D5DE00D7B8DD /* ReactiveSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReactiveSwift.framework; path = Carthage/Build/iOS/ReactiveSwift.framework; sourceTree = ""; };
137 | FDD85B6F23B8DD0500D7B8DD /* User.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; };
138 | FDD85B7623B8DD0500D7B8DD /* UserTemplates.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserTemplates.swift; sourceTree = ""; };
139 | FDD85B8D23B8E44000D7B8DD /* UITableViewExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITableViewExtensions.swift; sourceTree = ""; };
140 | /* End PBXFileReference section */
141 |
142 | /* Begin PBXFrameworksBuildPhase section */
143 | FDD85AA923B4BB6B00D7B8DD /* Frameworks */ = {
144 | isa = PBXFrameworksBuildPhase;
145 | buildActionMask = 2147483647;
146 | files = (
147 | AD8FA66F098FC2C2B5DD72AB /* Pods_iOS_MVVM_Template.framework in Frameworks */,
148 | );
149 | runOnlyForDeploymentPostprocessing = 0;
150 | };
151 | FDD85ABF23B4BB6D00D7B8DD /* Frameworks */ = {
152 | isa = PBXFrameworksBuildPhase;
153 | buildActionMask = 2147483647;
154 | files = (
155 | );
156 | runOnlyForDeploymentPostprocessing = 0;
157 | };
158 | /* End PBXFrameworksBuildPhase section */
159 |
160 | /* Begin PBXGroup section */
161 | 329A594DCE1D498E29E178E8 /* Pods */ = {
162 | isa = PBXGroup;
163 | children = (
164 | D8410366F6464789AD774A60 /* Pods-iOS-MVVM-Template.debug.xcconfig */,
165 | 827D6180C69E25BA54D3B059 /* Pods-iOS-MVVM-Template.release.xcconfig */,
166 | 344DE535F1B529B9C384CB87 /* Pods-iOS-MVVM-Template.debug staging.xcconfig */,
167 | 826C91F05D2CAE3FF0BF34EE /* Pods-iOS-MVVM-Template.release staging.xcconfig */,
168 | );
169 | path = Pods;
170 | sourceTree = "";
171 | };
172 | FDD85AA323B4BB6B00D7B8DD = {
173 | isa = PBXGroup;
174 | children = (
175 | FDD85AAE23B4BB6B00D7B8DD /* iOS-MVVM-Template */,
176 | FDD85AC523B4BB6D00D7B8DD /* iOS-MVVM-TemplateTests */,
177 | FDD85AAD23B4BB6B00D7B8DD /* Products */,
178 | FDD85B5F23B8D5DE00D7B8DD /* Frameworks */,
179 | 329A594DCE1D498E29E178E8 /* Pods */,
180 | );
181 | sourceTree = "";
182 | };
183 | FDD85AAD23B4BB6B00D7B8DD /* Products */ = {
184 | isa = PBXGroup;
185 | children = (
186 | FDD85AAC23B4BB6B00D7B8DD /* iOS-MVVM-Template.app */,
187 | FDD85AC223B4BB6D00D7B8DD /* iOS-MVVM-TemplateTests.xctest */,
188 | );
189 | name = Products;
190 | sourceTree = "";
191 | };
192 | FDD85AAE23B4BB6B00D7B8DD /* iOS-MVVM-Template */ = {
193 | isa = PBXGroup;
194 | children = (
195 | FDD85B1023B8C49000D7B8DD /* Api */,
196 | FDD85AD323B4F2A800D7B8DD /* Configs */,
197 | FDD85B6B23B8DD0500D7B8DD /* DataModels */,
198 | FDD85B3123B8CF4A00D7B8DD /* DataSources */,
199 | FDD85AD823B4F75300D7B8DD /* Library */,
200 | FDD85AD223B4F28F00D7B8DD /* Resources */,
201 | FDD85B3B23B8CFD600D7B8DD /* ViewModels */,
202 | FDD85B4E23B8D0DB00D7B8DD /* Views&Controllers */,
203 | FDD85AAF23B4BB6B00D7B8DD /* AppDelegate.swift */,
204 | FDD85AB323B4BB6B00D7B8DD /* ViewController.swift */,
205 | FDD85AB523B4BB6B00D7B8DD /* Main.storyboard */,
206 | FDD85ABA23B4BB6C00D7B8DD /* LaunchScreen.storyboard */,
207 | FDD85ABD23B4BB6C00D7B8DD /* Info.plist */,
208 | );
209 | path = "iOS-MVVM-Template";
210 | sourceTree = "";
211 | };
212 | FDD85AC523B4BB6D00D7B8DD /* iOS-MVVM-TemplateTests */ = {
213 | isa = PBXGroup;
214 | children = (
215 | FDD85B5523B8D13A00D7B8DD /* Api */,
216 | FDD85B5E23B8D1A100D7B8DD /* DataSources */,
217 | FDD85AC823B4BB6D00D7B8DD /* Info.plist */,
218 | );
219 | path = "iOS-MVVM-TemplateTests";
220 | sourceTree = "";
221 | };
222 | FDD85AD223B4F28F00D7B8DD /* Resources */ = {
223 | isa = PBXGroup;
224 | children = (
225 | FDD85AB823B4BB6C00D7B8DD /* Assets.xcassets */,
226 | );
227 | path = Resources;
228 | sourceTree = "";
229 | };
230 | FDD85AD323B4F2A800D7B8DD /* Configs */ = {
231 | isa = PBXGroup;
232 | children = (
233 | FDD85AD523B4F2BC00D7B8DD /* Global.swift */,
234 | FDD85AD423B4F2BC00D7B8DD /* Secrets.swift */,
235 | );
236 | path = Configs;
237 | sourceTree = "";
238 | };
239 | FDD85AD823B4F75300D7B8DD /* Library */ = {
240 | isa = PBXGroup;
241 | children = (
242 | FDD85AD923B4F75E00D7B8DD /* Extensions */,
243 | FDD85AF523B5985D00D7B8DD /* App.swift */,
244 | FDD85AF723B5985D00D7B8DD /* AppEnvironment.swift */,
245 | FDD85AF923B5985D00D7B8DD /* Environment.swift */,
246 | FDD85AF223B5985D00D7B8DD /* Keyboard.swift */,
247 | FDD85AF823B5985D00D7B8DD /* Nib.swift */,
248 | FDD85AF323B5985D00D7B8DD /* Notifications.swift */,
249 | FDD85AFA23B5985D00D7B8DD /* PushRegistration.swift */,
250 | FDD85AF623B5985D00D7B8DD /* PushRegistrationType.swift */,
251 | FDD85AF423B5985D00D7B8DD /* Storyboard.swift */,
252 | );
253 | path = Library;
254 | sourceTree = "";
255 | };
256 | FDD85AD923B4F75E00D7B8DD /* Extensions */ = {
257 | isa = PBXGroup;
258 | children = (
259 | FD7E543526677F1D00ABFE61 /* DecodableExtensions.swift */,
260 | FDD85AE223B4F77600D7B8DD /* DictionaryExtensions.swift */,
261 | FD7E543326677F0800ABFE61 /* EncodableExtensions.swift */,
262 | FD7D4EB3249DBF5C00246661 /* ErrorExtensions.swift */,
263 | FDD85ADA23B4F77500D7B8DD /* OptionalExtensions.swift */,
264 | FDD85ADB23B4F77500D7B8DD /* StringExtensions.swift */,
265 | FDD85AE523B4F77600D7B8DD /* UIAlertControllerExtensions.swift */,
266 | FDD85ADC23B4F77500D7B8DD /* UIColorExtensions.swift */,
267 | FDD85ADF23B4F77600D7B8DD /* UIFontExtensions.swift */,
268 | FDD85ADE23B4F77500D7B8DD /* UIScreenExtensions.swift */,
269 | FDD85B8D23B8E44000D7B8DD /* UITableViewExtensions.swift */,
270 | FDD85AE323B4F77600D7B8DD /* UIViewController+Preparation.swift */,
271 | FDD85ADD23B4F77500D7B8DD /* UIViewControllerExtensions.swift */,
272 | FDD85AE423B4F77600D7B8DD /* UIViewExtensions.swift */,
273 | );
274 | path = Extensions;
275 | sourceTree = "";
276 | };
277 | FDD85B1023B8C49000D7B8DD /* Api */ = {
278 | isa = PBXGroup;
279 | children = (
280 | FDD85B1523B8C49000D7B8DD /* Models */,
281 | FDD85B1C23B8C49000D7B8DD /* Extensions */,
282 | FDD85B1F23B8C49000D7B8DD /* Lib */,
283 | FDD85B1223B8C49000D7B8DD /* EnvironmentType.swift */,
284 | FDD85B2223B8C49000D7B8DD /* MockService.swift */,
285 | FDD85B1323B8C49000D7B8DD /* Service.swift */,
286 | FDD85B1E23B8C49000D7B8DD /* ServerConfig.swift */,
287 | FDD85B1423B8C49000D7B8DD /* Service+RequestHelpers.swift */,
288 | FDD85B1123B8C49000D7B8DD /* ServiceType.swift */,
289 | );
290 | path = Api;
291 | sourceTree = "";
292 | };
293 | FDD85B1523B8C49000D7B8DD /* Models */ = {
294 | isa = PBXGroup;
295 | children = (
296 | FDD85B1723B8C49000D7B8DD /* AccessToken.swift */,
297 | FDD85B1823B8C49000D7B8DD /* ErrorCode.swift */,
298 | FDD85B1B23B8C49000D7B8DD /* ErrorEnvelope.swift */,
299 | FDD85B1623B8C49000D7B8DD /* VoidEnvelope.swift */,
300 | FDD85B1923B8C49000D7B8DD /* Templates */,
301 | );
302 | path = Models;
303 | sourceTree = "";
304 | };
305 | FDD85B1923B8C49000D7B8DD /* Templates */ = {
306 | isa = PBXGroup;
307 | children = (
308 | FDD85B1A23B8C49000D7B8DD /* AccessTokenTemplates.swift */,
309 | );
310 | path = Templates;
311 | sourceTree = "";
312 | };
313 | FDD85B1C23B8C49000D7B8DD /* Extensions */ = {
314 | isa = PBXGroup;
315 | children = (
316 | FDD85B1D23B8C49000D7B8DD /* URLSession+RequestHelpers.swift */,
317 | );
318 | path = Extensions;
319 | sourceTree = "";
320 | };
321 | FDD85B1F23B8C49000D7B8DD /* Lib */ = {
322 | isa = PBXGroup;
323 | children = (
324 | FDD85B2023B8C49000D7B8DD /* RequestMethod.swift */,
325 | FDD85B2123B8C49000D7B8DD /* Route.swift */,
326 | );
327 | path = Lib;
328 | sourceTree = "";
329 | };
330 | FDD85B3123B8CF4A00D7B8DD /* DataSources */ = {
331 | isa = PBXGroup;
332 | children = (
333 | FDD85B3423B8CF4A00D7B8DD /* Base */,
334 | );
335 | path = DataSources;
336 | sourceTree = "";
337 | };
338 | FDD85B3423B8CF4A00D7B8DD /* Base */ = {
339 | isa = PBXGroup;
340 | children = (
341 | FDD85B3623B8CF4A00D7B8DD /* ValueCell.swift */,
342 | FDD85B3523B8CF4A00D7B8DD /* ValueCellDataSource.swift */,
343 | );
344 | path = Base;
345 | sourceTree = "";
346 | };
347 | FDD85B3B23B8CFD600D7B8DD /* ViewModels */ = {
348 | isa = PBXGroup;
349 | children = (
350 | FDD85B4023B8CFD600D7B8DD /* AppDelegateViewModel.swift */,
351 | );
352 | path = ViewModels;
353 | sourceTree = "";
354 | };
355 | FDD85B4E23B8D0DB00D7B8DD /* Views&Controllers */ = {
356 | isa = PBXGroup;
357 | children = (
358 | );
359 | path = "Views&Controllers";
360 | sourceTree = "";
361 | };
362 | FDD85B5523B8D13A00D7B8DD /* Api */ = {
363 | isa = PBXGroup;
364 | children = (
365 | FDD85B5623B8D13A00D7B8DD /* ServiceTests.swift */,
366 | FDD85B5723B8D13A00D7B8DD /* ServiceTypeTests.swift */,
367 | );
368 | path = Api;
369 | sourceTree = "";
370 | };
371 | FDD85B5E23B8D1A100D7B8DD /* DataSources */ = {
372 | isa = PBXGroup;
373 | children = (
374 | FDD85B5423B8D13A00D7B8DD /* ValueCellDataSourceTest.swift */,
375 | );
376 | path = DataSources;
377 | sourceTree = "";
378 | };
379 | FDD85B5F23B8D5DE00D7B8DD /* Frameworks */ = {
380 | isa = PBXGroup;
381 | children = (
382 | FDD85B6023B8D5DE00D7B8DD /* PKHUD.framework */,
383 | FDD85B6123B8D5DE00D7B8DD /* ReactiveExtensions.framework */,
384 | FDD85B6223B8D5DE00D7B8DD /* ReactiveSwift.framework */,
385 | 62F501160D9CF3394B0C531D /* Pods_iOS_MVVM_Template.framework */,
386 | );
387 | name = Frameworks;
388 | sourceTree = "";
389 | };
390 | FDD85B6B23B8DD0500D7B8DD /* DataModels */ = {
391 | isa = PBXGroup;
392 | children = (
393 | FDD85B6F23B8DD0500D7B8DD /* User.swift */,
394 | FDD85B7423B8DD0500D7B8DD /* Templates */,
395 | );
396 | path = DataModels;
397 | sourceTree = "";
398 | };
399 | FDD85B7423B8DD0500D7B8DD /* Templates */ = {
400 | isa = PBXGroup;
401 | children = (
402 | FDD85B7623B8DD0500D7B8DD /* UserTemplates.swift */,
403 | );
404 | path = Templates;
405 | sourceTree = "";
406 | };
407 | /* End PBXGroup section */
408 |
409 | /* Begin PBXNativeTarget section */
410 | FDD85AAB23B4BB6B00D7B8DD /* iOS-MVVM-Template */ = {
411 | isa = PBXNativeTarget;
412 | buildConfigurationList = FDD85ACB23B4BB6D00D7B8DD /* Build configuration list for PBXNativeTarget "iOS-MVVM-Template" */;
413 | buildPhases = (
414 | 4540A5AE9D6008F8A591F95E /* [CP] Check Pods Manifest.lock */,
415 | FDD85AA823B4BB6B00D7B8DD /* Sources */,
416 | FDD85AA923B4BB6B00D7B8DD /* Frameworks */,
417 | FDD85AAA23B4BB6B00D7B8DD /* Resources */,
418 | FDD85AD123B4F15200D7B8DD /* SwiftLint */,
419 | B09216E026132C9F0DB7C89E /* [CP] Embed Pods Frameworks */,
420 | );
421 | buildRules = (
422 | );
423 | dependencies = (
424 | );
425 | name = "iOS-MVVM-Template";
426 | productName = "iOS-MVVM-Template";
427 | productReference = FDD85AAC23B4BB6B00D7B8DD /* iOS-MVVM-Template.app */;
428 | productType = "com.apple.product-type.application";
429 | };
430 | FDD85AC123B4BB6D00D7B8DD /* iOS-MVVM-TemplateTests */ = {
431 | isa = PBXNativeTarget;
432 | buildConfigurationList = FDD85ACE23B4BB6D00D7B8DD /* Build configuration list for PBXNativeTarget "iOS-MVVM-TemplateTests" */;
433 | buildPhases = (
434 | FDD85ABE23B4BB6D00D7B8DD /* Sources */,
435 | FDD85ABF23B4BB6D00D7B8DD /* Frameworks */,
436 | FDD85AC023B4BB6D00D7B8DD /* Resources */,
437 | );
438 | buildRules = (
439 | );
440 | dependencies = (
441 | FDD85AC423B4BB6D00D7B8DD /* PBXTargetDependency */,
442 | );
443 | name = "iOS-MVVM-TemplateTests";
444 | productName = "iOS-MVVM-TemplateTests";
445 | productReference = FDD85AC223B4BB6D00D7B8DD /* iOS-MVVM-TemplateTests.xctest */;
446 | productType = "com.apple.product-type.bundle.unit-test";
447 | };
448 | /* End PBXNativeTarget section */
449 |
450 | /* Begin PBXProject section */
451 | FDD85AA423B4BB6B00D7B8DD /* Project object */ = {
452 | isa = PBXProject;
453 | attributes = {
454 | LastSwiftUpdateCheck = 1130;
455 | LastUpgradeCheck = 1250;
456 | ORGANIZATIONNAME = Lebron;
457 | TargetAttributes = {
458 | FDD85AAB23B4BB6B00D7B8DD = {
459 | CreatedOnToolsVersion = 11.3;
460 | };
461 | FDD85AC123B4BB6D00D7B8DD = {
462 | CreatedOnToolsVersion = 11.3;
463 | LastSwiftMigration = 1130;
464 | TestTargetID = FDD85AAB23B4BB6B00D7B8DD;
465 | };
466 | };
467 | };
468 | buildConfigurationList = FDD85AA723B4BB6B00D7B8DD /* Build configuration list for PBXProject "iOS-MVVM-Template" */;
469 | compatibilityVersion = "Xcode 9.3";
470 | developmentRegion = en;
471 | hasScannedForEncodings = 0;
472 | knownRegions = (
473 | en,
474 | Base,
475 | );
476 | mainGroup = FDD85AA323B4BB6B00D7B8DD;
477 | productRefGroup = FDD85AAD23B4BB6B00D7B8DD /* Products */;
478 | projectDirPath = "";
479 | projectRoot = "";
480 | targets = (
481 | FDD85AAB23B4BB6B00D7B8DD /* iOS-MVVM-Template */,
482 | FDD85AC123B4BB6D00D7B8DD /* iOS-MVVM-TemplateTests */,
483 | );
484 | };
485 | /* End PBXProject section */
486 |
487 | /* Begin PBXResourcesBuildPhase section */
488 | FDD85AAA23B4BB6B00D7B8DD /* Resources */ = {
489 | isa = PBXResourcesBuildPhase;
490 | buildActionMask = 2147483647;
491 | files = (
492 | FDD85ABC23B4BB6C00D7B8DD /* LaunchScreen.storyboard in Resources */,
493 | FDD85AB923B4BB6C00D7B8DD /* Assets.xcassets in Resources */,
494 | FDD85AB723B4BB6B00D7B8DD /* Main.storyboard in Resources */,
495 | );
496 | runOnlyForDeploymentPostprocessing = 0;
497 | };
498 | FDD85AC023B4BB6D00D7B8DD /* Resources */ = {
499 | isa = PBXResourcesBuildPhase;
500 | buildActionMask = 2147483647;
501 | files = (
502 | );
503 | runOnlyForDeploymentPostprocessing = 0;
504 | };
505 | /* End PBXResourcesBuildPhase section */
506 |
507 | /* Begin PBXShellScriptBuildPhase section */
508 | 4540A5AE9D6008F8A591F95E /* [CP] Check Pods Manifest.lock */ = {
509 | isa = PBXShellScriptBuildPhase;
510 | buildActionMask = 2147483647;
511 | files = (
512 | );
513 | inputFileListPaths = (
514 | );
515 | inputPaths = (
516 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
517 | "${PODS_ROOT}/Manifest.lock",
518 | );
519 | name = "[CP] Check Pods Manifest.lock";
520 | outputFileListPaths = (
521 | );
522 | outputPaths = (
523 | "$(DERIVED_FILE_DIR)/Pods-iOS-MVVM-Template-checkManifestLockResult.txt",
524 | );
525 | runOnlyForDeploymentPostprocessing = 0;
526 | shellPath = /bin/sh;
527 | 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";
528 | showEnvVarsInLog = 0;
529 | };
530 | B09216E026132C9F0DB7C89E /* [CP] Embed Pods Frameworks */ = {
531 | isa = PBXShellScriptBuildPhase;
532 | buildActionMask = 2147483647;
533 | files = (
534 | );
535 | inputFileListPaths = (
536 | "${PODS_ROOT}/Target Support Files/Pods-iOS-MVVM-Template/Pods-iOS-MVVM-Template-frameworks-${CONFIGURATION}-input-files.xcfilelist",
537 | );
538 | name = "[CP] Embed Pods Frameworks";
539 | outputFileListPaths = (
540 | "${PODS_ROOT}/Target Support Files/Pods-iOS-MVVM-Template/Pods-iOS-MVVM-Template-frameworks-${CONFIGURATION}-output-files.xcfilelist",
541 | );
542 | runOnlyForDeploymentPostprocessing = 0;
543 | shellPath = /bin/sh;
544 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iOS-MVVM-Template/Pods-iOS-MVVM-Template-frameworks.sh\"\n";
545 | showEnvVarsInLog = 0;
546 | };
547 | FDD85AD123B4F15200D7B8DD /* SwiftLint */ = {
548 | isa = PBXShellScriptBuildPhase;
549 | buildActionMask = 2147483647;
550 | files = (
551 | );
552 | inputFileListPaths = (
553 | );
554 | inputPaths = (
555 | );
556 | name = SwiftLint;
557 | outputFileListPaths = (
558 | );
559 | outputPaths = (
560 | );
561 | runOnlyForDeploymentPostprocessing = 0;
562 | shellPath = /bin/sh;
563 | shellScript = "if which swiftlint >/dev/null; then\nswiftlint\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
564 | };
565 | /* End PBXShellScriptBuildPhase section */
566 |
567 | /* Begin PBXSourcesBuildPhase section */
568 | FDD85AA823B4BB6B00D7B8DD /* Sources */ = {
569 | isa = PBXSourcesBuildPhase;
570 | buildActionMask = 2147483647;
571 | files = (
572 | FDD85B4923B8CFD600D7B8DD /* AppDelegateViewModel.swift in Sources */,
573 | FDD85B2D23B8C49000D7B8DD /* ServerConfig.swift in Sources */,
574 | FDD85B2723B8C49000D7B8DD /* VoidEnvelope.swift in Sources */,
575 | FDD85AB423B4BB6B00D7B8DD /* ViewController.swift in Sources */,
576 | FDD85B8623B8DD0600D7B8DD /* UserTemplates.swift in Sources */,
577 | FDD85AEE23B4F77600D7B8DD /* DictionaryExtensions.swift in Sources */,
578 | FDD85AE623B4F77600D7B8DD /* OptionalExtensions.swift in Sources */,
579 | FDD85AFE23B5985D00D7B8DD /* App.swift in Sources */,
580 | FDD85B0323B5985D00D7B8DD /* PushRegistration.swift in Sources */,
581 | FDD85B2523B8C49000D7B8DD /* Service.swift in Sources */,
582 | FDD85AD623B4F2BD00D7B8DD /* Secrets.swift in Sources */,
583 | FDD85AF123B4F77600D7B8DD /* UIAlertControllerExtensions.swift in Sources */,
584 | FDD85B2923B8C49000D7B8DD /* ErrorCode.swift in Sources */,
585 | FDD85AFF23B5985D00D7B8DD /* PushRegistrationType.swift in Sources */,
586 | FDD85AE723B4F77600D7B8DD /* StringExtensions.swift in Sources */,
587 | FDD85AEB23B4F77600D7B8DD /* UIFontExtensions.swift in Sources */,
588 | FDD85AE923B4F77600D7B8DD /* UIViewControllerExtensions.swift in Sources */,
589 | FDD85AEF23B4F77600D7B8DD /* UIViewController+Preparation.swift in Sources */,
590 | FDD85B2E23B8C49000D7B8DD /* RequestMethod.swift in Sources */,
591 | FD7E543426677F0800ABFE61 /* EncodableExtensions.swift in Sources */,
592 | FDD85AF023B4F77600D7B8DD /* UIViewExtensions.swift in Sources */,
593 | FDD85AFB23B5985D00D7B8DD /* Keyboard.swift in Sources */,
594 | FDD85B8023B8DD0600D7B8DD /* User.swift in Sources */,
595 | FDD85B0223B5985D00D7B8DD /* Environment.swift in Sources */,
596 | FDD85B8E23B8E44000D7B8DD /* UITableViewExtensions.swift in Sources */,
597 | FDD85B2B23B8C49000D7B8DD /* ErrorEnvelope.swift in Sources */,
598 | FDD85B2323B8C49000D7B8DD /* ServiceType.swift in Sources */,
599 | FD7D4EB4249DBF5C00246661 /* ErrorExtensions.swift in Sources */,
600 | FDD85B3923B8CF4A00D7B8DD /* ValueCellDataSource.swift in Sources */,
601 | FDD85B2C23B8C49000D7B8DD /* URLSession+RequestHelpers.swift in Sources */,
602 | FDD85AB023B4BB6B00D7B8DD /* AppDelegate.swift in Sources */,
603 | FDD85B2423B8C49000D7B8DD /* EnvironmentType.swift in Sources */,
604 | FDD85B2623B8C49000D7B8DD /* Service+RequestHelpers.swift in Sources */,
605 | FDD85B0023B5985D00D7B8DD /* AppEnvironment.swift in Sources */,
606 | FDD85B2A23B8C49000D7B8DD /* AccessTokenTemplates.swift in Sources */,
607 | FDD85B0123B5985D00D7B8DD /* Nib.swift in Sources */,
608 | FDD85B3023B8C49000D7B8DD /* MockService.swift in Sources */,
609 | FDD85AD723B4F2BD00D7B8DD /* Global.swift in Sources */,
610 | FDD85AFC23B5985D00D7B8DD /* Notifications.swift in Sources */,
611 | FD7E543626677F1D00ABFE61 /* DecodableExtensions.swift in Sources */,
612 | FDD85B2F23B8C49000D7B8DD /* Route.swift in Sources */,
613 | FDD85AEA23B4F77600D7B8DD /* UIScreenExtensions.swift in Sources */,
614 | FDD85B3A23B8CF4A00D7B8DD /* ValueCell.swift in Sources */,
615 | FDD85AFD23B5985D00D7B8DD /* Storyboard.swift in Sources */,
616 | FDD85AE823B4F77600D7B8DD /* UIColorExtensions.swift in Sources */,
617 | FDD85B2823B8C49000D7B8DD /* AccessToken.swift in Sources */,
618 | );
619 | runOnlyForDeploymentPostprocessing = 0;
620 | };
621 | FDD85ABE23B4BB6D00D7B8DD /* Sources */ = {
622 | isa = PBXSourcesBuildPhase;
623 | buildActionMask = 2147483647;
624 | files = (
625 | FDD85B5B23B8D13B00D7B8DD /* ValueCellDataSourceTest.swift in Sources */,
626 | FDD85B5C23B8D13B00D7B8DD /* ServiceTests.swift in Sources */,
627 | FDD85B5D23B8D13B00D7B8DD /* ServiceTypeTests.swift in Sources */,
628 | );
629 | runOnlyForDeploymentPostprocessing = 0;
630 | };
631 | /* End PBXSourcesBuildPhase section */
632 |
633 | /* Begin PBXTargetDependency section */
634 | FDD85AC423B4BB6D00D7B8DD /* PBXTargetDependency */ = {
635 | isa = PBXTargetDependency;
636 | target = FDD85AAB23B4BB6B00D7B8DD /* iOS-MVVM-Template */;
637 | targetProxy = FDD85AC323B4BB6D00D7B8DD /* PBXContainerItemProxy */;
638 | };
639 | /* End PBXTargetDependency section */
640 |
641 | /* Begin PBXVariantGroup section */
642 | FDD85AB523B4BB6B00D7B8DD /* Main.storyboard */ = {
643 | isa = PBXVariantGroup;
644 | children = (
645 | FDD85AB623B4BB6B00D7B8DD /* Base */,
646 | );
647 | name = Main.storyboard;
648 | sourceTree = "";
649 | };
650 | FDD85ABA23B4BB6C00D7B8DD /* LaunchScreen.storyboard */ = {
651 | isa = PBXVariantGroup;
652 | children = (
653 | FDD85ABB23B4BB6C00D7B8DD /* Base */,
654 | );
655 | name = LaunchScreen.storyboard;
656 | sourceTree = "";
657 | };
658 | /* End PBXVariantGroup section */
659 |
660 | /* Begin XCBuildConfiguration section */
661 | FDD85AC923B4BB6D00D7B8DD /* Debug */ = {
662 | isa = XCBuildConfiguration;
663 | buildSettings = {
664 | ALWAYS_SEARCH_USER_PATHS = NO;
665 | CLANG_ANALYZER_NONNULL = YES;
666 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
667 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
668 | CLANG_CXX_LIBRARY = "libc++";
669 | CLANG_ENABLE_MODULES = YES;
670 | CLANG_ENABLE_OBJC_ARC = YES;
671 | CLANG_ENABLE_OBJC_WEAK = YES;
672 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
673 | CLANG_WARN_BOOL_CONVERSION = YES;
674 | CLANG_WARN_COMMA = YES;
675 | CLANG_WARN_CONSTANT_CONVERSION = YES;
676 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
677 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
678 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
679 | CLANG_WARN_EMPTY_BODY = YES;
680 | CLANG_WARN_ENUM_CONVERSION = YES;
681 | CLANG_WARN_INFINITE_RECURSION = YES;
682 | CLANG_WARN_INT_CONVERSION = YES;
683 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
684 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
685 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
686 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
687 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
688 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
689 | CLANG_WARN_STRICT_PROTOTYPES = YES;
690 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
691 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
692 | CLANG_WARN_UNREACHABLE_CODE = YES;
693 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
694 | COPY_PHASE_STRIP = NO;
695 | DEBUG_INFORMATION_FORMAT = dwarf;
696 | ENABLE_STRICT_OBJC_MSGSEND = YES;
697 | ENABLE_TESTABILITY = YES;
698 | GCC_C_LANGUAGE_STANDARD = gnu11;
699 | GCC_DYNAMIC_NO_PIC = NO;
700 | GCC_NO_COMMON_BLOCKS = YES;
701 | GCC_OPTIMIZATION_LEVEL = 0;
702 | GCC_PREPROCESSOR_DEFINITIONS = (
703 | "DEBUG=1",
704 | "$(inherited)",
705 | );
706 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
707 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
708 | GCC_WARN_UNDECLARED_SELECTOR = YES;
709 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
710 | GCC_WARN_UNUSED_FUNCTION = YES;
711 | GCC_WARN_UNUSED_VARIABLE = YES;
712 | IPHONEOS_DEPLOYMENT_TARGET = 13.2;
713 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
714 | MTL_FAST_MATH = YES;
715 | ONLY_ACTIVE_ARCH = YES;
716 | SDKROOT = iphoneos;
717 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
718 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
719 | };
720 | name = Debug;
721 | };
722 | FDD85ACA23B4BB6D00D7B8DD /* Release */ = {
723 | isa = XCBuildConfiguration;
724 | buildSettings = {
725 | ALWAYS_SEARCH_USER_PATHS = NO;
726 | CLANG_ANALYZER_NONNULL = YES;
727 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
728 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
729 | CLANG_CXX_LIBRARY = "libc++";
730 | CLANG_ENABLE_MODULES = YES;
731 | CLANG_ENABLE_OBJC_ARC = YES;
732 | CLANG_ENABLE_OBJC_WEAK = YES;
733 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
734 | CLANG_WARN_BOOL_CONVERSION = YES;
735 | CLANG_WARN_COMMA = YES;
736 | CLANG_WARN_CONSTANT_CONVERSION = YES;
737 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
738 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
739 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
740 | CLANG_WARN_EMPTY_BODY = YES;
741 | CLANG_WARN_ENUM_CONVERSION = YES;
742 | CLANG_WARN_INFINITE_RECURSION = YES;
743 | CLANG_WARN_INT_CONVERSION = YES;
744 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
745 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
746 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
747 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
748 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
749 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
750 | CLANG_WARN_STRICT_PROTOTYPES = YES;
751 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
752 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
753 | CLANG_WARN_UNREACHABLE_CODE = YES;
754 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
755 | COPY_PHASE_STRIP = NO;
756 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
757 | ENABLE_NS_ASSERTIONS = NO;
758 | ENABLE_STRICT_OBJC_MSGSEND = YES;
759 | GCC_C_LANGUAGE_STANDARD = gnu11;
760 | GCC_NO_COMMON_BLOCKS = YES;
761 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
762 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
763 | GCC_WARN_UNDECLARED_SELECTOR = YES;
764 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
765 | GCC_WARN_UNUSED_FUNCTION = YES;
766 | GCC_WARN_UNUSED_VARIABLE = YES;
767 | IPHONEOS_DEPLOYMENT_TARGET = 13.2;
768 | MTL_ENABLE_DEBUG_INFO = NO;
769 | MTL_FAST_MATH = YES;
770 | SDKROOT = iphoneos;
771 | SWIFT_COMPILATION_MODE = wholemodule;
772 | SWIFT_OPTIMIZATION_LEVEL = "-O";
773 | VALIDATE_PRODUCT = YES;
774 | };
775 | name = Release;
776 | };
777 | FDD85ACC23B4BB6D00D7B8DD /* Debug */ = {
778 | isa = XCBuildConfiguration;
779 | baseConfigurationReference = D8410366F6464789AD774A60 /* Pods-iOS-MVVM-Template.debug.xcconfig */;
780 | buildSettings = {
781 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
782 | CODE_SIGN_STYLE = Automatic;
783 | FRAMEWORK_SEARCH_PATHS = (
784 | "$(inherited)",
785 | "$(PROJECT_DIR)/Carthage/Build/iOS",
786 | );
787 | INFOPLIST_FILE = "iOS-MVVM-Template/Info.plist";
788 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
789 | IS_STAGING = NO;
790 | LD_RUNPATH_SEARCH_PATHS = (
791 | "$(inherited)",
792 | "@executable_path/Frameworks",
793 | );
794 | PRODUCT_BUNDLE_IDENTIFIER = "com.lebron.iOS-MVVM-Template";
795 | PRODUCT_NAME = "$(TARGET_NAME)";
796 | SWIFT_VERSION = 5.0;
797 | TARGETED_DEVICE_FAMILY = "1,2";
798 | };
799 | name = Debug;
800 | };
801 | FDD85ACD23B4BB6D00D7B8DD /* Release */ = {
802 | isa = XCBuildConfiguration;
803 | baseConfigurationReference = 827D6180C69E25BA54D3B059 /* Pods-iOS-MVVM-Template.release.xcconfig */;
804 | buildSettings = {
805 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
806 | CODE_SIGN_STYLE = Automatic;
807 | FRAMEWORK_SEARCH_PATHS = (
808 | "$(inherited)",
809 | "$(PROJECT_DIR)/Carthage/Build/iOS",
810 | );
811 | INFOPLIST_FILE = "iOS-MVVM-Template/Info.plist";
812 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
813 | IS_STAGING = NO;
814 | LD_RUNPATH_SEARCH_PATHS = (
815 | "$(inherited)",
816 | "@executable_path/Frameworks",
817 | );
818 | PRODUCT_BUNDLE_IDENTIFIER = "com.lebron.iOS-MVVM-Template";
819 | PRODUCT_NAME = "$(TARGET_NAME)";
820 | SWIFT_VERSION = 5.0;
821 | TARGETED_DEVICE_FAMILY = "1,2";
822 | };
823 | name = Release;
824 | };
825 | FDD85ACF23B4BB6D00D7B8DD /* Debug */ = {
826 | isa = XCBuildConfiguration;
827 | buildSettings = {
828 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
829 | BUNDLE_LOADER = "$(TEST_HOST)";
830 | CLANG_ENABLE_MODULES = YES;
831 | CODE_SIGN_STYLE = Automatic;
832 | INFOPLIST_FILE = "iOS-MVVM-TemplateTests/Info.plist";
833 | IPHONEOS_DEPLOYMENT_TARGET = 13.2;
834 | LD_RUNPATH_SEARCH_PATHS = (
835 | "$(inherited)",
836 | "@executable_path/Frameworks",
837 | "@loader_path/Frameworks",
838 | );
839 | PRODUCT_BUNDLE_IDENTIFIER = "com.lebron.iOS-MVVM-TemplateTests";
840 | PRODUCT_NAME = "$(TARGET_NAME)";
841 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
842 | SWIFT_VERSION = 5.0;
843 | TARGETED_DEVICE_FAMILY = "1,2";
844 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/iOS-MVVM-Template.app/iOS-MVVM-Template";
845 | };
846 | name = Debug;
847 | };
848 | FDD85AD023B4BB6D00D7B8DD /* Release */ = {
849 | isa = XCBuildConfiguration;
850 | buildSettings = {
851 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
852 | BUNDLE_LOADER = "$(TEST_HOST)";
853 | CLANG_ENABLE_MODULES = YES;
854 | CODE_SIGN_STYLE = Automatic;
855 | INFOPLIST_FILE = "iOS-MVVM-TemplateTests/Info.plist";
856 | IPHONEOS_DEPLOYMENT_TARGET = 13.2;
857 | LD_RUNPATH_SEARCH_PATHS = (
858 | "$(inherited)",
859 | "@executable_path/Frameworks",
860 | "@loader_path/Frameworks",
861 | );
862 | PRODUCT_BUNDLE_IDENTIFIER = "com.lebron.iOS-MVVM-TemplateTests";
863 | PRODUCT_NAME = "$(TARGET_NAME)";
864 | SWIFT_VERSION = 5.0;
865 | TARGETED_DEVICE_FAMILY = "1,2";
866 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/iOS-MVVM-Template.app/iOS-MVVM-Template";
867 | };
868 | name = Release;
869 | };
870 | FDD85B8F23B8E65A00D7B8DD /* Debug Staging */ = {
871 | isa = XCBuildConfiguration;
872 | buildSettings = {
873 | ALWAYS_SEARCH_USER_PATHS = NO;
874 | CLANG_ANALYZER_NONNULL = YES;
875 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
876 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
877 | CLANG_CXX_LIBRARY = "libc++";
878 | CLANG_ENABLE_MODULES = YES;
879 | CLANG_ENABLE_OBJC_ARC = YES;
880 | CLANG_ENABLE_OBJC_WEAK = YES;
881 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
882 | CLANG_WARN_BOOL_CONVERSION = YES;
883 | CLANG_WARN_COMMA = YES;
884 | CLANG_WARN_CONSTANT_CONVERSION = YES;
885 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
886 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
887 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
888 | CLANG_WARN_EMPTY_BODY = YES;
889 | CLANG_WARN_ENUM_CONVERSION = YES;
890 | CLANG_WARN_INFINITE_RECURSION = YES;
891 | CLANG_WARN_INT_CONVERSION = YES;
892 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
893 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
894 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
895 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
896 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
897 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
898 | CLANG_WARN_STRICT_PROTOTYPES = YES;
899 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
900 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
901 | CLANG_WARN_UNREACHABLE_CODE = YES;
902 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
903 | COPY_PHASE_STRIP = NO;
904 | DEBUG_INFORMATION_FORMAT = dwarf;
905 | ENABLE_STRICT_OBJC_MSGSEND = YES;
906 | ENABLE_TESTABILITY = YES;
907 | GCC_C_LANGUAGE_STANDARD = gnu11;
908 | GCC_DYNAMIC_NO_PIC = NO;
909 | GCC_NO_COMMON_BLOCKS = YES;
910 | GCC_OPTIMIZATION_LEVEL = 0;
911 | GCC_PREPROCESSOR_DEFINITIONS = (
912 | "DEBUG=1",
913 | "$(inherited)",
914 | );
915 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
916 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
917 | GCC_WARN_UNDECLARED_SELECTOR = YES;
918 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
919 | GCC_WARN_UNUSED_FUNCTION = YES;
920 | GCC_WARN_UNUSED_VARIABLE = YES;
921 | IPHONEOS_DEPLOYMENT_TARGET = 13.2;
922 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
923 | MTL_FAST_MATH = YES;
924 | ONLY_ACTIVE_ARCH = YES;
925 | SDKROOT = iphoneos;
926 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
927 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
928 | };
929 | name = "Debug Staging";
930 | };
931 | FDD85B9023B8E65A00D7B8DD /* Debug Staging */ = {
932 | isa = XCBuildConfiguration;
933 | baseConfigurationReference = 344DE535F1B529B9C384CB87 /* Pods-iOS-MVVM-Template.debug staging.xcconfig */;
934 | buildSettings = {
935 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
936 | CODE_SIGN_STYLE = Automatic;
937 | FRAMEWORK_SEARCH_PATHS = (
938 | "$(inherited)",
939 | "$(PROJECT_DIR)/Carthage/Build/iOS",
940 | );
941 | INFOPLIST_FILE = "iOS-MVVM-Template/Info.plist";
942 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
943 | IS_STAGING = YES;
944 | LD_RUNPATH_SEARCH_PATHS = (
945 | "$(inherited)",
946 | "@executable_path/Frameworks",
947 | );
948 | PRODUCT_BUNDLE_IDENTIFIER = "com.lebron.iOS-MVVM-Template";
949 | PRODUCT_NAME = "$(TARGET_NAME)";
950 | SWIFT_VERSION = 5.0;
951 | TARGETED_DEVICE_FAMILY = "1,2";
952 | };
953 | name = "Debug Staging";
954 | };
955 | FDD85B9123B8E65A00D7B8DD /* Debug Staging */ = {
956 | isa = XCBuildConfiguration;
957 | buildSettings = {
958 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
959 | BUNDLE_LOADER = "$(TEST_HOST)";
960 | CLANG_ENABLE_MODULES = YES;
961 | CODE_SIGN_STYLE = Automatic;
962 | INFOPLIST_FILE = "iOS-MVVM-TemplateTests/Info.plist";
963 | IPHONEOS_DEPLOYMENT_TARGET = 13.2;
964 | LD_RUNPATH_SEARCH_PATHS = (
965 | "$(inherited)",
966 | "@executable_path/Frameworks",
967 | "@loader_path/Frameworks",
968 | );
969 | PRODUCT_BUNDLE_IDENTIFIER = "com.lebron.iOS-MVVM-TemplateTests";
970 | PRODUCT_NAME = "$(TARGET_NAME)";
971 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
972 | SWIFT_VERSION = 5.0;
973 | TARGETED_DEVICE_FAMILY = "1,2";
974 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/iOS-MVVM-Template.app/iOS-MVVM-Template";
975 | };
976 | name = "Debug Staging";
977 | };
978 | FDD85B9223B8E66300D7B8DD /* Release Staging */ = {
979 | isa = XCBuildConfiguration;
980 | buildSettings = {
981 | ALWAYS_SEARCH_USER_PATHS = NO;
982 | CLANG_ANALYZER_NONNULL = YES;
983 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
984 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
985 | CLANG_CXX_LIBRARY = "libc++";
986 | CLANG_ENABLE_MODULES = YES;
987 | CLANG_ENABLE_OBJC_ARC = YES;
988 | CLANG_ENABLE_OBJC_WEAK = YES;
989 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
990 | CLANG_WARN_BOOL_CONVERSION = YES;
991 | CLANG_WARN_COMMA = YES;
992 | CLANG_WARN_CONSTANT_CONVERSION = YES;
993 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
994 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
995 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
996 | CLANG_WARN_EMPTY_BODY = YES;
997 | CLANG_WARN_ENUM_CONVERSION = YES;
998 | CLANG_WARN_INFINITE_RECURSION = YES;
999 | CLANG_WARN_INT_CONVERSION = YES;
1000 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
1001 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
1002 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
1003 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
1004 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
1005 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
1006 | CLANG_WARN_STRICT_PROTOTYPES = YES;
1007 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
1008 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
1009 | CLANG_WARN_UNREACHABLE_CODE = YES;
1010 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
1011 | COPY_PHASE_STRIP = NO;
1012 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
1013 | ENABLE_NS_ASSERTIONS = NO;
1014 | ENABLE_STRICT_OBJC_MSGSEND = YES;
1015 | GCC_C_LANGUAGE_STANDARD = gnu11;
1016 | GCC_NO_COMMON_BLOCKS = YES;
1017 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
1018 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
1019 | GCC_WARN_UNDECLARED_SELECTOR = YES;
1020 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
1021 | GCC_WARN_UNUSED_FUNCTION = YES;
1022 | GCC_WARN_UNUSED_VARIABLE = YES;
1023 | IPHONEOS_DEPLOYMENT_TARGET = 13.2;
1024 | MTL_ENABLE_DEBUG_INFO = NO;
1025 | MTL_FAST_MATH = YES;
1026 | SDKROOT = iphoneos;
1027 | SWIFT_COMPILATION_MODE = wholemodule;
1028 | SWIFT_OPTIMIZATION_LEVEL = "-O";
1029 | VALIDATE_PRODUCT = YES;
1030 | };
1031 | name = "Release Staging";
1032 | };
1033 | FDD85B9323B8E66300D7B8DD /* Release Staging */ = {
1034 | isa = XCBuildConfiguration;
1035 | baseConfigurationReference = 826C91F05D2CAE3FF0BF34EE /* Pods-iOS-MVVM-Template.release staging.xcconfig */;
1036 | buildSettings = {
1037 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
1038 | CODE_SIGN_STYLE = Automatic;
1039 | FRAMEWORK_SEARCH_PATHS = (
1040 | "$(inherited)",
1041 | "$(PROJECT_DIR)/Carthage/Build/iOS",
1042 | );
1043 | INFOPLIST_FILE = "iOS-MVVM-Template/Info.plist";
1044 | IPHONEOS_DEPLOYMENT_TARGET = 12.0;
1045 | IS_STAGING = YES;
1046 | LD_RUNPATH_SEARCH_PATHS = (
1047 | "$(inherited)",
1048 | "@executable_path/Frameworks",
1049 | );
1050 | PRODUCT_BUNDLE_IDENTIFIER = "com.lebron.iOS-MVVM-Template";
1051 | PRODUCT_NAME = "$(TARGET_NAME)";
1052 | SWIFT_VERSION = 5.0;
1053 | TARGETED_DEVICE_FAMILY = "1,2";
1054 | };
1055 | name = "Release Staging";
1056 | };
1057 | FDD85B9423B8E66300D7B8DD /* Release Staging */ = {
1058 | isa = XCBuildConfiguration;
1059 | buildSettings = {
1060 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
1061 | BUNDLE_LOADER = "$(TEST_HOST)";
1062 | CLANG_ENABLE_MODULES = YES;
1063 | CODE_SIGN_STYLE = Automatic;
1064 | INFOPLIST_FILE = "iOS-MVVM-TemplateTests/Info.plist";
1065 | IPHONEOS_DEPLOYMENT_TARGET = 13.2;
1066 | LD_RUNPATH_SEARCH_PATHS = (
1067 | "$(inherited)",
1068 | "@executable_path/Frameworks",
1069 | "@loader_path/Frameworks",
1070 | );
1071 | PRODUCT_BUNDLE_IDENTIFIER = "com.lebron.iOS-MVVM-TemplateTests";
1072 | PRODUCT_NAME = "$(TARGET_NAME)";
1073 | SWIFT_VERSION = 5.0;
1074 | TARGETED_DEVICE_FAMILY = "1,2";
1075 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/iOS-MVVM-Template.app/iOS-MVVM-Template";
1076 | };
1077 | name = "Release Staging";
1078 | };
1079 | /* End XCBuildConfiguration section */
1080 |
1081 | /* Begin XCConfigurationList section */
1082 | FDD85AA723B4BB6B00D7B8DD /* Build configuration list for PBXProject "iOS-MVVM-Template" */ = {
1083 | isa = XCConfigurationList;
1084 | buildConfigurations = (
1085 | FDD85AC923B4BB6D00D7B8DD /* Debug */,
1086 | FDD85B8F23B8E65A00D7B8DD /* Debug Staging */,
1087 | FDD85ACA23B4BB6D00D7B8DD /* Release */,
1088 | FDD85B9223B8E66300D7B8DD /* Release Staging */,
1089 | );
1090 | defaultConfigurationIsVisible = 0;
1091 | defaultConfigurationName = Release;
1092 | };
1093 | FDD85ACB23B4BB6D00D7B8DD /* Build configuration list for PBXNativeTarget "iOS-MVVM-Template" */ = {
1094 | isa = XCConfigurationList;
1095 | buildConfigurations = (
1096 | FDD85ACC23B4BB6D00D7B8DD /* Debug */,
1097 | FDD85B9023B8E65A00D7B8DD /* Debug Staging */,
1098 | FDD85ACD23B4BB6D00D7B8DD /* Release */,
1099 | FDD85B9323B8E66300D7B8DD /* Release Staging */,
1100 | );
1101 | defaultConfigurationIsVisible = 0;
1102 | defaultConfigurationName = Release;
1103 | };
1104 | FDD85ACE23B4BB6D00D7B8DD /* Build configuration list for PBXNativeTarget "iOS-MVVM-TemplateTests" */ = {
1105 | isa = XCConfigurationList;
1106 | buildConfigurations = (
1107 | FDD85ACF23B4BB6D00D7B8DD /* Debug */,
1108 | FDD85B9123B8E65A00D7B8DD /* Debug Staging */,
1109 | FDD85AD023B4BB6D00D7B8DD /* Release */,
1110 | FDD85B9423B8E66300D7B8DD /* Release Staging */,
1111 | );
1112 | defaultConfigurationIsVisible = 0;
1113 | defaultConfigurationName = Release;
1114 | };
1115 | /* End XCConfigurationList section */
1116 | };
1117 | rootObject = FDD85AA423B4BB6B00D7B8DD /* Project object */;
1118 | }
1119 |
--------------------------------------------------------------------------------