├── MVVM
├── Assets.xcassets
│ ├── Contents.json
│ ├── NoData.imageset
│ │ ├── NoData.png
│ │ └── Contents.json
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── Extension
│ ├── ArrayExtension.swift
│ ├── Tools.swift
│ └── UIColorExtension.swift
├── Model
│ ├── ZipModel.swift
│ ├── GarbageStationInfoModel.swift
│ └── ClassModel.swift
├── View
│ ├── TableViewCell
│ │ ├── SchoolDataCell.swift
│ │ └── GarbageStationInfoCell.swift
│ └── CollectionViewCell
│ │ └── ZipCollectionViewCell.swift
├── Base
│ ├── Protocol
│ │ ├── ViewModelStatusEnum.swift
│ │ ├── ViewModelDelegate.swift
│ │ ├── ViewModelProtocol.swift
│ │ ├── BaseDataProtocol.swift
│ │ ├── ViewModelMultipleContentDataProtocol.swift
│ │ └── JsonProtocol.swift
│ ├── View
│ │ ├── NoDataView.swift
│ │ └── EmptyView.swift
│ ├── Error.swift
│ ├── ViewController
│ │ └── BaseVC.swift
│ └── ViewModel
│ │ └── BaseViewModel.swift
├── ViewModel
│ ├── ZipViewModel.swift
│ ├── SchoolDataViewModel.swift
│ └── GarbageStationInfoViewModel.swift
├── AppDelegate.swift
├── data.json
├── ViewController
│ ├── MainVC.swift
│ ├── GarbageStationInfoVC.swift
│ ├── ZipVC.swift
│ └── SchoolDataVC.swift
├── Info.plist
├── Base.lproj
│ └── LaunchScreen.storyboard
└── Services
│ └── WebService
│ ├── ChlURLProtocol.swift
│ └── WebService.swift
├── MVVM.xcodeproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
└── project.pbxproj
├── Podfile
├── .swiftlint.yml
├── MVVM.xcworkspace
└── contents.xcworkspacedata
├── Podfile.lock
├── LICENSE
├── README.md
├── .gitignore
└── sortXcodeProject.pl
/MVVM/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/MVVM/Assets.xcassets/NoData.imageset/NoData.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nick6969/MVVM/HEAD/MVVM/Assets.xcassets/NoData.imageset/NoData.png
--------------------------------------------------------------------------------
/MVVM.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Podfile:
--------------------------------------------------------------------------------
1 | platform :ios, '9.0'
2 |
3 | inhibit_all_warnings!
4 |
5 | target 'MVVM' do
6 |
7 | use_frameworks!
8 |
9 | pod 'Alamofire', '4.7.1'
10 | pod 'mLayout', '0.1.2'
11 | pod 'SVProgressHUD', '2.2.5'
12 |
13 | end
14 |
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | disabled_rules:
2 | # you can use - 'swiftlint rules' find
3 | - trailing_whitespace
4 | - force_cast
5 | opt_in_rules:
6 | # you can use - 'swiftlint rules' find
7 |
8 | included:
9 |
10 | excluded:
11 | - Pods
12 |
13 | line_length: 300
14 | function_body_length: 100
--------------------------------------------------------------------------------
/MVVM.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/MVVM/Extension/ArrayExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ArrayExtension.swift
3 | // MVVM
4 | //
5 | // Created by Nick Lin on 2018/4/7.
6 | // Copyright © 2018年 Nick Lin. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Array {
12 | subscript(safe index: Int) -> Element? {
13 | return (0 <= index && index < count) ? self[index] : nil
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/MVVM/Model/ZipModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ZipModel.swift
3 | // MVVM
4 | //
5 | // Created by Nick Lin on 2018/4/3.
6 | // Copyright © 2018年 Nick Lin. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct ZipModel: JsonModel {
12 | let district, zipcode: String
13 |
14 | enum CodingKeys: String, CodingKey {
15 | case district = "District"
16 | case zipcode
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/MVVM/View/TableViewCell/SchoolDataCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SchoolDataCell.swift
3 | // MVVM
4 | //
5 | // Created by Nick Lin on 2018/4/7.
6 | // Copyright © 2018年 Nick Lin. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class SchoolDataCell: UITableViewCell {
12 |
13 | func setup(with model: StudentModel) {
14 | textLabel?.text = "座號: \(model.seatNumber), 姓名: \(model.name)"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/MVVM/Assets.xcassets/NoData.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "NoData.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/MVVM/Extension/Tools.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Tools.swift
3 | // MVVM
4 | //
5 | // Created by Nick Lin on 2018/4/3.
6 | // Copyright © 2018年 Nick Lin. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public func print(msg: T, file: String = #file, method: String = #function, line: Int = #line) {
12 | #if DEBUG
13 | Swift.print("\((file as NSString).lastPathComponent)[\(line)], \(method): \(msg)")
14 | #endif
15 | }
16 |
--------------------------------------------------------------------------------
/MVVM/View/TableViewCell/GarbageStationInfoCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GarbageStationInfoCell.swift
3 | // MVVM
4 | //
5 | // Created by Nick Lin on 2018/4/3.
6 | // Copyright © 2018年 Nick Lin. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class GarbageStationInfoCell: UITableViewCell {
12 |
13 | func setup(with model: GarbageStationInfoModel) {
14 | textLabel?.text = model.linename + ", " + model.time + ", " + model.name
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/MVVM/Base/Protocol/ViewModelStatusEnum.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewModelStatusEnum.swift
3 | // MVVM
4 | //
5 | // Created by Nick Lin on 2018/4/3.
6 | // Copyright © 2018年 Nick Lin. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | enum ViewModelStatus {
12 | case initialize
13 | case loadStart
14 | case loadDone
15 | case loadFail
16 | case loadMoreStart
17 | case loadMoreDone
18 | case loadMoreFail
19 | case noMoreCanLoad
20 | case refreshLoading
21 | }
22 |
--------------------------------------------------------------------------------
/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Alamofire (4.7.1)
3 | - mLayout (0.1.2)
4 | - SVProgressHUD (2.2.5)
5 |
6 | DEPENDENCIES:
7 | - Alamofire (= 4.7.1)
8 | - mLayout (= 0.1.2)
9 | - SVProgressHUD (= 2.2.5)
10 |
11 | SPEC CHECKSUMS:
12 | Alamofire: 68d7d521118d49c615a8d2214d87cdf525599d30
13 | mLayout: 7fa679d0da8636d2f93a73a41acf1c860190dc4d
14 | SVProgressHUD: 1428aafac632c1f86f62aa4243ec12008d7a51d6
15 |
16 | PODFILE CHECKSUM: c9da9087f274c2cdb994940953fb6dfc851f65ec
17 |
18 | COCOAPODS: 1.4.0
19 |
--------------------------------------------------------------------------------
/MVVM/ViewModel/ZipViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ZipViewModel.swift
3 | // MVVM
4 | //
5 | // Created by Nick Lin on 2018/4/3.
6 | // Copyright © 2018年 Nick Lin. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class ZipViewModel: BaseViewModel, StandardViewModel {
12 |
13 | func loadData() {
14 | WebService.shared.getZipData(success: dataConvertToModelsClosure, fail: loadingFailClosure)
15 | }
16 |
17 | func loadDataMore() {
18 | status = .noMoreCanLoad
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/MVVM/Base/Protocol/ViewModelDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewModelDelegate.swift
3 | // MVVM
4 | //
5 | // Created by Nick Lin on 2018/4/3.
6 | // Copyright © 2018年 Nick Lin. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol ViewModelLoadingDelegate: class {
12 | func loadingDone()
13 | func loadingFail(_ error: Error?)
14 | }
15 |
16 | protocol ViewModelLoadingStatusDelegate: class {
17 | func showEmptyView(with: Error?)
18 | func removeEmptyView()
19 | func showLoading(_ bool: Bool)
20 | }
21 |
--------------------------------------------------------------------------------
/MVVM/Model/GarbageStationInfoModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GarbageStationInfoModel.swift
3 | // MVVM
4 | //
5 | // Created by Nick Lin on 2018/4/3.
6 | // Copyright © 2018年 Nick Lin. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct GarbageStationInfoModel: JsonModel {
12 | let city, lineid, linename, rank, name, village, longitude, latitude, time, memo: String
13 |
14 | enum CodingKeys: String, CodingKey {
15 | case city, lineid, linename, rank, name, village, longitude, latitude, time, memo
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/MVVM/ViewModel/SchoolDataViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SchoolDataViewModel.swift
3 | // MVVM
4 | //
5 | // Created by Nick Lin on 2018/4/7.
6 | // Copyright © 2018年 Nick Lin. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class SchoolDataViewModel: BaseViewModel, StandardMultipleContentViewModel {
12 |
13 | func loadData() {
14 | WebService.shared.getSchoolData(success: dataConvertToModelsClosure, fail: loadingFailClosure)
15 | }
16 |
17 | func loadDataMore() {
18 | status = .noMoreCanLoad
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/MVVM/Model/ClassModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ClassModel.swift
3 | // MVVM
4 | //
5 | // Created by Nick Lin on 2018/4/7.
6 | // Copyright © 2018年 Nick Lin. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct ClassModel: MultipleContentProtocol {
12 | let className, tutorName: String
13 | var subModels: [StudentModel]
14 | enum CodingKeys: String, CodingKey {
15 | case subModels = "students"
16 | case className
17 | case tutorName
18 | }
19 | }
20 |
21 | struct StudentModel: JsonModel {
22 | let name: String
23 | let seatNumber: Int
24 | }
25 |
--------------------------------------------------------------------------------
/MVVM/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // MVVM
4 | //
5 | // Created by Nick Lin on 2018/4/3.
6 | // Copyright © 2018年 Nick Lin. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import mLayout
11 |
12 | @UIApplicationMain
13 | class AppDelegate: UIResponder, UIApplicationDelegate {
14 |
15 | var window: UIWindow?
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
18 |
19 | window = UIWindow(frame: UIScreen.main.bounds)
20 | window?.makeKeyAndVisible()
21 | window?.backgroundColor = .white
22 | window?.rootViewController = UINavigationController(rootViewController: MainVC())
23 |
24 | return true
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/MVVM/Extension/UIColorExtension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIColorExtension.swift
3 | // MVVM
4 | //
5 | // Created by Nick Lin on 2018/4/3.
6 | // Copyright © 2018年 Nick Lin. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIColor {
12 | convenience init(red: Int, green: Int, blue: Int) {
13 | assert(red >= 0 && red <= 255, "Invalid red component")
14 | assert(green >= 0 && green <= 255, "Invalid green component")
15 | assert(blue >= 0 && blue <= 255, "Invalid blue component")
16 |
17 | self.init(red: CGFloat(red) / 255.0, green: CGFloat(green) / 255.0, blue: CGFloat(blue) / 255.0, alpha: 1.0)
18 | }
19 |
20 | convenience init(netHex: Int) {
21 | self.init(red: (netHex >> 16) & 0xff, green: (netHex >> 8) & 0xff, blue: netHex & 0xff)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/MVVM/ViewModel/GarbageStationInfoViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GarbageStationInfoViewModel.swift
3 | // MVVM
4 | //
5 | // Created by Nick Lin on 2018/4/3.
6 | // Copyright © 2018年 Nick Lin. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class GarbageStationInfoViewModel: BaseViewModel, StandardViewModel {
12 |
13 | fileprivate var area: String = String()
14 |
15 | convenience init(with area: String) {
16 | self.init()
17 | self.area = area
18 | }
19 |
20 | func loadData() {
21 | useLoadData()
22 | }
23 |
24 | func loadDataMore() {
25 | useLoadData()
26 | }
27 |
28 | private func useLoadData() {
29 | WebService.shared.getGarbageStationInfo(with: self.area, skip: self.datasCount, success: dataConvertToModelsClosure, fail: loadingFailClosure)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Nick Lin
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MVVM/View/CollectionViewCell/ZipCollectionViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ZipCollectionViewCell.swift
3 | // MVVM
4 | //
5 | // Created by Nick Lin on 2018/4/3.
6 | // Copyright © 2018年 Nick Lin. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ZipCollectionViewCell: UICollectionViewCell {
12 |
13 | fileprivate let label = UILabel()
14 |
15 | override init(frame: CGRect) {
16 | super.init(frame: frame)
17 | setupUI()
18 | }
19 |
20 | required init?(coder aDecoder: NSCoder) {
21 | super.init(coder: aDecoder)
22 | setupUI()
23 | }
24 |
25 | private func setupUI() {
26 | contentView.addSubview(label)
27 | label.mLay(pin: .zero)
28 | label.textAlignment = .center
29 | label.font = UIFont.systemFont(ofSize: 18)
30 | contentView.layer.borderWidth = 2
31 | contentView.layer.borderColor = UIColor.init(netHex: 0x747474).cgColor
32 | contentView.layer.masksToBounds = true
33 | contentView.layer.cornerRadius = 10
34 | contentView.backgroundColor = .white
35 | }
36 |
37 | func setup(with model: ZipModel) {
38 | self.label.text = model.district
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/MVVM/data.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "className": "八年七班",
4 | "tutorName": "凌凌漆",
5 | "students": [
6 | {
7 | "name": "天字第一號",
8 | "seatNumber": 1
9 | },
10 | {
11 | "name": "雙雙對對",
12 | "seatNumber": 2
13 | },
14 | {
15 | "name": "三人成群",
16 | "seatNumber": 3
17 | },
18 | {
19 | "name": "亞洲四小龍",
20 | "seatNumber": 4
21 | }
22 | ]
23 | },
24 | {
25 | "className": "一年二班",
26 | "tutorName": "黑黑黑",
27 | "students": [
28 | {
29 | "name": "😱😱😱",
30 | "seatNumber": 1
31 | },
32 | {
33 | "name": "💀💀💀💀",
34 | "seatNumber": 2
35 | },
36 | {
37 | "name": "😡😡😡😡",
38 | "seatNumber": 3
39 | },
40 | {
41 | "name": "🐽🐽🐽🐽",
42 | "seatNumber": 4
43 | },
44 | {
45 | "name": "🌪🌪🌪🌪",
46 | "seatNumber": 5
47 | }
48 | ]
49 | }
50 |
51 | ]
--------------------------------------------------------------------------------
/MVVM/Base/View/NoDataView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NoDataView.swift
3 | // MVVM
4 | //
5 | // Created by Nick Lin on 2018/4/3.
6 | // Copyright © 2018年 Nick Lin. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class NoDataView: UIView {
12 | let imageView = UIImageView()
13 | let messageLabel = UILabel()
14 | required init?(coder aDecoder: NSCoder) {
15 | super.init(coder: aDecoder)
16 | setupUI()
17 | }
18 |
19 | override init(frame: CGRect) {
20 | super.init(frame: frame)
21 | setupUI()
22 | }
23 |
24 | private func setupUI() {
25 | backgroundColor = UIColor.init(netHex: 0xf6f6f6)
26 | addSubview(imageView)
27 | addSubview(messageLabel)
28 | messageLabel.font = UIFont.systemFont(ofSize: 18)
29 | messageLabel.textAlignment = .center
30 | messageLabel.text = "目前沒有任何內容"
31 | messageLabel.numberOfLines = 1
32 | imageView.image = UIImage(named: "NoData")
33 | imageView.contentMode = .scaleAspectFit
34 |
35 | imageView.mLay(size: CGSize(width: 180, height: 100))
36 | imageView.mLay(.centerX, .equal, self)
37 | imageView.mLay(.centerY, .equal, self, multiplier: 0.8, constant: 0)
38 |
39 | messageLabel.mLay(.top, .equal, self, .centerY, multiplier: 1.2, constant: 0)
40 | messageLabel.mLay(.centerX, .equal, self)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 關於
2 |
3 | 這是一個 MVVM 的展示專案
4 |
5 | ViewModel 使用 POP & OOP 建立
6 |
7 | 方便使用,並且減少大量重複的 Code
8 |
9 |
10 | --
11 |
12 | # 架構
13 |
14 | ### Model
15 | Just Model
16 |
17 | --------------
18 |
19 | ### ViewModel
20 | #### 持有 Models
21 | var models: [Model]
22 |
23 | #### 有自己的狀態
24 | var status: ViewModelStatus
25 |
26 | #### 負責取得資料(Web Api / Locale DB)
27 | func loadData()
28 | func loadDataMode()
29 |
30 | #### 負責通知 ViewController Loading 狀態
31 | var loadingDelegate: ViewModelLoadingDelegate?
32 | var loadingStatusDelegate: ViewModelLoadingStatusDelegate?
33 |
34 | #### 提供 Method 讓 ViewController 取用 Model
35 | var datasCount: Int
36 | func model(at index: Int) -> Model?
37 | func isLastData(index: Int) -> Bool
38 | func isLoadMore(index: Int) -> Bool
39 |
40 | #### 提供 Method 讓 ViewController 呼叫做事
41 | func refreshData()
42 | func nextStatus()
43 |
44 | --------------
45 |
46 | ### ViewController
47 | #### 持有 ViewModel
48 | var viewModel: BaseViewModel
49 |
50 | #### conform ViewModel 的 Delegate
51 | extension ViewController: ViewModelLoadingDelegate
52 |
53 | func loadingDone()
54 | func loadingFail(_ error: Error?)
55 |
56 | extension ViewController: ViewModelLoadingStatusDelegate
57 |
58 | func showEmptyView(with: Error?)
59 | func removeEmptyView()
60 | func showLoading(_ bool: Bool)
61 |
62 | #### 請 ViewModel 做事
63 | viewModel.nextStatus()
64 | viewModel.refreshData()
65 |
--------------------------------------------------------------------------------
/MVVM/Base/Protocol/ViewModelProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewModelProtocol.swift
3 | // MVVM
4 | //
5 | // Created by Nick Lin on 2018/4/3.
6 | // Copyright © 2018年 Nick Lin. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | typealias StandardViewModel = ViewModelDataProtocol & ViewModelLoadingProtocol & ViewModelLoadingFuncProtocol
12 |
13 | protocol ViewModelDataProtocol: BaseDataProtocol where Model: JsonModel {
14 | var datasCount: Int { get }
15 | func model(at index: Int) -> Model?
16 | func isLastData(index: Int) -> Bool
17 | func isLoadMore(index: Int) -> Bool
18 | }
19 |
20 | extension ViewModelDataProtocol {
21 |
22 | var datasCount: Int { return models.count }
23 |
24 | func model(at index: Int) -> Model? {
25 | return models[safe: index]
26 | }
27 |
28 | func isLastData(index: Int) -> Bool {
29 | return index + 1 == datasCount
30 | }
31 |
32 | func isLoadMore(index: Int) -> Bool {
33 | if models.isEmpty { return false }
34 | return index == datasCount
35 | }
36 |
37 | }
38 |
39 | protocol ViewModelLoadingProtocol: class {
40 | var loadingDelegate: ViewModelLoadingDelegate? {get set}
41 | var loadingStatusDelegate: ViewModelLoadingStatusDelegate? {get set}
42 | var status: ViewModelStatus { get set }
43 | func refreshData()
44 | func nextStatus()
45 | }
46 |
47 | protocol ViewModelLoadingFuncProtocol: class {
48 | func loadData()
49 | func loadDataMore()
50 | }
51 |
--------------------------------------------------------------------------------
/MVVM/Base/Protocol/BaseDataProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BaseDataProtocol.swift
3 | // MVVM
4 | //
5 | // Created by Nick Lin on 2018/4/10.
6 | // Copyright © 2018年 Nick Lin. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol BaseDataProtocol: class {
12 | associatedtype Model
13 | var models: [Model] { get set }
14 | }
15 |
16 | extension ViewModelLoadingProtocol where Self: BaseDataProtocol, Self: ViewModelLoadingFuncProtocol {
17 |
18 | func refreshData() {
19 | if status != .loadStart {
20 | status = .refreshLoading
21 | nextStatus()
22 | }
23 | }
24 |
25 | func nextStatus() {
26 | switch status {
27 | case .initialize, .loadFail:
28 | status = .loadStart
29 | if models.isEmpty {
30 | DispatchQueue.main.async {
31 | self.loadingStatusDelegate?.showLoading(true)
32 | }
33 | }
34 | loadData()
35 |
36 | case .loadDone, .loadMoreDone, .loadMoreFail:
37 | status = .loadMoreStart
38 | loadDataMore()
39 |
40 | case .refreshLoading:
41 | status = .loadStart
42 | models = []
43 | DispatchQueue.main.async {
44 | self.loadingStatusDelegate?.showLoading(true)
45 | }
46 | loadData()
47 |
48 | case .loadStart, .loadMoreStart, .noMoreCanLoad:
49 | break
50 | }
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/MVVM/Base/Protocol/ViewModelMultipleContentDataProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewModelMultipleContentDataProtocol.swift
3 | // MVVM
4 | //
5 | // Created by Nick Lin on 2018/4/7.
6 | // Copyright © 2018年 Nick Lin. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | typealias StandardMultipleContentViewModel = ViewModelMultipleContentDataProtocol & ViewModelLoadingProtocol & ViewModelLoadingFuncProtocol
12 |
13 | protocol ViewModelMultipleContentDataProtocol: BaseDataProtocol where Model: MultipleContentProtocol {
14 | func numberOfSection() -> Int
15 | func numberOfItem(in section: Int) -> Int
16 | func modelAt(section: Int) -> Model?
17 | func modelAt(indexPath: IndexPath) -> Model.SubModel?
18 | func isLastData(index: IndexPath) -> Bool
19 | }
20 |
21 | extension ViewModelMultipleContentDataProtocol {
22 |
23 | func numberOfSection() -> Int {
24 | return models.count
25 | }
26 |
27 | func numberOfItem(in section: Int) -> Int {
28 | return modelAt(section: section)?.subModels.count ?? 0
29 | }
30 |
31 | func modelAt(section: Int) -> Model? {
32 | return models[safe: section]
33 | }
34 |
35 | func modelAt(indexPath: IndexPath) -> Model.SubModel? {
36 | return models[safe: indexPath.section]?.subModels[safe: indexPath.item]
37 | }
38 |
39 | func isLastData(index: IndexPath) -> Bool {
40 | return index.section + 1 == models.count && index.item + 1 == numberOfItem(in: index.section)
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/MVVM/Base/View/EmptyView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EmptyView.swift
3 | // MVVM
4 | //
5 | // Created by Nick Lin on 2018/4/3.
6 | // Copyright © 2018年 Nick Lin. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class EmptyView: UIView {
12 |
13 | let messageLabel = UILabel()
14 | let reloadButton = UIButton()
15 |
16 | required init?(coder aDecoder: NSCoder) {
17 | super.init(coder: aDecoder)
18 | setupUI()
19 | }
20 |
21 | override init(frame: CGRect) {
22 | super.init(frame: frame)
23 | setupUI()
24 | }
25 |
26 | private func setupUI() {
27 | backgroundColor = UIColor.init(netHex: 0xf6f6f6)
28 | addSubview(messageLabel)
29 | addSubview(reloadButton)
30 |
31 | messageLabel.font = UIFont.systemFont(ofSize: 18)
32 | messageLabel.textAlignment = .center
33 | messageLabel.text = "連線異常,請重新嘗試"
34 | messageLabel.numberOfLines = 2
35 |
36 | reloadButton.backgroundColor = UIColor.red
37 | reloadButton.setTitle("重新整理", for: .normal)
38 | reloadButton.titleLabel?.font = UIFont.systemFont(ofSize: 18)
39 |
40 | messageLabel.mLay(.centerX, .equal, self)
41 | messageLabel.mLay(.width, 300)
42 | messageLabel.mLay(.bottom, .equal, self, .centerY, multiplier: 1, constant: -10)
43 |
44 | reloadButton.mLay(.centerX, .equal, self)
45 | reloadButton.mLay(.width, 300)
46 | reloadButton.mLay(.top, .equal, self, .centerY, multiplier: 1, constant: 10)
47 | reloadButton.mLay(.height, 50)
48 |
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/MVVM/ViewController/MainVC.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MainVC.swift
3 | // MVVM
4 | //
5 | // Created by Nick Lin on 2018/4/3.
6 | // Copyright © 2018年 Nick Lin. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class MainVC: UIViewController {
12 |
13 | fileprivate lazy var button: UIButton = {
14 | let button = UIButton()
15 | button.backgroundColor = .red
16 | button.setTitle("新北市垃圾車路線", for: .normal)
17 | button.addTarget(self, action: #selector(didTapButton), for: .touchUpInside)
18 | return button
19 | }()
20 |
21 | fileprivate lazy var schoolButton: UIButton = {
22 | let button = UIButton()
23 | button.backgroundColor = .red
24 | button.setTitle("學校資料", for: .normal)
25 | button.addTarget(self, action: #selector(didTapSchoolButton), for: .touchUpInside)
26 | return button
27 | }()
28 |
29 | override func viewDidLoad() {
30 | super.viewDidLoad()
31 | view.addSubview(button)
32 | button.mLayCenterXY()
33 | button.mLay(size: CGSize(width: 180, height: 50))
34 |
35 | view.addSubview(schoolButton)
36 | schoolButton.mLay(.centerX, .equal, view)
37 | schoolButton.mLay(size: CGSize(width: 180, height: 50))
38 | schoolButton.mLay(.centerY, .equal, view, multiplier: 1.5, constant: 0)
39 | }
40 |
41 | @objc func didTapButton() {
42 | self.navigationController?.pushViewController(ZipVC(), animated: true)
43 | }
44 |
45 | @objc func didTapSchoolButton() {
46 | self.navigationController?.pushViewController(SchoolDataVC(), animated: true)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/MVVM/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | NSAppTransportSecurity
24 |
25 | NSAllowsArbitraryLoads
26 |
27 |
28 | UILaunchStoryboardName
29 | LaunchScreen
30 | UIRequiredDeviceCapabilities
31 |
32 | armv7
33 |
34 | UISupportedInterfaceOrientations
35 |
36 | UIInterfaceOrientationPortrait
37 | UIInterfaceOrientationLandscapeLeft
38 | UIInterfaceOrientationLandscapeRight
39 |
40 | UISupportedInterfaceOrientations~ipad
41 |
42 | UIInterfaceOrientationPortrait
43 | UIInterfaceOrientationPortraitUpsideDown
44 | UIInterfaceOrientationLandscapeLeft
45 | UIInterfaceOrientationLandscapeRight
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xccheckout
23 | *.xcscmblueprint
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 | *.ipa
28 | *.dSYM.zip
29 | *.dSYM
30 |
31 | ## Playgrounds
32 | timeline.xctimeline
33 | playground.xcworkspace
34 |
35 | # Swift Package Manager
36 | #
37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
38 | # Packages/
39 | # Package.pins
40 | .build/
41 |
42 | # CocoaPods
43 | #
44 | # We recommend against adding the Pods directory to your .gitignore. However
45 | # you should judge for yourself, the pros and cons are mentioned at:
46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
47 | #
48 | Pods/
49 |
50 | # Carthage
51 | #
52 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
53 | # Carthage/Checkouts
54 |
55 | Carthage/Build
56 |
57 | # fastlane
58 | #
59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
60 | # screenshots whenever they are needed.
61 | # For more information about the recommended setup visit:
62 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
63 |
64 | fastlane/report.xml
65 | fastlane/Preview.html
66 | fastlane/screenshots
67 | fastlane/test_output
68 |
--------------------------------------------------------------------------------
/MVVM/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 |
--------------------------------------------------------------------------------
/MVVM/Base/Protocol/JsonProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // JsonProtocol.swift
3 | // MVVM
4 | //
5 | // Created by Nick Lin on 2018/4/3.
6 | // Copyright © 2018年 Nick Lin. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | typealias JsonModel = Codable & JsonProtocol
12 |
13 | protocol MultipleContentProtocol: JsonModel {
14 | associatedtype SubModel: JsonModel
15 | var subModels: [SubModel] {get set}
16 | }
17 |
18 | protocol JsonProtocol {
19 | static func decodeJSON(data: Data?) throws -> Self
20 | func encodeToJSON() throws -> Data
21 | }
22 |
23 | extension JsonProtocol where Self: Codable {
24 |
25 | static func decodeJSON(data: Data?) throws -> Self {
26 | guard let data = data else { throw JSONConvertError.noDataError }
27 | do {
28 | return try JSONDecoder().decode(self, from: data)
29 | } catch {
30 | throw error
31 | }
32 | }
33 |
34 | func encodeToJSON() throws -> Data {
35 | do {
36 | return try JSONEncoder().encode(self)
37 | } catch {
38 | throw error
39 | }
40 | }
41 | }
42 |
43 | extension Data {
44 | func decodeToModel(type: T.Type) throws -> T {
45 | do {
46 | return try T.decodeJSON(data: self)
47 | } catch {
48 | throw error
49 | }
50 | }
51 |
52 | func decodeToModelArray(type: T.Type) throws -> [T] {
53 | do {
54 | return try JSONDecoder().decode([T].self, from: self)
55 | } catch {
56 | throw error
57 | }
58 | }
59 | }
60 |
61 | extension Array where Element: JsonModel {
62 | func encodeToJSON() throws -> Data {
63 | do {
64 | return try JSONEncoder().encode(self)
65 | } catch {
66 | throw error
67 | }
68 | }
69 | }
70 |
71 | extension Data {
72 |
73 | @nonobjc var string: String? {
74 | return String(data: self, encoding: .utf8)
75 | }
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/MVVM/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 | }
--------------------------------------------------------------------------------
/MVVM/Base/Error.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Error.swift
3 | // MVVM
4 | //
5 | // Created by Nick Lin on 2018/4/3.
6 | // Copyright © 2018年 Nick Lin. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | enum JSONConvertError: Error, LocalizedError, CustomNSError {
12 | case noDataError
13 |
14 | var localizedDescription: String {
15 | return "JSON Convert Error"
16 | }
17 |
18 | public var errorDescription: String? {
19 | switch self {
20 | case .noDataError: return "無輸入資料"
21 | }
22 | }
23 |
24 | public static var errorDomain: String {
25 | return "JSON Convert Error"
26 | }
27 |
28 | public var errorCode: Int {
29 | switch self {
30 | case .noDataError: return 8000
31 | }
32 | }
33 |
34 | public var errorUserInfo: [String: Any] {
35 | switch self {
36 | case .noDataError: return [:]
37 | }
38 | }
39 | }
40 |
41 | enum WebError: Error, LocalizedError, CustomNSError {
42 | static let url = "url"
43 | static let heads = "httpHeads"
44 | static let body = "httpBody"
45 |
46 | case convertFail(userInfo: [String: Any])
47 | case unKnowError(userInfo: [String: Any])
48 |
49 | var localizedDescription: String {
50 | switch self {
51 | case .convertFail: return "網路請求錯誤 - 伺服器回傳資料型別錯誤"
52 | case .unKnowError: return "網路請求錯誤 - 未知錯誤"
53 | }
54 | }
55 |
56 | public var errorDescription: String? {
57 | switch self {
58 | case .convertFail: return "網路請求錯誤 - 伺服器回傳資料型別錯誤"
59 | case .unKnowError: return "網路請求錯誤 - 未知錯誤"
60 | }
61 | }
62 |
63 | public static var errorDomain: String {
64 | return "網路請求錯誤"
65 | }
66 |
67 | public var errorCode: Int {
68 | switch self {
69 | case .convertFail: return 9000
70 | case .unKnowError: return 9001
71 | }
72 | }
73 |
74 | public var errorUserInfo: [String: Any] {
75 | switch self {
76 | case .convertFail(let dic): return dic
77 | case .unKnowError(let dic): return dic
78 | }
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/MVVM/ViewController/GarbageStationInfoVC.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GarbageStationInfoVC.swift
3 | // MVVM
4 | //
5 | // Created by Nick Lin on 2018/4/3.
6 | // Copyright © 2018年 Nick Lin. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class GarbageStationInfoVC: BaseVC {
12 |
13 | fileprivate lazy var viewModel: GarbageStationInfoViewModel = GarbageStationInfoViewModel()
14 |
15 | fileprivate let tableView: UITableView = UITableView()
16 |
17 | convenience init(with area: String) {
18 | self.init()
19 | self.viewModel = GarbageStationInfoViewModel(with: area)
20 | self.title = area
21 | }
22 |
23 | override func viewDidLoad() {
24 | super.viewDidLoad()
25 | setupUI()
26 | viewModel.loadingDelegate = self
27 | viewModel.loadingStatusDelegate = self
28 | viewModel.nextStatus()
29 | }
30 |
31 | private func setupUI() {
32 | view.addSubview(tableView)
33 | tableView.mLay(pin: .zero)
34 | tableView.dataSource = self
35 | tableView.delegate = self
36 | tableView.register(GarbageStationInfoCell.self, forCellReuseIdentifier: "cell")
37 | }
38 |
39 | override func didTapReloadButton() {
40 | viewModel.refreshData()
41 | }
42 | }
43 |
44 | extension GarbageStationInfoVC: UITableViewDataSource, UITableViewDelegate {
45 |
46 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
47 | return viewModel.datasCount
48 | }
49 |
50 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
51 | let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! GarbageStationInfoCell
52 | if let model = viewModel.model(at: indexPath.row) {
53 | cell.setup(with: model)
54 | }
55 | return cell
56 | }
57 |
58 | func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
59 | if viewModel.isLastData(index: indexPath.row) {
60 | viewModel.nextStatus()
61 | }
62 | }
63 | }
64 |
65 | extension GarbageStationInfoVC: ViewModelLoadingDelegate {
66 |
67 | func loadingDone() {
68 | tableView.reloadData()
69 | }
70 |
71 | func loadingFail(_ error: Error?) {
72 |
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/MVVM/Base/ViewController/BaseVC.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BaseVC.swift
3 | // MVVM
4 | //
5 | // Created by Nick Lin on 2018/4/3.
6 | // Copyright © 2018年 Nick Lin. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import SVProgressHUD
11 |
12 | class BaseVC: UIViewController {
13 | fileprivate var emptyView: EmptyView?
14 | fileprivate var noDataView: NoDataView?
15 | }
16 |
17 | extension BaseVC: ViewModelLoadingStatusDelegate {
18 | func showLoading(_ bool: Bool) {
19 |
20 | if bool {
21 | DispatchQueue.main.async {
22 | SVProgressHUD.show()
23 | }
24 | } else {
25 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
26 | SVProgressHUD.dismiss()
27 | }
28 | }
29 | }
30 |
31 | func showEmptyView(with error: Error?) {
32 | if error != nil {
33 | if emptyView == nil {
34 | let emptyView = EmptyView()
35 | emptyView.alpha = 0
36 | emptyView.reloadButton.addTarget(self, action: #selector(didTapReloadButton), for: .touchUpInside)
37 | self.emptyView = emptyView
38 | self.view.addSubview(emptyView)
39 | emptyView.mLay(pin: .zero)
40 | self.view.bringSubview(toFront: emptyView)
41 | }
42 | UIView.animate(withDuration: 0.2) {
43 | self.emptyView?.alpha = 1
44 | }
45 | } else {
46 | if noDataView == nil {
47 | let noDataView = NoDataView()
48 | noDataView.alpha = 0
49 | self.noDataView = noDataView
50 | self.view.addSubview(noDataView)
51 | noDataView.mLay(pin: .zero)
52 | self.view.bringSubview(toFront: noDataView)
53 | }
54 | UIView.animate(withDuration: 0.2, animations: {
55 | self.noDataView?.alpha = 1
56 | })
57 | }
58 | }
59 |
60 | func removeEmptyView() {
61 | if emptyView != nil {
62 | UIView.animate(withDuration: 0.2, animations: {
63 | self.emptyView?.alpha = 0
64 | self.emptyView?.removeFromSuperview()
65 | })
66 | }
67 | if noDataView != nil {
68 | UIView.animate(withDuration: 0.2, animations: {
69 | self.noDataView?.alpha = 0
70 | self.noDataView?.removeFromSuperview()
71 | })
72 | }
73 | }
74 |
75 | @objc func didTapReloadButton() {
76 | assert(false, "if sub class need using emptyView, remeber override this method :\(#function)")
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/MVVM/ViewController/ZipVC.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ZipVC.swift
3 | // MVVM
4 | //
5 | // Created by Nick Lin on 2018/4/3.
6 | // Copyright © 2018年 Nick Lin. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ZipVC: BaseVC {
12 |
13 | fileprivate lazy var collectionView: UICollectionView = UICollectionView(frame: .zero, collectionViewLayout: self.flowLayout)
14 | fileprivate let flowLayout = UICollectionViewFlowLayout()
15 | fileprivate lazy var viewModel: ZipViewModel = ZipViewModel()
16 |
17 | override func viewDidLoad() {
18 | super.viewDidLoad()
19 | setupUI()
20 | setupFlowLayout()
21 | viewModel.loadingDelegate = self
22 | viewModel.loadingStatusDelegate = self
23 | viewModel.nextStatus()
24 | }
25 |
26 | private func setupUI() {
27 | view.addSubview(collectionView)
28 | collectionView.mLay(pin: .zero)
29 | collectionView.backgroundColor = .white
30 | collectionView.delegate = self
31 | collectionView.dataSource = self
32 | collectionView.register(ZipCollectionViewCell.self, forCellWithReuseIdentifier: "cell")
33 | }
34 |
35 | private func setupFlowLayout() {
36 | flowLayout.sectionInset = .init(top: 10, left: 10, bottom: 20, right: 10)
37 | flowLayout.minimumLineSpacing = 10
38 | flowLayout.minimumInteritemSpacing = 10
39 | let width = CGFloat(Int((UIScreen.main.bounds.width - 40) / 3))
40 | flowLayout.itemSize = CGSize(width: width, height: 50)
41 | }
42 |
43 | override func didTapReloadButton() {
44 | viewModel.refreshData()
45 | }
46 | }
47 |
48 | extension ZipVC: UICollectionViewDataSource, UICollectionViewDelegate {
49 |
50 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
51 | return viewModel.datasCount
52 | }
53 |
54 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
55 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! ZipCollectionViewCell
56 | if let model = viewModel.model(at: indexPath.item) {
57 | cell.setup(with: model)
58 | }
59 | return cell
60 | }
61 |
62 | func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
63 | guard let model = viewModel.model(at: indexPath.item) else { return }
64 | let vc = GarbageStationInfoVC(with: model.district)
65 | self.navigationController?.pushViewController(vc, animated: true)
66 | }
67 | }
68 |
69 | extension ZipVC: ViewModelLoadingDelegate {
70 |
71 | func loadingDone() {
72 | collectionView.reloadData()
73 | }
74 |
75 | func loadingFail(_ error: Error?) {
76 |
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/MVVM/Services/WebService/ChlURLProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChlURLProtocol.swift
3 | // MVVM
4 | //
5 | // Created by Nick Lin on 2018/4/3.
6 | // Copyright © 2018年 Nick Lin. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class ChlURLProtocol: URLProtocol, URLSessionDataDelegate {
12 |
13 | fileprivate var session: URLSession?
14 | fileprivate var sessionTask: URLSessionTask?
15 |
16 | fileprivate var clientThread: Thread?
17 |
18 | override class func canInit(with request: URLRequest) -> Bool {
19 | if URLProtocol.property(forKey: "process", in: request) != nil {
20 | return false
21 | }
22 | return true
23 | }
24 |
25 | override class func canInit(with task: URLSessionTask) -> Bool {
26 | guard let request = task.originalRequest else {
27 | return false
28 | }
29 |
30 | if URLProtocol.property(forKey: "process", in: request) != nil {
31 | return false
32 | }
33 | return true
34 | }
35 |
36 | override class func canonicalRequest(for request: URLRequest) -> URLRequest {
37 |
38 | let newRequest = request as! NSMutableURLRequest
39 | URLProtocol.setProperty("process", forKey: "process", in: newRequest)
40 | return newRequest as URLRequest
41 | }
42 |
43 | override func startLoading() {
44 |
45 | clientThread = Thread.current
46 |
47 | session = URLSession(configuration: .default, delegate: self, delegateQueue: URLSession.shared.delegateQueue)
48 | sessionTask = self.session?.dataTask(with: self.request) { [weak self] (data, res, err) in
49 | guard let strongSelf = self else { return }
50 |
51 | strongSelf.threadSafety {
52 |
53 | if let err = err {
54 | strongSelf.client?.urlProtocol(strongSelf, didFailWithError: err)
55 | } else {
56 | strongSelf.client?.urlProtocol(strongSelf, didReceive: res!, cacheStoragePolicy: .allowed)
57 | strongSelf.client?.urlProtocol(strongSelf, didLoad: data!)
58 | strongSelf.client?.urlProtocolDidFinishLoading(strongSelf)
59 | }
60 | }
61 | }
62 | sessionTask?.resume()
63 | }
64 |
65 | override func stopLoading() {
66 | self.sessionTask?.cancel()
67 | self.sessionTask = nil
68 | self.session = nil
69 | }
70 |
71 | func threadSafety(_ clourse: (() -> Void)) {
72 | guard let thread = clientThread else { return }
73 | perform(#selector(performClourseOnThread(_:)), on: thread, with: clourse, waitUntilDone: false)
74 | }
75 |
76 | @objc func performClourseOnThread(_ clourse: Any) {
77 | guard let clourse = clourse as? () -> Void else { return }
78 | clourse()
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/MVVM/ViewController/SchoolDataVC.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SchoolDataVC.swift
3 | // MVVM
4 | //
5 | // Created by Nick Lin on 2018/4/7.
6 | // Copyright © 2018年 Nick Lin. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class SchoolDataVC: BaseVC {
12 |
13 | fileprivate lazy var viewModel: SchoolDataViewModel = SchoolDataViewModel()
14 |
15 | fileprivate let tableView: UITableView = UITableView()
16 |
17 | override func viewDidLoad() {
18 | super.viewDidLoad()
19 | setupUI()
20 | viewModel.loadingDelegate = self
21 | viewModel.loadingStatusDelegate = self
22 | viewModel.nextStatus()
23 | }
24 |
25 | private func setupUI() {
26 | view.addSubview(tableView)
27 | tableView.mLay(pin: .zero)
28 | tableView.dataSource = self
29 | tableView.delegate = self
30 | tableView.register(SchoolDataCell.self, forCellReuseIdentifier: "cell")
31 | }
32 |
33 | override func didTapReloadButton() {
34 | viewModel.refreshData()
35 | }
36 |
37 | }
38 |
39 | extension SchoolDataVC: UITableViewDataSource, UITableViewDelegate {
40 |
41 | func numberOfSections(in tableView: UITableView) -> Int {
42 | return viewModel.numberOfSection()
43 | }
44 |
45 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
46 | return viewModel.numberOfItem(in: section)
47 | }
48 |
49 | func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
50 | if let model = viewModel.modelAt(section: section) {
51 | let headerLabel = UILabel()
52 | headerLabel.text = " 🔷班級: \(model.className), 導師: \(model.tutorName)"
53 | headerLabel.backgroundColor = .red
54 | return headerLabel
55 | }
56 | return nil
57 | }
58 |
59 | func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
60 | return viewModel.modelAt(section: section) != nil ? 50 : 0
61 | }
62 |
63 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
64 | let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! SchoolDataCell
65 | if let model = viewModel.modelAt(indexPath: indexPath) {
66 | cell.setup(with: model)
67 | }
68 | return cell
69 | }
70 |
71 | func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
72 | if viewModel.isLastData(index: indexPath) {
73 | viewModel.nextStatus()
74 | }
75 | }
76 | }
77 |
78 | extension SchoolDataVC: ViewModelLoadingDelegate {
79 |
80 | func loadingDone() {
81 | tableView.reloadData()
82 | }
83 |
84 | func loadingFail(_ error: Error?) {
85 |
86 | }
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/MVVM/Services/WebService/WebService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WebService.swift
3 | // MVVM
4 | //
5 | // Created by Nick Lin on 2018/4/3.
6 | // Copyright © 2018年 Nick Lin. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Alamofire
11 |
12 | typealias DataCompleClosure = (Data) -> Void
13 | typealias LoadDataFailClosure = (Error?) -> Void
14 |
15 | final class WebService: SessionManager {
16 |
17 | static let shared = WebService()
18 | private convenience init() {
19 | let config = URLSessionConfiguration.default
20 | config.protocolClasses = [ChlURLProtocol.self]
21 | config.timeoutIntervalForRequest = 10
22 | self.init(configuration: config, delegate: SessionDelegate(), serverTrustPolicyManager: nil)
23 | }
24 |
25 | fileprivate func request(method: HTTPMethod, url: String = String(), parameters: [String: Any]? = nil, handle: @escaping (Data?, HTTPURLResponse?, Error?) -> Void) {
26 | request(url, method: method, parameters: parameters).validate().response { response in
27 | handle(response.data, response.response, response.error)
28 | }
29 | }
30 |
31 | fileprivate func baseRequest(method: HTTPMethod, url: String = String(), parameters: [String: Any]?, success: DataCompleClosure?, fail: LoadDataFailClosure?) {
32 | request(method: .get, url: url, parameters: parameters) { (data, res, error) in
33 |
34 | guard error == nil else {
35 | fail?(error ?? WebError.unKnowError(userInfo: [WebError.url: url, WebError.heads: res?.allHeaderFields ?? [AnyHashable: Any](), WebError.body: data?.string ?? Data()]))
36 | return
37 | }
38 | guard let datas = data else {
39 | fail?(WebError.convertFail(userInfo: [WebError.url: url, WebError.heads: res?.allHeaderFields ?? [AnyHashable: Any](), WebError.body: data ?? Data()]))
40 | return
41 | }
42 | success?(datas)
43 | return
44 | }
45 | }
46 |
47 | }
48 |
49 | extension WebService {
50 |
51 | func getZipData(success: DataCompleClosure?, fail: LoadDataFailClosure?) {
52 | let url = "http://data.ntpc.gov.tw/od/data/api/AC110AF8-C847-43E5-B62C-7985E9E049F9"
53 | var para: [String: Any] = [:]
54 | para["$format"] = "json"
55 | baseRequest(method: .get, url: url, parameters: para, success: success, fail: fail)
56 | }
57 |
58 | func getGarbageStationInfo(with area: String, skip: Int, success: DataCompleClosure?, fail: LoadDataFailClosure?) {
59 | let path = "http://data.ntpc.gov.tw/od/data/api/EDC3AD26-8AE7-4916-A00B-BC6048D19BF8"
60 | var para: [String: Any] = [:]
61 | para["$format"] = "json"
62 | para["$filter"] = "city eq \(area)"
63 | para["$skip"] = skip
64 | para["$top"] = 40
65 | baseRequest(method: .get, url: path, parameters: para, success: success, fail: fail)
66 | }
67 |
68 | func getSchoolData(success: DataCompleClosure?, fail: LoadDataFailClosure?) {
69 | let url = "https://github.com/nick6969/MVVM/blob/master/MVVM/data.json?raw=true"
70 | baseRequest(method: .get, url: url, parameters: nil, success: success, fail: fail)
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/MVVM/Base/ViewModel/BaseViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BaseViewModel.swift
3 | // MVVM
4 | //
5 | // Created by Nick Lin on 2018/4/3.
6 | // Copyright © 2018年 Nick Lin. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class BaseViewModel where T: JsonModel {
12 | weak var loadingDelegate: ViewModelLoadingDelegate?
13 | weak var loadingStatusDelegate: ViewModelLoadingStatusDelegate?
14 | var status: ViewModelStatus = .initialize
15 |
16 | var models: [T] = [] {
17 | didSet {
18 | if models.isEmpty && status != .loadStart {
19 | DispatchQueue.main.async {
20 | self.loadingStatusDelegate?.showEmptyView(with: nil)
21 | }
22 | }
23 | }
24 | }
25 |
26 | var modelsSuccessClosure: (([T]) -> Void)?
27 | var modelSuccessClosure: ((T) -> Void)?
28 | var loadingFailClosure: ( (Error?) -> Void )?
29 |
30 | var dataConvertToModelsClosure: ((Data) -> Void)?
31 | var dataConvertToModelClosure: ((Data) -> Void)?
32 |
33 | init() {
34 | setupClosure()
35 | }
36 |
37 | fileprivate func setupClosure() {
38 |
39 | dataConvertToModelsClosure = { [weak self] data in
40 | guard let `self` = self else { return }
41 | do {
42 | let models = try data.decodeToModelArray(type: T.self)
43 | self.modelsSuccessClosure?(models)
44 | } catch {
45 | self.loadingFailClosure?(error)
46 | }
47 | }
48 |
49 | dataConvertToModelClosure = { [weak self] data in
50 | guard let `self` = self else { return }
51 | do {
52 | let model = try data.decodeToModel(type: T.self)
53 | self.modelSuccessClosure?(model)
54 | } catch {
55 | self.loadingFailClosure?(error)
56 | }
57 | }
58 |
59 | modelsSuccessClosure = { [weak self] models in
60 | guard let `self` = self else { return }
61 | if self.status == .loadStart {
62 | self.models = models
63 | } else if self.status == .loadMoreStart {
64 | if models.isEmpty {
65 | self.status = .noMoreCanLoad
66 | } else {
67 | self.models.append(contentsOf: models)
68 | }
69 | }
70 | self.loadDoneChangeStatus()
71 | DispatchQueue.main.async {
72 | self.loadingStatusDelegate?.showLoading(false)
73 | if self.models.isEmpty {
74 | self.loadingStatusDelegate?.showEmptyView(with: nil)
75 | } else {
76 | self.loadingStatusDelegate?.removeEmptyView()
77 | }
78 | self.loadingDelegate?.loadingDone()
79 | }
80 | }
81 |
82 | modelSuccessClosure = { [weak self] model in
83 | guard let `self` = self else { return }
84 | self.models = [model]
85 | self.loadDoneChangeStatus()
86 | DispatchQueue.main.async {
87 | self.loadingStatusDelegate?.showLoading(false)
88 | self.loadingStatusDelegate?.removeEmptyView()
89 | self.loadingDelegate?.loadingDone()
90 | }
91 | }
92 |
93 | loadingFailClosure = { [weak self] error in
94 | guard let `self` = self else { return }
95 | self.loadFailChangeStatus()
96 | DispatchQueue.main.async {
97 | self.loadingStatusDelegate?.showLoading(false)
98 | if self.models.isEmpty {
99 | self.loadingStatusDelegate?.showEmptyView(with: error)
100 | }
101 | self.loadingDelegate?.loadingFail(error)
102 | }
103 | }
104 | }
105 |
106 | fileprivate func loadDoneChangeStatus() {
107 | switch status {
108 | case .loadStart:
109 | status = .loadDone
110 | case .loadMoreStart:
111 | status = .loadMoreDone
112 | default:
113 | break
114 | }
115 | }
116 |
117 | fileprivate func loadFailChangeStatus() {
118 | switch status {
119 | case .loadStart:
120 | status = .loadFail
121 | case .loadMoreStart:
122 | status = .loadMoreFail
123 | default:
124 | break
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/sortXcodeProject.pl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/perl -w
2 |
3 | # Copyright (C) 2007, 2008, 2009, 2010 Apple Inc. All rights reserved.
4 | #
5 | # Redistribution and use in source and binary forms, with or without
6 | # modification, are permitted provided that the following conditions
7 | # are met:
8 | #
9 | # 1. Redistributions of source code must retain the above copyright
10 | # notice, this list of conditions and the following disclaimer.
11 | # 2. Redistributions in binary form must reproduce the above copyright
12 | # notice, this list of conditions and the following disclaimer in the
13 | # documentation and/or other materials provided with the distribution.
14 | # 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15 | # its contributors may be used to endorse or promote products derived
16 | # from this software without specific prior written permission.
17 | #
18 | # THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19 | # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | # DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22 | # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 | # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
29 | # Script to sort "children" and "files" sections in Xcode project.pbxproj files
30 |
31 | use strict;
32 |
33 | use File::Basename;
34 | use File::Spec;
35 | use File::Temp qw(tempfile);
36 | use Getopt::Long;
37 |
38 | sub sortChildrenByFileName($$);
39 | sub sortFilesByFileName($$);
40 |
41 | # Files (or products) without extensions
42 | my %isFile = map { $_ => 1 } qw(
43 | create_hash_table
44 | jsc
45 | minidom
46 | testapi
47 | testjsglue
48 | );
49 |
50 | my $printWarnings = 1;
51 | my $showHelp;
52 |
53 | my $getOptionsResult = GetOptions(
54 | 'h|help' => \$showHelp,
55 | 'w|warnings!' => \$printWarnings,
56 | );
57 |
58 | if (scalar(@ARGV) == 0 && !$showHelp) {
59 | print STDERR "ERROR: No Xcode project files (project.pbxproj) listed on command-line.\n";
60 | undef $getOptionsResult;
61 | }
62 |
63 | if (!$getOptionsResult || $showHelp) {
64 | print STDERR <<__END__;
65 | Usage: @{[ basename($0) ]} [options] path/to/project.pbxproj [path/to/project.pbxproj ...]
66 | -h|--help show this help message
67 | -w|--[no-]warnings show or suppress warnings (default: show warnings)
68 | __END__
69 | exit 1;
70 | }
71 |
72 | for my $projectFile (@ARGV) {
73 | if (basename($projectFile) =~ /\.xcodeproj$/) {
74 | $projectFile = File::Spec->catfile($projectFile, "project.pbxproj");
75 | }
76 |
77 | if (basename($projectFile) ne "project.pbxproj") {
78 | print STDERR "WARNING: Not an Xcode project file: $projectFile\n" if $printWarnings;
79 | next;
80 | }
81 |
82 | # Grab the mainGroup for the project file
83 | my $mainGroup = "";
84 | open(IN, "< $projectFile") || die "Could not open $projectFile: $!";
85 | while (my $line = ) {
86 | $mainGroup = $2 if $line =~ m#^(\s*)mainGroup = ([0-9A-F]{24} /\* .+ \*/);$#;
87 | }
88 | close(IN);
89 |
90 | my ($OUT, $tempFileName) = tempfile(
91 | basename($projectFile) . "-XXXXXXXX",
92 | DIR => dirname($projectFile),
93 | UNLINK => 0,
94 | );
95 |
96 | # Clean up temp file in case of die()
97 | $SIG{__DIE__} = sub {
98 | close(IN);
99 | close($OUT);
100 | unlink($tempFileName);
101 | };
102 |
103 | my @lastTwo = ();
104 | open(IN, "< $projectFile") || die "Could not open $projectFile: $!";
105 | while (my $line = ) {
106 | if ($line =~ /^(\s*)files = \(\s*$/) {
107 | print $OUT $line;
108 | my $endMarker = $1 . ");";
109 | my @files;
110 | while (my $fileLine = ) {
111 | if ($fileLine =~ /^\Q$endMarker\E\s*$/) {
112 | $endMarker = $fileLine;
113 | last;
114 | }
115 | push @files, $fileLine;
116 | }
117 | print $OUT sort sortFilesByFileName @files;
118 | print $OUT $endMarker;
119 | } elsif ($line =~ /^(\s*)children = \(\s*$/) {
120 | print $OUT $line;
121 | my $endMarker = $1 . ");";
122 | my @children;
123 | while (my $childLine = ) {
124 | if ($childLine =~ /^\Q$endMarker\E\s*$/) {
125 | $endMarker = $childLine;
126 | last;
127 | }
128 | push @children, $childLine;
129 | }
130 | if ($lastTwo[0] =~ m#^\s+\Q$mainGroup\E = \{$#) {
131 | # Don't sort mainGroup
132 | print $OUT @children;
133 | } else {
134 | print $OUT sort sortChildrenByFileName @children;
135 | }
136 | print $OUT $endMarker;
137 | } else {
138 | print $OUT $line;
139 | }
140 |
141 | push @lastTwo, $line;
142 | shift @lastTwo if scalar(@lastTwo) > 2;
143 | }
144 | close(IN);
145 | close($OUT);
146 |
147 | unlink($projectFile) || die "Could not delete $projectFile: $!";
148 | rename($tempFileName, $projectFile) || die "Could not rename $tempFileName to $projectFile: $!";
149 | }
150 |
151 | exit 0;
152 |
153 | sub sortChildrenByFileName($$)
154 | {
155 | my ($a, $b) = @_;
156 | my $aFileName = $1 if $a =~ /^\s*[A-Z0-9]{24} \/\* (.+) \*\/,$/;
157 | my $bFileName = $1 if $b =~ /^\s*[A-Z0-9]{24} \/\* (.+) \*\/,$/;
158 | my $aSuffix = $1 if $aFileName =~ m/\.([^.]+)$/;
159 | my $bSuffix = $1 if $bFileName =~ m/\.([^.]+)$/;
160 | if ((!$aSuffix && !$isFile{$aFileName} && $bSuffix) || ($aSuffix && !$bSuffix && !$isFile{$bFileName})) {
161 | return !$aSuffix ? -1 : 1;
162 | }
163 | return lc($aFileName) cmp lc($bFileName);
164 | }
165 |
166 | sub sortFilesByFileName($$)
167 | {
168 | my ($a, $b) = @_;
169 | my $aFileName = $1 if $a =~ /^\s*[A-Z0-9]{24} \/\* (.+) in /;
170 | my $bFileName = $1 if $b =~ /^\s*[A-Z0-9]{24} \/\* (.+) in /;
171 | return lc($aFileName) cmp lc($bFileName);
172 | }
--------------------------------------------------------------------------------
/MVVM.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 48;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 01919CBE2073C36A00400B51 /* ChlURLProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01919CBD2073C36A00400B51 /* ChlURLProtocol.swift */; };
11 | 01919CC02073C47300400B51 /* Tools.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01919CBF2073C47300400B51 /* Tools.swift */; };
12 | 0194DAAF2078081F000328AF /* ViewModelMultipleContentDataProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0194DAAE2078081F000328AF /* ViewModelMultipleContentDataProtocol.swift */; };
13 | 0194DAB12078089B000328AF /* ArrayExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0194DAB02078089B000328AF /* ArrayExtension.swift */; };
14 | 0194DAB320780D5F000328AF /* ClassModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0194DAB220780D5F000328AF /* ClassModel.swift */; };
15 | 0194DAB720780FE2000328AF /* SchoolDataViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0194DAB620780FE2000328AF /* SchoolDataViewModel.swift */; };
16 | 0194DABB207817FE000328AF /* SchoolDataVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0194DABA207817FE000328AF /* SchoolDataVC.swift */; };
17 | 0194DABD20781984000328AF /* SchoolDataCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0194DABC20781984000328AF /* SchoolDataCell.swift */; };
18 | 0194DAC120782241000328AF /* data.json in Resources */ = {isa = PBXBuildFile; fileRef = 0194DAC020782241000328AF /* data.json */; };
19 | 01A279E720735B6F0045DE1C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A279E620735B6F0045DE1C /* AppDelegate.swift */; };
20 | 01A279EE20735B6F0045DE1C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 01A279ED20735B6F0045DE1C /* Assets.xcassets */; };
21 | 01A279F120735B6F0045DE1C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 01A279EF20735B6F0045DE1C /* LaunchScreen.storyboard */; };
22 | 01CFAB6520735C8900720BF6 /* JsonProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CFAB6420735C8900720BF6 /* JsonProtocol.swift */; };
23 | 01CFAB6720735CE700720BF6 /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CFAB6620735CE700720BF6 /* Error.swift */; };
24 | 01CFAB6E20735E0100720BF6 /* ViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CFAB6D20735E0100720BF6 /* ViewModelProtocol.swift */; };
25 | 01CFAB7020735E9500720BF6 /* ViewModelStatusEnum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CFAB6F20735E9500720BF6 /* ViewModelStatusEnum.swift */; };
26 | 01CFAB7220735EF400720BF6 /* ViewModelDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CFAB7120735EF400720BF6 /* ViewModelDelegate.swift */; };
27 | 01CFAB7420735FD300720BF6 /* BaseViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CFAB7320735FD300720BF6 /* BaseViewModel.swift */; };
28 | 01CFAB792073645200720BF6 /* BaseVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CFAB782073645200720BF6 /* BaseVC.swift */; };
29 | 01CFAB7C2073665800720BF6 /* EmptyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CFAB7B2073665800720BF6 /* EmptyView.swift */; };
30 | 01CFAB7E207366B600720BF6 /* UIColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CFAB7D207366B600720BF6 /* UIColorExtension.swift */; };
31 | 01CFAB802073671200720BF6 /* NoDataView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CFAB7F2073671200720BF6 /* NoDataView.swift */; };
32 | 01CFAB852073695700720BF6 /* WebService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CFAB842073695700720BF6 /* WebService.swift */; };
33 | 01CFAB8820736F6400720BF6 /* ZipModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CFAB8720736F6400720BF6 /* ZipModel.swift */; };
34 | 01CFAB8A2073702A00720BF6 /* GarbageStationInfoModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CFAB892073702A00720BF6 /* GarbageStationInfoModel.swift */; };
35 | 01CFAB8D2073719000720BF6 /* ZipViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CFAB8C2073719000720BF6 /* ZipViewModel.swift */; };
36 | 01CFAB8F2073720900720BF6 /* GarbageStationInfoViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CFAB8E2073720900720BF6 /* GarbageStationInfoViewModel.swift */; };
37 | 01CFAB95207372B600720BF6 /* MainVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CFAB94207372B600720BF6 /* MainVC.swift */; };
38 | 01CFAB97207373C200720BF6 /* ZipVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CFAB96207373C200720BF6 /* ZipVC.swift */; };
39 | 01CFAB992073770A00720BF6 /* ZipCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CFAB982073770A00720BF6 /* ZipCollectionViewCell.swift */; };
40 | 01CFAB9B20737C5D00720BF6 /* GarbageStationInfoVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CFAB9A20737C5D00720BF6 /* GarbageStationInfoVC.swift */; };
41 | 01CFAB9D20737D9E00720BF6 /* GarbageStationInfoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01CFAB9C20737D9E00720BF6 /* GarbageStationInfoCell.swift */; };
42 | 01F7D5F8207CDA07006CB1FB /* BaseDataProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F7D5F7207CDA07006CB1FB /* BaseDataProtocol.swift */; };
43 | F8BEC2ED52579ED6A147E636 /* Pods_MVVM.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5D796F75F668E558ECF32D /* Pods_MVVM.framework */; };
44 | /* End PBXBuildFile section */
45 |
46 | /* Begin PBXFileReference section */
47 | 01919CBD2073C36A00400B51 /* ChlURLProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChlURLProtocol.swift; sourceTree = ""; };
48 | 01919CBF2073C47300400B51 /* Tools.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tools.swift; sourceTree = ""; };
49 | 0194DAAE2078081F000328AF /* ViewModelMultipleContentDataProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModelMultipleContentDataProtocol.swift; sourceTree = ""; };
50 | 0194DAB02078089B000328AF /* ArrayExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrayExtension.swift; sourceTree = ""; };
51 | 0194DAB220780D5F000328AF /* ClassModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClassModel.swift; sourceTree = ""; };
52 | 0194DAB620780FE2000328AF /* SchoolDataViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SchoolDataViewModel.swift; sourceTree = ""; };
53 | 0194DABA207817FE000328AF /* SchoolDataVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SchoolDataVC.swift; sourceTree = ""; };
54 | 0194DABC20781984000328AF /* SchoolDataCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SchoolDataCell.swift; sourceTree = ""; };
55 | 0194DAC020782241000328AF /* data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = data.json; sourceTree = ""; };
56 | 01A279E320735B6F0045DE1C /* MVVM.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MVVM.app; sourceTree = BUILT_PRODUCTS_DIR; };
57 | 01A279E620735B6F0045DE1C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
58 | 01A279ED20735B6F0045DE1C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
59 | 01A279F020735B6F0045DE1C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
60 | 01A279F220735B6F0045DE1C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
61 | 01CFAB6420735C8900720BF6 /* JsonProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JsonProtocol.swift; sourceTree = ""; };
62 | 01CFAB6620735CE700720BF6 /* Error.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Error.swift; sourceTree = ""; };
63 | 01CFAB6D20735E0100720BF6 /* ViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModelProtocol.swift; sourceTree = ""; };
64 | 01CFAB6F20735E9500720BF6 /* ViewModelStatusEnum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModelStatusEnum.swift; sourceTree = ""; };
65 | 01CFAB7120735EF400720BF6 /* ViewModelDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModelDelegate.swift; sourceTree = ""; };
66 | 01CFAB7320735FD300720BF6 /* BaseViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseViewModel.swift; sourceTree = ""; };
67 | 01CFAB782073645200720BF6 /* BaseVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseVC.swift; sourceTree = ""; };
68 | 01CFAB7B2073665800720BF6 /* EmptyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyView.swift; sourceTree = ""; };
69 | 01CFAB7D207366B600720BF6 /* UIColorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIColorExtension.swift; sourceTree = ""; };
70 | 01CFAB7F2073671200720BF6 /* NoDataView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoDataView.swift; sourceTree = ""; };
71 | 01CFAB842073695700720BF6 /* WebService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebService.swift; sourceTree = ""; };
72 | 01CFAB8720736F6400720BF6 /* ZipModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZipModel.swift; sourceTree = ""; };
73 | 01CFAB892073702A00720BF6 /* GarbageStationInfoModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GarbageStationInfoModel.swift; sourceTree = ""; };
74 | 01CFAB8C2073719000720BF6 /* ZipViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZipViewModel.swift; sourceTree = ""; };
75 | 01CFAB8E2073720900720BF6 /* GarbageStationInfoViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GarbageStationInfoViewModel.swift; sourceTree = ""; };
76 | 01CFAB94207372B600720BF6 /* MainVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainVC.swift; sourceTree = ""; };
77 | 01CFAB96207373C200720BF6 /* ZipVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZipVC.swift; sourceTree = ""; };
78 | 01CFAB982073770A00720BF6 /* ZipCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZipCollectionViewCell.swift; sourceTree = ""; };
79 | 01CFAB9A20737C5D00720BF6 /* GarbageStationInfoVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GarbageStationInfoVC.swift; sourceTree = ""; };
80 | 01CFAB9C20737D9E00720BF6 /* GarbageStationInfoCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GarbageStationInfoCell.swift; sourceTree = ""; };
81 | 01F7D5F7207CDA07006CB1FB /* BaseDataProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseDataProtocol.swift; sourceTree = ""; };
82 | 41924B0A77A02B66C1CE8545 /* Pods-MVVM.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MVVM.release.xcconfig"; path = "Pods/Target Support Files/Pods-MVVM/Pods-MVVM.release.xcconfig"; sourceTree = ""; };
83 | DA5D796F75F668E558ECF32D /* Pods_MVVM.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MVVM.framework; sourceTree = BUILT_PRODUCTS_DIR; };
84 | FF129F2F693F8BE2354CB5E2 /* Pods-MVVM.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MVVM.debug.xcconfig"; path = "Pods/Target Support Files/Pods-MVVM/Pods-MVVM.debug.xcconfig"; sourceTree = ""; };
85 | /* End PBXFileReference section */
86 |
87 | /* Begin PBXFrameworksBuildPhase section */
88 | 01A279E020735B6F0045DE1C /* Frameworks */ = {
89 | isa = PBXFrameworksBuildPhase;
90 | buildActionMask = 2147483647;
91 | files = (
92 | F8BEC2ED52579ED6A147E636 /* Pods_MVVM.framework in Frameworks */,
93 | );
94 | runOnlyForDeploymentPostprocessing = 0;
95 | };
96 | /* End PBXFrameworksBuildPhase section */
97 |
98 | /* Begin PBXGroup section */
99 | 01A279DA20735B6F0045DE1C = {
100 | isa = PBXGroup;
101 | children = (
102 | 6420965D9D701EBD4C87971F /* Frameworks */,
103 | 01A279E520735B6F0045DE1C /* MVVM */,
104 | 30374CBEA4692B7246227482 /* Pods */,
105 | 01A279E420735B6F0045DE1C /* Products */,
106 | );
107 | sourceTree = "";
108 | };
109 | 01A279E420735B6F0045DE1C /* Products */ = {
110 | isa = PBXGroup;
111 | children = (
112 | 01A279E320735B6F0045DE1C /* MVVM.app */,
113 | );
114 | name = Products;
115 | sourceTree = "";
116 | };
117 | 01A279E520735B6F0045DE1C /* MVVM */ = {
118 | isa = PBXGroup;
119 | children = (
120 | 01CFAB6A20735D6800720BF6 /* Base */,
121 | 01CFAB6320735C7800720BF6 /* Extension */,
122 | 01CFAB82207368FB00720BF6 /* Services */,
123 | 01CFAB6220735C3F00720BF6 /* Support */,
124 | 01CFAB902073727400720BF6 /* View */,
125 | 01CFAB8620736F5900720BF6 /* Model */,
126 | 01CFAB93207372A900720BF6 /* ViewController */,
127 | 01CFAB8B2073717F00720BF6 /* ViewModel */,
128 | 01A279E620735B6F0045DE1C /* AppDelegate.swift */,
129 | );
130 | path = MVVM;
131 | sourceTree = "";
132 | };
133 | 01CFAB6220735C3F00720BF6 /* Support */ = {
134 | isa = PBXGroup;
135 | children = (
136 | 0194DAC020782241000328AF /* data.json */,
137 | 01A279ED20735B6F0045DE1C /* Assets.xcassets */,
138 | 01A279F220735B6F0045DE1C /* Info.plist */,
139 | 01A279EF20735B6F0045DE1C /* LaunchScreen.storyboard */,
140 | );
141 | name = Support;
142 | sourceTree = "";
143 | };
144 | 01CFAB6320735C7800720BF6 /* Extension */ = {
145 | isa = PBXGroup;
146 | children = (
147 | 01CFAB7D207366B600720BF6 /* UIColorExtension.swift */,
148 | 01919CBF2073C47300400B51 /* Tools.swift */,
149 | 0194DAB02078089B000328AF /* ArrayExtension.swift */,
150 | );
151 | path = Extension;
152 | sourceTree = "";
153 | };
154 | 01CFAB6A20735D6800720BF6 /* Base */ = {
155 | isa = PBXGroup;
156 | children = (
157 | 01CFAB6C20735D7B00720BF6 /* ViewModel */,
158 | 01CFAB6B20735D7300720BF6 /* Protocol */,
159 | 01CFAB7A2073664C00720BF6 /* View */,
160 | 01CFAB772073643800720BF6 /* ViewController */,
161 | 01CFAB6620735CE700720BF6 /* Error.swift */,
162 | );
163 | path = Base;
164 | sourceTree = "";
165 | };
166 | 01CFAB6B20735D7300720BF6 /* Protocol */ = {
167 | isa = PBXGroup;
168 | children = (
169 | 01CFAB6420735C8900720BF6 /* JsonProtocol.swift */,
170 | 01CFAB7120735EF400720BF6 /* ViewModelDelegate.swift */,
171 | 01CFAB6D20735E0100720BF6 /* ViewModelProtocol.swift */,
172 | 01CFAB6F20735E9500720BF6 /* ViewModelStatusEnum.swift */,
173 | 0194DAAE2078081F000328AF /* ViewModelMultipleContentDataProtocol.swift */,
174 | 01F7D5F7207CDA07006CB1FB /* BaseDataProtocol.swift */,
175 | );
176 | path = Protocol;
177 | sourceTree = "";
178 | };
179 | 01CFAB6C20735D7B00720BF6 /* ViewModel */ = {
180 | isa = PBXGroup;
181 | children = (
182 | 01CFAB7320735FD300720BF6 /* BaseViewModel.swift */,
183 | );
184 | path = ViewModel;
185 | sourceTree = "";
186 | };
187 | 01CFAB772073643800720BF6 /* ViewController */ = {
188 | isa = PBXGroup;
189 | children = (
190 | 01CFAB782073645200720BF6 /* BaseVC.swift */,
191 | );
192 | path = ViewController;
193 | sourceTree = "";
194 | };
195 | 01CFAB7A2073664C00720BF6 /* View */ = {
196 | isa = PBXGroup;
197 | children = (
198 | 01CFAB7B2073665800720BF6 /* EmptyView.swift */,
199 | 01CFAB7F2073671200720BF6 /* NoDataView.swift */,
200 | );
201 | path = View;
202 | sourceTree = "";
203 | };
204 | 01CFAB82207368FB00720BF6 /* Services */ = {
205 | isa = PBXGroup;
206 | children = (
207 | 01CFAB832073694800720BF6 /* WebService */,
208 | );
209 | path = Services;
210 | sourceTree = "";
211 | };
212 | 01CFAB832073694800720BF6 /* WebService */ = {
213 | isa = PBXGroup;
214 | children = (
215 | 01919CBD2073C36A00400B51 /* ChlURLProtocol.swift */,
216 | 01CFAB842073695700720BF6 /* WebService.swift */,
217 | );
218 | path = WebService;
219 | sourceTree = "";
220 | };
221 | 01CFAB8620736F5900720BF6 /* Model */ = {
222 | isa = PBXGroup;
223 | children = (
224 | 01CFAB892073702A00720BF6 /* GarbageStationInfoModel.swift */,
225 | 01CFAB8720736F6400720BF6 /* ZipModel.swift */,
226 | 0194DAB220780D5F000328AF /* ClassModel.swift */,
227 | );
228 | path = Model;
229 | sourceTree = "";
230 | };
231 | 01CFAB8B2073717F00720BF6 /* ViewModel */ = {
232 | isa = PBXGroup;
233 | children = (
234 | 01CFAB8E2073720900720BF6 /* GarbageStationInfoViewModel.swift */,
235 | 01CFAB8C2073719000720BF6 /* ZipViewModel.swift */,
236 | 0194DAB620780FE2000328AF /* SchoolDataViewModel.swift */,
237 | );
238 | path = ViewModel;
239 | sourceTree = "";
240 | };
241 | 01CFAB902073727400720BF6 /* View */ = {
242 | isa = PBXGroup;
243 | children = (
244 | 01CFAB912073727D00720BF6 /* CollectionViewCell */,
245 | 01CFAB922073728600720BF6 /* TableViewCell */,
246 | );
247 | path = View;
248 | sourceTree = "";
249 | };
250 | 01CFAB912073727D00720BF6 /* CollectionViewCell */ = {
251 | isa = PBXGroup;
252 | children = (
253 | 01CFAB982073770A00720BF6 /* ZipCollectionViewCell.swift */,
254 | );
255 | path = CollectionViewCell;
256 | sourceTree = "";
257 | };
258 | 01CFAB922073728600720BF6 /* TableViewCell */ = {
259 | isa = PBXGroup;
260 | children = (
261 | 01CFAB9C20737D9E00720BF6 /* GarbageStationInfoCell.swift */,
262 | 0194DABC20781984000328AF /* SchoolDataCell.swift */,
263 | );
264 | path = TableViewCell;
265 | sourceTree = "";
266 | };
267 | 01CFAB93207372A900720BF6 /* ViewController */ = {
268 | isa = PBXGroup;
269 | children = (
270 | 01CFAB9A20737C5D00720BF6 /* GarbageStationInfoVC.swift */,
271 | 01CFAB94207372B600720BF6 /* MainVC.swift */,
272 | 01CFAB96207373C200720BF6 /* ZipVC.swift */,
273 | 0194DABA207817FE000328AF /* SchoolDataVC.swift */,
274 | );
275 | path = ViewController;
276 | sourceTree = "";
277 | };
278 | 30374CBEA4692B7246227482 /* Pods */ = {
279 | isa = PBXGroup;
280 | children = (
281 | FF129F2F693F8BE2354CB5E2 /* Pods-MVVM.debug.xcconfig */,
282 | 41924B0A77A02B66C1CE8545 /* Pods-MVVM.release.xcconfig */,
283 | );
284 | name = Pods;
285 | sourceTree = "";
286 | };
287 | 6420965D9D701EBD4C87971F /* Frameworks */ = {
288 | isa = PBXGroup;
289 | children = (
290 | DA5D796F75F668E558ECF32D /* Pods_MVVM.framework */,
291 | );
292 | name = Frameworks;
293 | sourceTree = "";
294 | };
295 | /* End PBXGroup section */
296 |
297 | /* Begin PBXNativeTarget section */
298 | 01A279E220735B6F0045DE1C /* MVVM */ = {
299 | isa = PBXNativeTarget;
300 | buildConfigurationList = 01A279F520735B6F0045DE1C /* Build configuration list for PBXNativeTarget "MVVM" */;
301 | buildPhases = (
302 | AEF328AFE8BB45C64F3AE66E /* [CP] Check Pods Manifest.lock */,
303 | 01A279DF20735B6F0045DE1C /* Sources */,
304 | 01A279E020735B6F0045DE1C /* Frameworks */,
305 | 01A279E120735B6F0045DE1C /* Resources */,
306 | 3EC900E9EE760ECD851E69AB /* [CP] Embed Pods Frameworks */,
307 | 9540B610D2FA978EA5A493AA /* [CP] Copy Pods Resources */,
308 | 01CFAB81207367CC00720BF6 /* Run SwiftLint */,
309 | );
310 | buildRules = (
311 | );
312 | dependencies = (
313 | );
314 | name = MVVM;
315 | productName = MVVM;
316 | productReference = 01A279E320735B6F0045DE1C /* MVVM.app */;
317 | productType = "com.apple.product-type.application";
318 | };
319 | /* End PBXNativeTarget section */
320 |
321 | /* Begin PBXProject section */
322 | 01A279DB20735B6F0045DE1C /* Project object */ = {
323 | isa = PBXProject;
324 | attributes = {
325 | LastSwiftUpdateCheck = 0920;
326 | LastUpgradeCheck = 0920;
327 | ORGANIZATIONNAME = "Nick Lin";
328 | TargetAttributes = {
329 | 01A279E220735B6F0045DE1C = {
330 | CreatedOnToolsVersion = 9.2;
331 | ProvisioningStyle = Automatic;
332 | };
333 | };
334 | };
335 | buildConfigurationList = 01A279DE20735B6F0045DE1C /* Build configuration list for PBXProject "MVVM" */;
336 | compatibilityVersion = "Xcode 8.0";
337 | developmentRegion = en;
338 | hasScannedForEncodings = 0;
339 | knownRegions = (
340 | en,
341 | Base,
342 | );
343 | mainGroup = 01A279DA20735B6F0045DE1C;
344 | productRefGroup = 01A279E420735B6F0045DE1C /* Products */;
345 | projectDirPath = "";
346 | projectRoot = "";
347 | targets = (
348 | 01A279E220735B6F0045DE1C /* MVVM */,
349 | );
350 | };
351 | /* End PBXProject section */
352 |
353 | /* Begin PBXResourcesBuildPhase section */
354 | 01A279E120735B6F0045DE1C /* Resources */ = {
355 | isa = PBXResourcesBuildPhase;
356 | buildActionMask = 2147483647;
357 | files = (
358 | 01A279EE20735B6F0045DE1C /* Assets.xcassets in Resources */,
359 | 0194DAC120782241000328AF /* data.json in Resources */,
360 | 01A279F120735B6F0045DE1C /* LaunchScreen.storyboard in Resources */,
361 | );
362 | runOnlyForDeploymentPostprocessing = 0;
363 | };
364 | /* End PBXResourcesBuildPhase section */
365 |
366 | /* Begin PBXShellScriptBuildPhase section */
367 | 01CFAB81207367CC00720BF6 /* Run SwiftLint */ = {
368 | isa = PBXShellScriptBuildPhase;
369 | buildActionMask = 2147483647;
370 | files = (
371 | );
372 | inputPaths = (
373 | );
374 | name = "Run SwiftLint";
375 | outputPaths = (
376 | );
377 | runOnlyForDeploymentPostprocessing = 0;
378 | shellPath = /bin/sh;
379 | shellScript = "if which swiftlint >/dev/null; then\nswiftlint\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi";
380 | };
381 | 3EC900E9EE760ECD851E69AB /* [CP] Embed Pods Frameworks */ = {
382 | isa = PBXShellScriptBuildPhase;
383 | buildActionMask = 2147483647;
384 | files = (
385 | );
386 | inputPaths = (
387 | "${SRCROOT}/Pods/Target Support Files/Pods-MVVM/Pods-MVVM-frameworks.sh",
388 | "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework",
389 | "${BUILT_PRODUCTS_DIR}/SVProgressHUD/SVProgressHUD.framework",
390 | "${BUILT_PRODUCTS_DIR}/mLayout/mLayout.framework",
391 | );
392 | name = "[CP] Embed Pods Frameworks";
393 | outputPaths = (
394 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework",
395 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SVProgressHUD.framework",
396 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/mLayout.framework",
397 | );
398 | runOnlyForDeploymentPostprocessing = 0;
399 | shellPath = /bin/sh;
400 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-MVVM/Pods-MVVM-frameworks.sh\"\n";
401 | showEnvVarsInLog = 0;
402 | };
403 | 9540B610D2FA978EA5A493AA /* [CP] Copy Pods Resources */ = {
404 | isa = PBXShellScriptBuildPhase;
405 | buildActionMask = 2147483647;
406 | files = (
407 | );
408 | inputPaths = (
409 | );
410 | name = "[CP] Copy Pods Resources";
411 | outputPaths = (
412 | );
413 | runOnlyForDeploymentPostprocessing = 0;
414 | shellPath = /bin/sh;
415 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-MVVM/Pods-MVVM-resources.sh\"\n";
416 | showEnvVarsInLog = 0;
417 | };
418 | AEF328AFE8BB45C64F3AE66E /* [CP] Check Pods Manifest.lock */ = {
419 | isa = PBXShellScriptBuildPhase;
420 | buildActionMask = 2147483647;
421 | files = (
422 | );
423 | inputPaths = (
424 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
425 | "${PODS_ROOT}/Manifest.lock",
426 | );
427 | name = "[CP] Check Pods Manifest.lock";
428 | outputPaths = (
429 | "$(DERIVED_FILE_DIR)/Pods-MVVM-checkManifestLockResult.txt",
430 | );
431 | runOnlyForDeploymentPostprocessing = 0;
432 | shellPath = /bin/sh;
433 | 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";
434 | showEnvVarsInLog = 0;
435 | };
436 | /* End PBXShellScriptBuildPhase section */
437 |
438 | /* Begin PBXSourcesBuildPhase section */
439 | 01A279DF20735B6F0045DE1C /* Sources */ = {
440 | isa = PBXSourcesBuildPhase;
441 | buildActionMask = 2147483647;
442 | files = (
443 | 0194DAB12078089B000328AF /* ArrayExtension.swift in Sources */,
444 | 01A279E720735B6F0045DE1C /* AppDelegate.swift in Sources */,
445 | 01CFAB7420735FD300720BF6 /* BaseViewModel.swift in Sources */,
446 | 01CFAB792073645200720BF6 /* BaseVC.swift in Sources */,
447 | 01919CBE2073C36A00400B51 /* ChlURLProtocol.swift in Sources */,
448 | 0194DAAF2078081F000328AF /* ViewModelMultipleContentDataProtocol.swift in Sources */,
449 | 0194DAB720780FE2000328AF /* SchoolDataViewModel.swift in Sources */,
450 | 01CFAB7C2073665800720BF6 /* EmptyView.swift in Sources */,
451 | 01CFAB6720735CE700720BF6 /* Error.swift in Sources */,
452 | 01919CC02073C47300400B51 /* Tools.swift in Sources */,
453 | 01CFAB9D20737D9E00720BF6 /* GarbageStationInfoCell.swift in Sources */,
454 | 0194DABD20781984000328AF /* SchoolDataCell.swift in Sources */,
455 | 01CFAB8A2073702A00720BF6 /* GarbageStationInfoModel.swift in Sources */,
456 | 01CFAB9B20737C5D00720BF6 /* GarbageStationInfoVC.swift in Sources */,
457 | 01CFAB8F2073720900720BF6 /* GarbageStationInfoViewModel.swift in Sources */,
458 | 01CFAB6520735C8900720BF6 /* JsonProtocol.swift in Sources */,
459 | 01CFAB95207372B600720BF6 /* MainVC.swift in Sources */,
460 | 01CFAB802073671200720BF6 /* NoDataView.swift in Sources */,
461 | 01CFAB7E207366B600720BF6 /* UIColorExtension.swift in Sources */,
462 | 01CFAB7220735EF400720BF6 /* ViewModelDelegate.swift in Sources */,
463 | 01CFAB6E20735E0100720BF6 /* ViewModelProtocol.swift in Sources */,
464 | 01CFAB7020735E9500720BF6 /* ViewModelStatusEnum.swift in Sources */,
465 | 01CFAB852073695700720BF6 /* WebService.swift in Sources */,
466 | 01CFAB992073770A00720BF6 /* ZipCollectionViewCell.swift in Sources */,
467 | 01CFAB8820736F6400720BF6 /* ZipModel.swift in Sources */,
468 | 01CFAB97207373C200720BF6 /* ZipVC.swift in Sources */,
469 | 01F7D5F8207CDA07006CB1FB /* BaseDataProtocol.swift in Sources */,
470 | 0194DABB207817FE000328AF /* SchoolDataVC.swift in Sources */,
471 | 0194DAB320780D5F000328AF /* ClassModel.swift in Sources */,
472 | 01CFAB8D2073719000720BF6 /* ZipViewModel.swift in Sources */,
473 | );
474 | runOnlyForDeploymentPostprocessing = 0;
475 | };
476 | /* End PBXSourcesBuildPhase section */
477 |
478 | /* Begin PBXVariantGroup section */
479 | 01A279EF20735B6F0045DE1C /* LaunchScreen.storyboard */ = {
480 | isa = PBXVariantGroup;
481 | children = (
482 | 01A279F020735B6F0045DE1C /* Base */,
483 | );
484 | name = LaunchScreen.storyboard;
485 | sourceTree = "";
486 | };
487 | /* End PBXVariantGroup section */
488 |
489 | /* Begin XCBuildConfiguration section */
490 | 01A279F320735B6F0045DE1C /* Debug */ = {
491 | isa = XCBuildConfiguration;
492 | buildSettings = {
493 | ALWAYS_SEARCH_USER_PATHS = NO;
494 | CLANG_ANALYZER_NONNULL = YES;
495 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
496 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
497 | CLANG_CXX_LIBRARY = "libc++";
498 | CLANG_ENABLE_MODULES = YES;
499 | CLANG_ENABLE_OBJC_ARC = YES;
500 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
501 | CLANG_WARN_BOOL_CONVERSION = YES;
502 | CLANG_WARN_COMMA = YES;
503 | CLANG_WARN_CONSTANT_CONVERSION = YES;
504 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
505 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
506 | CLANG_WARN_EMPTY_BODY = YES;
507 | CLANG_WARN_ENUM_CONVERSION = YES;
508 | CLANG_WARN_INFINITE_RECURSION = YES;
509 | CLANG_WARN_INT_CONVERSION = YES;
510 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
511 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
512 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
513 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
514 | CLANG_WARN_STRICT_PROTOTYPES = YES;
515 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
516 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
517 | CLANG_WARN_UNREACHABLE_CODE = YES;
518 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
519 | CODE_SIGN_IDENTITY = "iPhone Developer";
520 | COPY_PHASE_STRIP = NO;
521 | DEBUG_INFORMATION_FORMAT = dwarf;
522 | ENABLE_STRICT_OBJC_MSGSEND = YES;
523 | ENABLE_TESTABILITY = YES;
524 | GCC_C_LANGUAGE_STANDARD = gnu11;
525 | GCC_DYNAMIC_NO_PIC = NO;
526 | GCC_NO_COMMON_BLOCKS = YES;
527 | GCC_OPTIMIZATION_LEVEL = 0;
528 | GCC_PREPROCESSOR_DEFINITIONS = (
529 | "DEBUG=1",
530 | "$(inherited)",
531 | );
532 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
533 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
534 | GCC_WARN_UNDECLARED_SELECTOR = YES;
535 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
536 | GCC_WARN_UNUSED_FUNCTION = YES;
537 | GCC_WARN_UNUSED_VARIABLE = YES;
538 | IPHONEOS_DEPLOYMENT_TARGET = 11.2;
539 | MTL_ENABLE_DEBUG_INFO = YES;
540 | ONLY_ACTIVE_ARCH = YES;
541 | SDKROOT = iphoneos;
542 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
543 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
544 | };
545 | name = Debug;
546 | };
547 | 01A279F420735B6F0045DE1C /* Release */ = {
548 | isa = XCBuildConfiguration;
549 | buildSettings = {
550 | ALWAYS_SEARCH_USER_PATHS = NO;
551 | CLANG_ANALYZER_NONNULL = YES;
552 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
553 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
554 | CLANG_CXX_LIBRARY = "libc++";
555 | CLANG_ENABLE_MODULES = YES;
556 | CLANG_ENABLE_OBJC_ARC = YES;
557 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
558 | CLANG_WARN_BOOL_CONVERSION = YES;
559 | CLANG_WARN_COMMA = YES;
560 | CLANG_WARN_CONSTANT_CONVERSION = YES;
561 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
562 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
563 | CLANG_WARN_EMPTY_BODY = YES;
564 | CLANG_WARN_ENUM_CONVERSION = YES;
565 | CLANG_WARN_INFINITE_RECURSION = YES;
566 | CLANG_WARN_INT_CONVERSION = YES;
567 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
568 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
569 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
570 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
571 | CLANG_WARN_STRICT_PROTOTYPES = YES;
572 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
573 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
574 | CLANG_WARN_UNREACHABLE_CODE = YES;
575 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
576 | CODE_SIGN_IDENTITY = "iPhone Developer";
577 | COPY_PHASE_STRIP = NO;
578 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
579 | ENABLE_NS_ASSERTIONS = NO;
580 | ENABLE_STRICT_OBJC_MSGSEND = YES;
581 | GCC_C_LANGUAGE_STANDARD = gnu11;
582 | GCC_NO_COMMON_BLOCKS = YES;
583 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
584 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
585 | GCC_WARN_UNDECLARED_SELECTOR = YES;
586 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
587 | GCC_WARN_UNUSED_FUNCTION = YES;
588 | GCC_WARN_UNUSED_VARIABLE = YES;
589 | IPHONEOS_DEPLOYMENT_TARGET = 11.2;
590 | MTL_ENABLE_DEBUG_INFO = NO;
591 | SDKROOT = iphoneos;
592 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
593 | VALIDATE_PRODUCT = YES;
594 | };
595 | name = Release;
596 | };
597 | 01A279F620735B6F0045DE1C /* Debug */ = {
598 | isa = XCBuildConfiguration;
599 | baseConfigurationReference = FF129F2F693F8BE2354CB5E2 /* Pods-MVVM.debug.xcconfig */;
600 | buildSettings = {
601 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
602 | CODE_SIGN_STYLE = Automatic;
603 | DEVELOPMENT_TEAM = DZBBP5D6DJ;
604 | INFOPLIST_FILE = MVVM/Info.plist;
605 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
606 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
607 | PRODUCT_BUNDLE_IDENTIFIER = kcin.nil.MVVM;
608 | PRODUCT_NAME = "$(TARGET_NAME)";
609 | SWIFT_VERSION = 4.0;
610 | TARGETED_DEVICE_FAMILY = "1,2";
611 | };
612 | name = Debug;
613 | };
614 | 01A279F720735B6F0045DE1C /* Release */ = {
615 | isa = XCBuildConfiguration;
616 | baseConfigurationReference = 41924B0A77A02B66C1CE8545 /* Pods-MVVM.release.xcconfig */;
617 | buildSettings = {
618 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
619 | CODE_SIGN_STYLE = Automatic;
620 | DEVELOPMENT_TEAM = DZBBP5D6DJ;
621 | INFOPLIST_FILE = MVVM/Info.plist;
622 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
623 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
624 | PRODUCT_BUNDLE_IDENTIFIER = kcin.nil.MVVM;
625 | PRODUCT_NAME = "$(TARGET_NAME)";
626 | SWIFT_VERSION = 4.0;
627 | TARGETED_DEVICE_FAMILY = "1,2";
628 | };
629 | name = Release;
630 | };
631 | /* End XCBuildConfiguration section */
632 |
633 | /* Begin XCConfigurationList section */
634 | 01A279DE20735B6F0045DE1C /* Build configuration list for PBXProject "MVVM" */ = {
635 | isa = XCConfigurationList;
636 | buildConfigurations = (
637 | 01A279F320735B6F0045DE1C /* Debug */,
638 | 01A279F420735B6F0045DE1C /* Release */,
639 | );
640 | defaultConfigurationIsVisible = 0;
641 | defaultConfigurationName = Release;
642 | };
643 | 01A279F520735B6F0045DE1C /* Build configuration list for PBXNativeTarget "MVVM" */ = {
644 | isa = XCConfigurationList;
645 | buildConfigurations = (
646 | 01A279F620735B6F0045DE1C /* Debug */,
647 | 01A279F720735B6F0045DE1C /* Release */,
648 | );
649 | defaultConfigurationIsVisible = 0;
650 | defaultConfigurationName = Release;
651 | };
652 | /* End XCConfigurationList section */
653 | };
654 | rootObject = 01A279DB20735B6F0045DE1C /* Project object */;
655 | }
656 |
--------------------------------------------------------------------------------