├── CharlesGuide-Example.md ├── CharlesGuide-Install.md ├── CharlesGuide-Usage.md ├── CharlesGuide ├── .gitignore ├── .swift-version ├── Brewfile ├── CharlesGuide.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── nmint8m.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── nmint8m.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist ├── CharlesGuide.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── CharlesGuide │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ └── ItunesArtwork@2x.png │ │ ├── Contents.json │ │ ├── img-check-manual-device.imageset │ │ │ ├── Contents.json │ │ │ └── img-config-device4.png │ │ ├── img-check-proxy.imageset │ │ │ ├── Contents.json │ │ │ └── img-proxy3.png │ │ ├── img-check-ssl-cer.imageset │ │ │ ├── Contents.json │ │ │ └── img-ssl5.png │ │ ├── img-check-ssl-install.imageset │ │ │ ├── Contents.json │ │ │ └── img-ssl1.png │ │ ├── img-check-ssl.imageset │ │ │ ├── Contents.json │ │ │ └── img-ssl21.png │ │ ├── img-check-trust-ssl-simu.imageset │ │ │ ├── Contents.json │ │ │ └── img-config-simu3.png │ │ ├── img-pokemon-ball.imageset │ │ │ ├── Contents.json │ │ │ └── img-pokemon-ball@1x.png │ │ └── img-simu8.imageset │ │ │ ├── Contents.json │ │ │ └── img-simu8.png │ ├── CheckList │ │ ├── Cell │ │ │ ├── ChecklistCell.swift │ │ │ ├── ChecklistCell.xib │ │ │ └── ChecklistCellViewModel.swift │ │ ├── CheckListViewModel.swift │ │ ├── ChecklistVC.swift │ │ └── ChecklistVC.xib │ ├── Info.plist │ ├── LaunchScreen.storyboard │ ├── PokemonList │ │ ├── Cell │ │ │ ├── PokemonCell.swift │ │ │ ├── PokemonCell.xib │ │ │ └── PokemonCellViewModel.swift │ │ ├── PokemonListVC.swift │ │ ├── PokemonListVC.xib │ │ └── PokemonListViewModel.swift │ └── Service │ │ ├── API.swift │ │ ├── APIManager.swift │ │ ├── Models.swift │ │ └── PokemonService.swift ├── Gemfile ├── Gemfile.lock ├── Podfile └── Podfile.lock ├── Images ├── img-access1.png ├── img-access2.png ├── img-breakpoint1.png ├── img-breakpoint2.png ├── img-breakpoint3.png ├── img-config-device1.png ├── img-config-device2.png ├── img-config-device3.png ├── img-config-device4.png ├── img-config-simu0.png ├── img-config-simu1.png ├── img-config-simu2.png ├── img-config-simu3.png ├── img-ex1.png ├── img-ex2.png ├── img-ex3.png ├── img-ex4.png ├── img-ex5.png ├── img-ex6.png ├── img-ex7.png ├── img-ex8.png ├── img-handle.png ├── img-host1.png ├── img-host2.png ├── img-host3.png ├── img-icon.png ├── img-install1.png ├── img-install2.png ├── img-install3.png ├── img-install4.png ├── img-interface.png ├── img-local-ip1.png ├── img-local-ip2.png ├── img-pikachu@1x.png ├── img-pokemon-1.jpg ├── img-pokemon-ball@1x.png ├── img-proxy1.png ├── img-proxy2.png ├── img-proxy3.png ├── img-record1.png ├── img-record2.png ├── img-record3.png ├── img-record4.png ├── img-record5.png ├── img-register1.png ├── img-register2.png ├── img-register3.png ├── img-simu1.png ├── img-simu2.png ├── img-simu3.png ├── img-simu4.png ├── img-simu5.png ├── img-simu6.png ├── img-simu7.png ├── img-simu8.png ├── img-ssl0.png ├── img-ssl1.png ├── img-ssl2.png ├── img-ssl21.png ├── img-ssl22.png ├── img-ssl23.png ├── img-ssl3.png ├── img-ssl4.png ├── img-ssl5.png ├── img-throttlle1.png └── img-throttlle2.png ├── LICENSE └── README.md /CharlesGuide-Example.md: -------------------------------------------------------------------------------- 1 | # Charles Guide - Example 2 | 3 | _Written by **Nguyen Minh Tam**_ 4 | 5 | Ở phần trước mình đã trình bày cách cài đặt môi trường và cách làm việc cùng Charles. Trong phần này mình sẽ demo sử dụng Charles debug app nhỏ sử dụng API Pokemon [https://pokeapi.co](https://pokeapi.co). Bắt đầu thôi nào! 📱 6 | 7 | ### Cài đặt project example 8 | 9 | Mở Terminal, đến folder `CharlesGuide` (bằng cách kéo thả vào Terminal), chạy lần lượt: 10 | 11 | ``` 12 | bundle install 13 | ``` 14 | 15 | ``` 16 | bundle exec pod install 17 | ``` 18 | 19 | Mở Charles và làm các bước sau: 20 | 21 | - Cài đặt focus vào host: `pokeapi.co` 22 | - Cài đặt recording setting cho host: `pokeapi.co` 23 | - Cài đặt breakpoint setting cho path: `api/v2/pokemon` và tick chọn cả `Request` và `Response` 24 | 25 | > **My note:** 26 | > 27 | > Như mình đã đề cập ở phần trước, nên cài đặt focus và recording setting trỏ tới host, còn breakpoint setting chỉ cần quy định path là được. 28 | 29 | Sau khi cài đặt, mở file `CharlesGuide.xcworkspace` và run project. 30 | 31 | | | | 32 | |---|---| 33 | | Màn hình khởi động | Nhấn Bỏ qua / Bắt đầu | 34 | 35 | Lúc này giao diện của Charles như sau: 36 | 37 |
38 | 39 |
40 | 41 | Như bạn đã thấy, chúng ta có thể sửa request infor (như url, header,...) khi chọn tab `Edit request`. Nhưng lúc này ta sẽ tạm bỏ qua. Tiếp tục chọn `Execute`. 42 | 43 | Khi response trả về, giao diện của Charles sẽ như sau: 44 | 45 |
46 | 47 |
48 | 49 | Vào tab `JSON Text` trong `Edit response`, copy dán đoạn JSON phía dưới vào và chọn `Execute`. 50 | 51 | ``` 52 | { 53 | "count": 1, 54 | "next": "https://pokeapi.co/api/v2/pokemon?offset=20&limit=20", 55 | "previous": null, 56 | "results": [{ 57 | "name": "Pikachu", 58 | "url": "https://pokeapi.co/api/v2/pokemon/1/" 59 | }] 60 | } 61 | ``` 62 | 63 | Kết quả trên simulator/device sẽ hiển thị như sau: 64 | 65 |
66 | 67 |
68 | 69 | Như vậy, bạn đã biết cách sửa JSON của response, tiếp theo chúng ta sẽ đi tới sửa Headers của response. Bấm `Trang tiếp >`, làm như các bước trên và đợi response trả về. Bật tab `Header` tonse `Edit response`, bạn sẽ thấy như sau: 70 | 71 |
72 | 73 |
74 | 75 | Sửa `:status: 200` thành và chọn `Execute`. 76 | 77 | ``` 78 | :status: 404 79 | ``` 80 | 81 | Kết quả trên simulator/device sẽ hiển thị như sau: 82 | 83 |
84 | 85 |
86 | 87 | Sau khi nhấn `OK` để tắt pop up thì mình sẽ gọi lại API một lần nữa. 88 | 89 | Sau ví dụ đơn giản trên, mình muốn đề cập các vấn đề thường gặp khi bạn không debug được với Charles. Các lý do như sau: 90 | 91 | - Liên quan tới config lúc đầu. Đọc lại [Hướng dẫn cài đặt Charles](./CharlesGuide-Install.md) 🔧 92 | - Cài đặt sai host cho `Focus Host` và `Recording Settings` 93 | - Cài đặt sai path cho `Breakpoint Setting`. 94 | - Chưa cài đặt SSL Proxying Certificates. Đọc lại [Hướng dẫn cài đặt Charles](./CharlesGuide-Install.md) 🔧 95 | - App của bạn đang cache data response. Cụ thể như demo app đang sử dụng thư viện `Alamofire`, nó sẽ cache lại response, bởi vậy ở `AppDelegate` mình đã cài đặt không cho nó cache nữa: 96 | 97 | ```swift 98 | // AppDelegate.swift 99 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 100 | // Config not cache data 101 | URLCache.shared = URLCache(memoryCapacity: 0, diskCapacity: 0, diskPath: nil) 102 | ... 103 | } 104 | ``` 105 | 106 | Cám ơn các bạn đã đọc hết tài liệu **Charles Guide**. Ủng hộ mình tiếp tục viết blog bằng cách bấm ⭐️ cho mình nhé! 107 | 108 | ### Reference 109 | 110 | Đọc lại [Hướng dẫn cài đặt Charles](./CharlesGuide-Install.md) 🔧 111 | 112 | Đọc lại [Hướng dẫn sử dụng Charles](./CharlesGuide-Usage.md) 🗒 113 | 114 | Quay lại [Charles Guide](https://github.com/nmint8m/charlesguide) -------------------------------------------------------------------------------- /CharlesGuide-Install.md: -------------------------------------------------------------------------------- 1 | # Charles Guide - Install 2 | 3 | *Written by __Nguyen Minh Tam__* 4 | 5 | Nếu bạn là một mobile developer, mình khá chắc là bạn đã từng gặp qua tình huống éo le `vỡ UI` ít nhất một lần trong đời. `Vỡ UI` thường được bắt gặp khi mà bạn có một cái text, nội dung của cái text này được lấy bằng cách truy xuất database, hoặc từ kết quả mà API trả về. Cơ mà bạn lại quên mất việc kiểm tra UI khi nội dung cái text này dài ra trên các màn hình khác nhau, dẫn tới hậu quả UI của bạn banh chè. 6 | 7 | Mục đích của bài viết này giúp các bạn làm quen với một tool rất chy là bá đạo, cân từ debugging đến testing. 8 | 9 | Ví dụ nhé, nó giúp bạn có thể test các hiển thị lên UI của nhiều data set khác nhau như thế nào, ngay cả khi bạn chưa implement API. Trong một diễn biến khác, nó có thể giúp bạn debugging cách bạn call API đã đúng chưa: kiểm tra bạn đang gọi GET/POST/..., header ra sao, parameter như nào,... Ngoài ra nó còn cho phép bạn test những trường hợp download mạng chậm nữa. 10 | 11 | Và cái tool thần thánh được nhắc đến trong bài viết này chính là `Charles - Web Debugging Proxy Application`. 12 | 13 | > __Note:__ Các ví dụ mình đề cập trong bài viết này là những kinh nghiệm mình có được khi trong implement app trên iOS. Mình nghĩ về cơ bản thì develop Android và iOS khá giống nhau, nên chắc tài liệu này cũng có chút hữu ích với với các bạn Android developer. 14 | 15 | ## Mục lục 16 | - [Install Charles](#install-charles) 17 | - [Configure Charles và môi trường](#configure-charles-và-môi-trường) 18 | - [Config Proxy](#config-proxy) 19 | - [Enable macOS Proxy for Charles](#enable-macos-proxy-for-charles) 20 | - [Config proxy cho iOS Device](#config-proxy-cho-ios-device) 21 | - [Config proxy cho iOS Simulator](#config-proxy-cho-ios-simulator) 22 | - [Configuring SSL Proxying Certificates](#configuring-ssl-proxying-certificates) 23 | - [Add Charles CA Certificate](#add-charles-ca-certificate) 24 | - [Enable SSL Proxying Setting](#enable-ssl-proxying-setting) 25 | - [Trusting Charles's SSL Certificates](#trusting-charles-s-ssl-certificates) 26 | 27 | ## Install Charles 28 | 29 | Để cài đặt Charles cần: 30 | 31 | - Truy cập vào đường link [https://www.charlesproxy.com](https://www.charlesproxy.com/download/) và download file installer về máy. 32 | 33 |
34 | 35 |
36 | 37 | - Khởi động installer đã down về, hoàn thành theo chỉ dẫn: 38 | 39 |
40 | 41 | 42 |
43 | 44 | - Khởi động Charles: 45 | 46 |
47 | 48 |
49 | 50 | - Gói Charles free cho 30 ngày, vào đây để mua license nhé. Sau đó thì đi tới __Help > Register Charles... > Điền Register Name và License Key__ để đăng ký rồi restart lại Charles. 51 | 52 |
53 | 54 |
55 | 56 | 57 |
58 | 59 | ## Configure Charles And Environment 60 | 61 | Phần set up Charles và môi trường là phần vô cùng quan trọng. Nếu bạn set up môi trường không đúng hoặc không đầy đủ, điều tất yếu là bạn sẽ chả thể làm việc được với nó. 62 | 63 | Để giúp bạn có thể kiểm tra liệu mình đã set up đúng chưa, thì sau đây là checklist các bước mình sử dụng: 64 | 65 | - [x] Config Proxy 66 | - [x] Bật macOS Proxy cho Charles 67 | - [x] Config proxy cho device 68 | - [x] Config Configuring SSL Proxying Certificates 69 | - [x] Thêm Charles CA Certificate cho máy mac 70 | - [x] Bật SSL Proxy cho máy mac và cài đặt SSL cho tất cả host name 71 | - [x] Bật trust SSL Certìicate cho device/simulator 72 | 73 | ### Config Proxy 74 | 75 | Proxy là một Internet server làm nhiệm vụ chuyển tiếp thông tin và kiểm soát tạo sự an toàn cho việc truy cập Internet của các máy client. 76 | 77 | Khi sử dụng Charles trên máy Mac, bạn cần config cho Charles sử dụng macOS Proxy. 78 | 79 | #### Enable macOS Proxy for Charles 80 | 81 | Lần đầu sử dụng Charles bạn sẽ được tự động hỏi về việc cấp quyền macOS Proxy như sau. Chọn __Grant Privileges__ và nhập user name và password: 82 | 83 |
84 | 85 |
86 | 87 |
88 | 89 | Sau khi enable macOS Proxy cho Charles, dấu tick sẽ xuất hiện bên cạnh mục __View > macOS Proxy__ như thế này: 90 | 91 |
92 | 93 |
94 | 95 | Nếu bước trên bạn chọn __Not yet__ trong lúc cài đặt __Automatic macOS Proxy Configuration__, lần tới bạn có thế cài đặt thông qua __View > Proxy Settings...__. 96 | 97 | #### Config proxy cho iOS Device 98 | 99 | Khi bạn chọn debug app của bạn trên real device cùng với Charles, bạn cần phải trỏ HTTP Proxy của device đến máy tính mà bạn đang sử dụng. 100 | 101 | > __Lưu ý:__ Máy tính và điện thoại của bạn phải xài cùng wifi. 102 | 103 | Config cho device như sau: 104 | 105 | - Vào __Settings > Wifi__. 106 | - Chọn network đang kết nối tới. 107 | - Chọn __Config Proxy__. 108 | - Chọn __Manual__ và điền vào form, trong đó: 109 | - Server: Địa chỉ IP của máy tính đang chạy Charles 110 | - Port: Cổng mà Charles chạy (thường là 8888) 111 | - Authentication: Off 112 | 113 |
114 | 115 | 116 |
117 |
118 | 119 | 120 |
121 | 122 | Để biết local IP của máy tính, vào __Help > Local IP Address__. 123 | 124 |
125 | 126 | 127 |
128 | 129 | Tiếp tục, config access control cho máy mac như sau: 130 | 131 | - Vào __Proxy > Access Control Settings…__ để cho phép các device được phép kết nối với Charles bằng cách thêm mới. 132 | 133 |
134 | 135 | 136 |
137 | 138 | > __Lưu ý__: 139 | > Sau khi testing và debug trên device xong, nhớ setting lại wifi của device, bằng cách vào __Setting > Wifi__, chọn wifi sử dụng, chọn __Config Proxy > Off__. Nếu không làm vậy thì điện thoại của bạn không xài wifi được đâu. 140 | 141 | #### Config proxy cho iOS Simulator 142 | 143 | Simulator đã sử dụng system proxy, nên không cần phải config proxy cho simulator nữa. Nếu simulator vẫn gặp trục trặc, hãy khởi động lại simulator. 144 | 145 | ### Configuring SSL Proxying Certificates 146 | 147 |
148 | 149 |
150 | 151 | Charles có thể được sử dụng như một HTTPS proxy ở giữa, cho phép bạn đọc dữ liệu giao tiếp giữa web browser và SSL web server. Thay vì browser sẽ đọc certificate của server, Charles lúc này sẽ tạo một certificate cho server và xác thực bằng chính root certificate của nó (Charles CA Certificate). Charles nhận certificate của server trong khi browser của bạn nhận certificate của Charles. Vậy nên bạn sẽ nhận được thông báo bảo mật nói rằng `the root authority is not trusted`. Thông báo này sẽ không xuất hiện nữa nếu bạn đã thêm Charles CA Certificate vào trusted certificates. 152 | 153 | #### Add Charles CA Certificate 154 | 155 | - Vào __Help > SSL Proxying > Install Charles Root Certificates__. 156 | - Thêm __Certificates__. 157 | - Chọn __Charles Proxy CA__. 158 | - Chọn __Always Trust__. 159 | - Kết quả sẽ được như sau. 160 | 161 |
162 | 163 |
164 | 165 |
166 | 167 |
168 | 169 |
170 | 171 |
172 | 173 | Tiếp theo, bạn cần xác định những host name mà bạn muốn bật SSL proxy. Với mình thì mình bật cho tất cả các host name. 174 | 175 | #### Enable SSL Proxying Setting 176 | 177 | - __Proxy > SSL Proxying Settings...__ 178 | - Ở tab __SSL Proxying__, chọn __Enable SSL Proxying__. 179 | - Thêm __Location__. 180 | - Khởi động lại browser đang sử dụng (Safari/Chrome/...) để áp dụng thay đổi. 181 | 182 |
183 | 184 |
185 | 186 | 187 |
188 | 189 | #### Trusting Charles's SSL Certificates 190 | 191 | Vì kể từ lúc này Charles sẽ tự tạo certificate của chính nó, được xác thực bằng Charles Root Certificate, nên khi bạn test hoặc debug trên device/simulator, bạn sẽ nhận được cảnh báo Charles Root Certificate không thuộc trong list trusted root certificate của device/simulator. Đó là lý do ta cài đặt Charles’s Root Certificate và bật Certificate Trust. 192 | 193 | __Đối với iOS Device__ 194 | 195 | - Thực hiện cài đặt iOS device sử dụng Charles proxy như trong mục [Config proxy cho iOS Device](#config-proxy-cho-iOS-Device) 196 | - Mở Safari và truy cập tới [https://chls.pro/ssl](https://chls.pro/ssl) và chọn `Install` SSL certificate. 197 | - Tiếp tục vào __Settings > General > About > Certificate Trust Settings__. 198 | - Dưới mục __Enable full trust for root certificates__, bật cetificate cho Charles Proxy. 199 | 200 |
201 | 202 | 203 | 204 |
205 | 206 | __Đối với iOS Simulator__ 207 | 208 | - Cài cho tất cả các simulator: 209 | - Tắt tất cả simulator 210 | - Trên Charles, vào __Help > SSL Proxying > Install Charles Root Certificate in iOS Simulators__. 211 | 212 |
213 | 214 |
215 | 216 | - Cách trên đôi khi không thành công. Ta có thể thực hiện cài cho simulator bạn cần dùng như sau: 217 | - Vào __Help > SSL Proxying > Save Charles Root Certificate…__ để lưu file *.pem ra Desktop. 218 | - Kéo thả file *.pem vừa tạo vào simulator. 219 | 220 |
221 | 222 | 223 |
224 | 225 | - Thực hiện cài đặt certificate cho simulator. 226 | 227 |
228 | 229 | 230 | 231 | 232 | 233 |
234 | 235 | - Bật trust certificate cho simulator bằng cách: 236 | - Vào __Settings > General > About > Certificate Trust Settings__. 237 | - Dưới mục __Enable full trust for root certificates__, bật cetificate cho Charles Proxy. 238 | 239 |
240 | 241 | 242 | 243 |
244 | 245 | Nhắc lại checklist lần nữa để các bạn không quên: 246 | 247 | - [x] Config Proxy 248 | - [x] Bật macOS Proxy cho Charles 249 | - [x] Config proxy cho device 250 | - [x] Config Configuring SSL Proxying Certificates 251 | - [x] Thêm Charles CA Certificate cho máy mac 252 | - [x] Bật SSL Proxy cho máy mac và cài đặt SSL cho tất cả host name 253 | - [x] Bật trust SSL Certificate cho device/simulator 254 | 255 | Phía trên là các bước cài đặt môi trường làm việc cùng Charles. Hi vọng mọi người đã biết được một vài kiến thức hữu ích khi đọc tài liệu này. Ủng hộ mình một ⭐️ để có thêm động lực viết bài nhé! Cảm ơn mọi người. 🎉 256 | 257 | ### Reference 258 | 259 | Đọc tiếp [Hướng dẫn sử dụng Charles](./CharlesGuide-Usage.md) 🗒 260 | 261 | Đọc tiếp [Demo debugging an iOS app with dummy data from Charles](./CharlesGuide-Example.md) 🐞 262 | 263 | Quay lại [Charles Guide](https://github.com/nmint8m/charlesguide) -------------------------------------------------------------------------------- /CharlesGuide-Usage.md: -------------------------------------------------------------------------------- 1 | # Charles Guide - Usage 2 | 3 | _Written by **Nguyen Minh Tam**_ 4 | 5 | Ở phần trước mình đã trình bày cách cài đặt môi trường để làm việc cùng Charles. Trong phần này mình sẽ đề cập đến giao diện, những chức năng cơ bản và thường được sử dụng nhất của nó. Let's check it out! 🥇 6 | 7 | ## Mục lục 8 | 9 | - [Work with Charles Proxy](#work-with-charles-proxy) 10 | - [Application Interface](#application-interface) 11 | - [Focus](#focus) 12 | - [Recording settings](#recording-settings) 13 | - [Breakpoint](#breakpoint) 14 | - [Handling breakpoint](#handling-breakpoint) 15 | - [Throttle settings](#throttle-settings) 16 | 17 | ## Work with Charles Proxy 18 | 19 | ### Application Interface 20 | 21 |
22 | 23 |
24 | 25 | Các nút thông dụng: 26 | 27 | 1. **Clear the current session**: 28 | - Session chứa tất cả các thông tin được ghi lại. 29 | - Khi session đầy/busy, có thể clean session. 30 | 31 | 2. **Start/stop recording**: 32 | - Record là chức năng căn bản của Charles. 33 | - Request và response được lưu lại vào session hiện tại chỉ khi chức năng Record bật. 34 | - Request hiển thị trên màn hình session khi nó được lưu lại. Có thể xem request ở 2 chế độ: Structure và Sequence. 35 | 36 | 3. **Start/stop throttling**: Điều chỉnh băng thông 37 | 38 | 4. **Enable/Disable breakpoints**: Bật breakpoint để debug request/response 39 | 40 | 5. **Compose**: Soạn một request mới 41 | 42 | 6. **Repeat**: Thực hiện lại request được chọn 43 | 44 | 7. **Tool**: Active/deactive các tool như 45 | - Breakpoint 46 | - No caching 47 | 48 | 8. **Settings**: 49 | - Recording settings 50 | - Access control settings 51 | 52 | Nhưng trước khi đi vào sử dụng, chúng ta sẽ đi qua một số bước giúp hạn chế focus vào các host không cần thiết và cài đặt record lại những request/response cần thiết. 53 | 54 | ### Focus 55 | 56 | Vì có rất nhiều request và response từ vô số host trả về. Bước focus này giúp Charles tách riêng những host mà chúng ta cần quan tâm, tránh quá tải. 57 | 58 | - Vào **View > Focused Host > Add focused hosts...** 59 | - Adding protocol/host/port: `*.t8m.dev` 60 | 61 |
62 | 63 |
64 | 65 |
66 | 67 |
68 | 69 | ### Recording settings 70 | 71 | Bước này dùng để lọc ra những thứ mà chúng ta sẽ record lại trong session. 72 | 73 | - Vào **Settings** của session cần lọc, hoặc vào **Proxy > Recording Settings...** 74 | - Chọn tab **Include** 75 | - Thêm Locations. 76 | 77 | > **My note:** Thường thì mình chỉ thêm host vào đối với Record settings, còn path/query thì mình hay đặt trong Breakpoint setting. Còn protocol/port/query thì mình để trống, và chỉ quan tâm nó khi debug request. Mình sẽ nói rõ hơn ở phía dưới. 78 | 79 |
80 | 81 | hoặc 82 | 83 |
84 | 85 |
86 | 87 |
88 | 89 |
90 |
91 | 92 | > **My note:** Nếu như bạn không thấy trên Charles hiển thị request mà bạn đang debug, thì rất có thể bạn đã nhầm hoặc miss location ở bước này. Nhớ kiểm tra lại bạn đã add host bạn cần và check nó chưa nhé. 💣💣💣 93 | 94 | Vậy là xong phần config cơ bản, tiếp theo đây chúng ta sẽ nói về cách debug cùng Breakpoint và Throttle settings. Trước khi debug với một web/ios/android app, bạn cần phải cài đặt môi trường và cài certificate đầy đủ. Đọc cài đặt môi trường debug cho iOS app tại [đây](./CharlesGuide-Install.md) 95 | 96 | Check list: 97 | 98 | - [x] Config Proxy 99 | - [x] Bật macOS Proxy cho Charles 100 | - [x] Config proxy cho device 101 | - [x] Config Configuring SSL Proxying Certificates 102 | - [x] Thêm Charles CA Certificate cho máy mac 103 | - [x] Bật SSL Proxy cho máy mac và cài đặt SSL cho tất cả host name 104 | - [x] Bật trust SSL Certificate cho device/simulator 105 | 106 | Phần đầu tiên, ta sẽ học cách đặt breakpoint. 107 | 108 | ### Breakpoint 109 | 110 | - Vào **Proxy > Breakpoint Settings...** 111 | - Thêm breakpoint: 112 | - Adding host: Vì mình đã cài đặt trong `Recording setting` ở phía trên rồi, nên ở bước này có thể bỏ qua. Nếu bạn điển phần này, thì nhớ phải check lại trong Recording Setting xem host bạn điền đã được add và check chưa. 113 | - Adding path: `v1/item/search` 114 | - Add query: `name=somename` Có thể để trống bước này 115 | - Check breakpoints: `request/response` 116 | - Nếu bạn tick `request`, thì khi app gọi tới url này, bạn có thể thay đổi thông tin của request như heading, parameter, method,... 117 | - Nếu bạn tick `response`, bạn có khả năng thay đổi các thông tin như cấu trúc response xml, code,... 118 | 119 |
120 | 121 |
122 | 123 |
124 | 125 |
126 | 127 | Trên đây là ví dụ breakpoint đặt ở `response` với path là `v1/item/search`. Vậy khi app gọi tới url này và server phản hồi, Charles sẽ bật cửa sổ breakpoint request lên. Cách handle breakpoint sẽ được đề cập ngay bên dưới. 128 | 129 | ### Handling breakpoint 130 | 131 | Sau khi đã đặt được breakpoint thì bước này dễ như ăn kẹo. 132 | 133 |
134 | 135 |
136 | 137 | - Tab **Overview** để theo dõi thông tin cơ bản của request và response: url, protocol, response code, method, content type... 138 | - Tab **Request** để xem thông tin chi tiết của request 139 | - Tab **Edit Response** để xem và sửa đổi thông tin chi tiết của response. Ví dụ như: 140 | - Header: Editing header 141 | - JSON text: Editing body 142 | - Tab **Execute** để tiếp tục 143 | 144 | ### Throttle settings 145 | 146 | Như mình đã đề cập, chức năng này được sử dụng để điều chỉnh băng thông chậm: download, upload... 147 | 148 | - Vào **Proxy > Throttle Settings...** 149 | - Config throttle setting 150 | 151 |
152 | 153 |
154 | 155 |
156 | 157 | Phía trên là những chức năng cơ bản và thường được sử dụng nhất của Charles. Hi vọng mọi người đã biết được một vài kiến thức hữu ích khi đọc tài liệu này. Ủng hộ mình một ⭐️ để có thêm động lực viết bài nhé! Cảm ơn mọi người. 🎉 158 | 159 | ### Reference 160 | 161 | Đọc lại [Hướng dẫn cài đặt Charles](./CharlesGuide-Install.md) 🔧 162 | 163 | Đọc tiếp [Demo debugging an iOS app with dummy data from Charles](./CharlesGuide-Example.md) 🐞 164 | 165 | Quay lại [Charles Guide](https://github.com/nmint8m/charlesguide) -------------------------------------------------------------------------------- /CharlesGuide/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | ## Build generated 3 | build/ 4 | DerivedData/ 5 | 6 | ## Various settings 7 | *.pbxuser 8 | !default.pbxuser 9 | *.mode1v3 10 | !default.mode1v3 11 | *.mode2v3 12 | !default.mode2v3 13 | *.perspectivev3 14 | !default.perspectivev3 15 | xcuserdata/ 16 | 17 | ## Other 18 | *.moved-aside 19 | *.xccheckout 20 | *.xcscmblueprint 21 | 22 | ## Obj-C/Swift specific 23 | *.hmap 24 | *.ipa 25 | *.dSYM.zip 26 | *.dSYM 27 | 28 | ## Playgrounds 29 | timeline.xctimeline 30 | playground.xcworkspace 31 | 32 | # Swift Package Manager 33 | .build/ 34 | BuildExport 35 | 36 | # CocoaPods 37 | Pods 38 | 39 | # Carthage 40 | Carthage/Build 41 | 42 | # fastlane 43 | 44 | fastlane/report.xml 45 | fastlane/Preview.html 46 | fastlane/screenshots 47 | fastlane/test_output 48 | fastlane/README.md 49 | fastlane/release_notes.txt 50 | 51 | # Bundler 52 | .bundle 53 | vendor/bundle 54 | *.coverage.txt 55 | swiftlint-report.json 56 | -------------------------------------------------------------------------------- /CharlesGuide/.swift-version: -------------------------------------------------------------------------------- 1 | 4.0.3 -------------------------------------------------------------------------------- /CharlesGuide/Brewfile: -------------------------------------------------------------------------------- 1 | brew 'jq' 2 | -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 22535C0D225C5EC40000F46A /* PokemonService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22535C0C225C5EC40000F46A /* PokemonService.swift */; }; 11 | 22535C12225C63B50000F46A /* PokemonCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22535C10225C63B50000F46A /* PokemonCell.swift */; }; 12 | 22535C13225C63B50000F46A /* PokemonCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 22535C11225C63B50000F46A /* PokemonCell.xib */; }; 13 | 22535C15225C63CE0000F46A /* PokemonListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22535C14225C63CE0000F46A /* PokemonListViewModel.swift */; }; 14 | 22535C17225C63DC0000F46A /* PokemonCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22535C16225C63DC0000F46A /* PokemonCellViewModel.swift */; }; 15 | 225C701C225A4D6F0077D006 /* APIManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 225C701B225A4D6F0077D006 /* APIManager.swift */; }; 16 | 225C701F225A54CC0077D006 /* Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 225C701E225A54CC0077D006 /* Models.swift */; }; 17 | 2272BC2F225CA0E600846CC4 /* CheckListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2272BC2E225CA0E600846CC4 /* CheckListViewModel.swift */; }; 18 | 2272BC31225CA15E00846CC4 /* ChecklistCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2272BC30225CA15E00846CC4 /* ChecklistCellViewModel.swift */; }; 19 | 229581532259D43A00B1343B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 229581522259D43A00B1343B /* LaunchScreen.storyboard */; }; 20 | 229581562259D5A300B1343B /* ChecklistVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 229581542259D5A300B1343B /* ChecklistVC.swift */; }; 21 | 229581572259D5A300B1343B /* ChecklistVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 229581552259D5A300B1343B /* ChecklistVC.xib */; }; 22 | 2295815C2259DF6B00B1343B /* PokemonListVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2295815A2259DF6B00B1343B /* PokemonListVC.swift */; }; 23 | 2295815D2259DF6B00B1343B /* PokemonListVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2295815B2259DF6B00B1343B /* PokemonListVC.xib */; }; 24 | 229581692259E00700B1343B /* ChecklistCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 229581672259E00700B1343B /* ChecklistCell.swift */; }; 25 | 2295816A2259E00700B1343B /* ChecklistCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 229581682259E00700B1343B /* ChecklistCell.xib */; }; 26 | 22C094D42259F6F800EFED9D /* API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22C094D32259F6F800EFED9D /* API.swift */; }; 27 | 22E4AD9522361C2100164CE0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22E4AD9422361C2100164CE0 /* AppDelegate.swift */; }; 28 | 22E4AD9C22361C2400164CE0 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 22E4AD9B22361C2400164CE0 /* Assets.xcassets */; }; 29 | DC890A6963AFE115DC731D9A /* Pods_CharlesGuide.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 83553143EBC88C79029F136A /* Pods_CharlesGuide.framework */; }; 30 | /* End PBXBuildFile section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | 22535C0C225C5EC40000F46A /* PokemonService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PokemonService.swift; sourceTree = ""; }; 34 | 22535C10225C63B50000F46A /* PokemonCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PokemonCell.swift; sourceTree = ""; }; 35 | 22535C11225C63B50000F46A /* PokemonCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PokemonCell.xib; sourceTree = ""; }; 36 | 22535C14225C63CE0000F46A /* PokemonListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PokemonListViewModel.swift; sourceTree = ""; }; 37 | 22535C16225C63DC0000F46A /* PokemonCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PokemonCellViewModel.swift; sourceTree = ""; }; 38 | 225C701B225A4D6F0077D006 /* APIManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIManager.swift; sourceTree = ""; }; 39 | 225C701E225A54CC0077D006 /* Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Models.swift; sourceTree = ""; }; 40 | 2272BC2E225CA0E600846CC4 /* CheckListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckListViewModel.swift; sourceTree = ""; }; 41 | 2272BC30225CA15E00846CC4 /* ChecklistCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChecklistCellViewModel.swift; sourceTree = ""; }; 42 | 229581522259D43A00B1343B /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; 43 | 229581542259D5A300B1343B /* ChecklistVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChecklistVC.swift; sourceTree = ""; }; 44 | 229581552259D5A300B1343B /* ChecklistVC.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ChecklistVC.xib; sourceTree = ""; }; 45 | 2295815A2259DF6B00B1343B /* PokemonListVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PokemonListVC.swift; sourceTree = ""; }; 46 | 2295815B2259DF6B00B1343B /* PokemonListVC.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PokemonListVC.xib; sourceTree = ""; }; 47 | 229581672259E00700B1343B /* ChecklistCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChecklistCell.swift; sourceTree = ""; }; 48 | 229581682259E00700B1343B /* ChecklistCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ChecklistCell.xib; sourceTree = ""; }; 49 | 22C094D32259F6F800EFED9D /* API.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = API.swift; sourceTree = ""; }; 50 | 22E4AD9122361C2100164CE0 /* CharlesGuide.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CharlesGuide.app; sourceTree = BUILT_PRODUCTS_DIR; }; 51 | 22E4AD9422361C2100164CE0 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 52 | 22E4AD9B22361C2400164CE0 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 53 | 22E4ADA022361C2400164CE0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 54 | 3091CF309803E9A527B71F24 /* Pods-CharlesGuide.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CharlesGuide.release.xcconfig"; path = "Pods/Target Support Files/Pods-CharlesGuide/Pods-CharlesGuide.release.xcconfig"; sourceTree = ""; }; 55 | 83553143EBC88C79029F136A /* Pods_CharlesGuide.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_CharlesGuide.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 56 | 855A86287F5C3FF8EB4C681B /* Pods-CharlesGuide.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-CharlesGuide.debug.xcconfig"; path = "Pods/Target Support Files/Pods-CharlesGuide/Pods-CharlesGuide.debug.xcconfig"; sourceTree = ""; }; 57 | /* End PBXFileReference section */ 58 | 59 | /* Begin PBXFrameworksBuildPhase section */ 60 | 22E4AD8E22361C2100164CE0 /* Frameworks */ = { 61 | isa = PBXFrameworksBuildPhase; 62 | buildActionMask = 2147483647; 63 | files = ( 64 | DC890A6963AFE115DC731D9A /* Pods_CharlesGuide.framework in Frameworks */, 65 | ); 66 | runOnlyForDeploymentPostprocessing = 0; 67 | }; 68 | /* End PBXFrameworksBuildPhase section */ 69 | 70 | /* Begin PBXGroup section */ 71 | 22535C0F225C639B0000F46A /* Cell */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | 22535C10225C63B50000F46A /* PokemonCell.swift */, 75 | 22535C11225C63B50000F46A /* PokemonCell.xib */, 76 | 22535C16225C63DC0000F46A /* PokemonCellViewModel.swift */, 77 | ); 78 | path = Cell; 79 | sourceTree = ""; 80 | }; 81 | 229581582259DF2600B1343B /* CheckList */ = { 82 | isa = PBXGroup; 83 | children = ( 84 | 2295815E2259DF9700B1343B /* Cell */, 85 | 229581542259D5A300B1343B /* ChecklistVC.swift */, 86 | 229581552259D5A300B1343B /* ChecklistVC.xib */, 87 | 2272BC2E225CA0E600846CC4 /* CheckListViewModel.swift */, 88 | ); 89 | path = CheckList; 90 | sourceTree = ""; 91 | }; 92 | 229581592259DF4F00B1343B /* PokemonList */ = { 93 | isa = PBXGroup; 94 | children = ( 95 | 22535C0F225C639B0000F46A /* Cell */, 96 | 2295815A2259DF6B00B1343B /* PokemonListVC.swift */, 97 | 2295815B2259DF6B00B1343B /* PokemonListVC.xib */, 98 | 22535C14225C63CE0000F46A /* PokemonListViewModel.swift */, 99 | ); 100 | path = PokemonList; 101 | sourceTree = ""; 102 | }; 103 | 2295815E2259DF9700B1343B /* Cell */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | 229581672259E00700B1343B /* ChecklistCell.swift */, 107 | 229581682259E00700B1343B /* ChecklistCell.xib */, 108 | 2272BC30225CA15E00846CC4 /* ChecklistCellViewModel.swift */, 109 | ); 110 | path = Cell; 111 | sourceTree = ""; 112 | }; 113 | 22C094D22259F6CA00EFED9D /* Service */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | 22C094D32259F6F800EFED9D /* API.swift */, 117 | 225C701B225A4D6F0077D006 /* APIManager.swift */, 118 | 225C701E225A54CC0077D006 /* Models.swift */, 119 | 22535C0C225C5EC40000F46A /* PokemonService.swift */, 120 | ); 121 | path = Service; 122 | sourceTree = ""; 123 | }; 124 | 22E4AD8822361C2100164CE0 = { 125 | isa = PBXGroup; 126 | children = ( 127 | 22E4AD9322361C2100164CE0 /* CharlesGuide */, 128 | 22E4AD9222361C2100164CE0 /* Products */, 129 | 2E07D2D771D986F42442D12D /* Pods */, 130 | D0C28807BAB3E6E35F7B3412 /* Frameworks */, 131 | ); 132 | sourceTree = ""; 133 | }; 134 | 22E4AD9222361C2100164CE0 /* Products */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | 22E4AD9122361C2100164CE0 /* CharlesGuide.app */, 138 | ); 139 | name = Products; 140 | sourceTree = ""; 141 | }; 142 | 22E4AD9322361C2100164CE0 /* CharlesGuide */ = { 143 | isa = PBXGroup; 144 | children = ( 145 | 22C094D22259F6CA00EFED9D /* Service */, 146 | 229581592259DF4F00B1343B /* PokemonList */, 147 | 229581582259DF2600B1343B /* CheckList */, 148 | 22E4AD9422361C2100164CE0 /* AppDelegate.swift */, 149 | 22E4AD9B22361C2400164CE0 /* Assets.xcassets */, 150 | 22E4ADA022361C2400164CE0 /* Info.plist */, 151 | 229581522259D43A00B1343B /* LaunchScreen.storyboard */, 152 | ); 153 | path = CharlesGuide; 154 | sourceTree = ""; 155 | }; 156 | 2E07D2D771D986F42442D12D /* Pods */ = { 157 | isa = PBXGroup; 158 | children = ( 159 | 855A86287F5C3FF8EB4C681B /* Pods-CharlesGuide.debug.xcconfig */, 160 | 3091CF309803E9A527B71F24 /* Pods-CharlesGuide.release.xcconfig */, 161 | ); 162 | name = Pods; 163 | sourceTree = ""; 164 | }; 165 | D0C28807BAB3E6E35F7B3412 /* Frameworks */ = { 166 | isa = PBXGroup; 167 | children = ( 168 | 83553143EBC88C79029F136A /* Pods_CharlesGuide.framework */, 169 | ); 170 | name = Frameworks; 171 | sourceTree = ""; 172 | }; 173 | /* End PBXGroup section */ 174 | 175 | /* Begin PBXNativeTarget section */ 176 | 22E4AD9022361C2100164CE0 /* CharlesGuide */ = { 177 | isa = PBXNativeTarget; 178 | buildConfigurationList = 22E4ADA322361C2400164CE0 /* Build configuration list for PBXNativeTarget "CharlesGuide" */; 179 | buildPhases = ( 180 | E3F2FF138B599D64C47AE19A /* [CP] Check Pods Manifest.lock */, 181 | 22E4AD8D22361C2100164CE0 /* Sources */, 182 | 22E4AD8E22361C2100164CE0 /* Frameworks */, 183 | 22E4AD8F22361C2100164CE0 /* Resources */, 184 | C4FA06F8491C0F374B3454AE /* [CP] Embed Pods Frameworks */, 185 | ); 186 | buildRules = ( 187 | ); 188 | dependencies = ( 189 | ); 190 | name = CharlesGuide; 191 | productName = CharlesGuide; 192 | productReference = 22E4AD9122361C2100164CE0 /* CharlesGuide.app */; 193 | productType = "com.apple.product-type.application"; 194 | }; 195 | /* End PBXNativeTarget section */ 196 | 197 | /* Begin PBXProject section */ 198 | 22E4AD8922361C2100164CE0 /* Project object */ = { 199 | isa = PBXProject; 200 | attributes = { 201 | LastSwiftUpdateCheck = 1010; 202 | LastUpgradeCheck = 1010; 203 | ORGANIZATIONNAME = "Tam Nguyen M."; 204 | TargetAttributes = { 205 | 22E4AD9022361C2100164CE0 = { 206 | CreatedOnToolsVersion = 10.1; 207 | }; 208 | }; 209 | }; 210 | buildConfigurationList = 22E4AD8C22361C2100164CE0 /* Build configuration list for PBXProject "CharlesGuide" */; 211 | compatibilityVersion = "Xcode 9.3"; 212 | developmentRegion = en; 213 | hasScannedForEncodings = 0; 214 | knownRegions = ( 215 | en, 216 | Base, 217 | ); 218 | mainGroup = 22E4AD8822361C2100164CE0; 219 | productRefGroup = 22E4AD9222361C2100164CE0 /* Products */; 220 | projectDirPath = ""; 221 | projectRoot = ""; 222 | targets = ( 223 | 22E4AD9022361C2100164CE0 /* CharlesGuide */, 224 | ); 225 | }; 226 | /* End PBXProject section */ 227 | 228 | /* Begin PBXResourcesBuildPhase section */ 229 | 22E4AD8F22361C2100164CE0 /* Resources */ = { 230 | isa = PBXResourcesBuildPhase; 231 | buildActionMask = 2147483647; 232 | files = ( 233 | 229581532259D43A00B1343B /* LaunchScreen.storyboard in Resources */, 234 | 2295815D2259DF6B00B1343B /* PokemonListVC.xib in Resources */, 235 | 22E4AD9C22361C2400164CE0 /* Assets.xcassets in Resources */, 236 | 2295816A2259E00700B1343B /* ChecklistCell.xib in Resources */, 237 | 229581572259D5A300B1343B /* ChecklistVC.xib in Resources */, 238 | 22535C13225C63B50000F46A /* PokemonCell.xib in Resources */, 239 | ); 240 | runOnlyForDeploymentPostprocessing = 0; 241 | }; 242 | /* End PBXResourcesBuildPhase section */ 243 | 244 | /* Begin PBXShellScriptBuildPhase section */ 245 | C4FA06F8491C0F374B3454AE /* [CP] Embed Pods Frameworks */ = { 246 | isa = PBXShellScriptBuildPhase; 247 | buildActionMask = 2147483647; 248 | files = ( 249 | ); 250 | inputFileListPaths = ( 251 | ); 252 | inputPaths = ( 253 | "${SRCROOT}/Pods/Target Support Files/Pods-CharlesGuide/Pods-CharlesGuide-frameworks.sh", 254 | "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework", 255 | "${BUILT_PRODUCTS_DIR}/ObjectMapper/ObjectMapper.framework", 256 | "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework", 257 | ); 258 | name = "[CP] Embed Pods Frameworks"; 259 | outputFileListPaths = ( 260 | ); 261 | outputPaths = ( 262 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework", 263 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ObjectMapper.framework", 264 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework", 265 | ); 266 | runOnlyForDeploymentPostprocessing = 0; 267 | shellPath = /bin/sh; 268 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-CharlesGuide/Pods-CharlesGuide-frameworks.sh\"\n"; 269 | showEnvVarsInLog = 0; 270 | }; 271 | E3F2FF138B599D64C47AE19A /* [CP] Check Pods Manifest.lock */ = { 272 | isa = PBXShellScriptBuildPhase; 273 | buildActionMask = 2147483647; 274 | files = ( 275 | ); 276 | inputFileListPaths = ( 277 | ); 278 | inputPaths = ( 279 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 280 | "${PODS_ROOT}/Manifest.lock", 281 | ); 282 | name = "[CP] Check Pods Manifest.lock"; 283 | outputFileListPaths = ( 284 | ); 285 | outputPaths = ( 286 | "$(DERIVED_FILE_DIR)/Pods-CharlesGuide-checkManifestLockResult.txt", 287 | ); 288 | runOnlyForDeploymentPostprocessing = 0; 289 | shellPath = /bin/sh; 290 | 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"; 291 | showEnvVarsInLog = 0; 292 | }; 293 | /* End PBXShellScriptBuildPhase section */ 294 | 295 | /* Begin PBXSourcesBuildPhase section */ 296 | 22E4AD8D22361C2100164CE0 /* Sources */ = { 297 | isa = PBXSourcesBuildPhase; 298 | buildActionMask = 2147483647; 299 | files = ( 300 | 229581692259E00700B1343B /* ChecklistCell.swift in Sources */, 301 | 2272BC31225CA15E00846CC4 /* ChecklistCellViewModel.swift in Sources */, 302 | 22535C0D225C5EC40000F46A /* PokemonService.swift in Sources */, 303 | 22C094D42259F6F800EFED9D /* API.swift in Sources */, 304 | 2295815C2259DF6B00B1343B /* PokemonListVC.swift in Sources */, 305 | 229581562259D5A300B1343B /* ChecklistVC.swift in Sources */, 306 | 225C701C225A4D6F0077D006 /* APIManager.swift in Sources */, 307 | 22E4AD9522361C2100164CE0 /* AppDelegate.swift in Sources */, 308 | 225C701F225A54CC0077D006 /* Models.swift in Sources */, 309 | 22535C17225C63DC0000F46A /* PokemonCellViewModel.swift in Sources */, 310 | 2272BC2F225CA0E600846CC4 /* CheckListViewModel.swift in Sources */, 311 | 22535C15225C63CE0000F46A /* PokemonListViewModel.swift in Sources */, 312 | 22535C12225C63B50000F46A /* PokemonCell.swift in Sources */, 313 | ); 314 | runOnlyForDeploymentPostprocessing = 0; 315 | }; 316 | /* End PBXSourcesBuildPhase section */ 317 | 318 | /* Begin XCBuildConfiguration section */ 319 | 22E4ADA122361C2400164CE0 /* Debug */ = { 320 | isa = XCBuildConfiguration; 321 | buildSettings = { 322 | ALWAYS_SEARCH_USER_PATHS = NO; 323 | CLANG_ANALYZER_NONNULL = YES; 324 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 325 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 326 | CLANG_CXX_LIBRARY = "libc++"; 327 | CLANG_ENABLE_MODULES = YES; 328 | CLANG_ENABLE_OBJC_ARC = YES; 329 | CLANG_ENABLE_OBJC_WEAK = YES; 330 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 331 | CLANG_WARN_BOOL_CONVERSION = YES; 332 | CLANG_WARN_COMMA = YES; 333 | CLANG_WARN_CONSTANT_CONVERSION = YES; 334 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 335 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 336 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 337 | CLANG_WARN_EMPTY_BODY = YES; 338 | CLANG_WARN_ENUM_CONVERSION = YES; 339 | CLANG_WARN_INFINITE_RECURSION = YES; 340 | CLANG_WARN_INT_CONVERSION = YES; 341 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 342 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 343 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 344 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 345 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 346 | CLANG_WARN_STRICT_PROTOTYPES = YES; 347 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 348 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 349 | CLANG_WARN_UNREACHABLE_CODE = YES; 350 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 351 | CODE_SIGN_IDENTITY = "iPhone Developer"; 352 | COPY_PHASE_STRIP = NO; 353 | DEBUG_INFORMATION_FORMAT = dwarf; 354 | ENABLE_STRICT_OBJC_MSGSEND = YES; 355 | ENABLE_TESTABILITY = YES; 356 | GCC_C_LANGUAGE_STANDARD = gnu11; 357 | GCC_DYNAMIC_NO_PIC = NO; 358 | GCC_NO_COMMON_BLOCKS = YES; 359 | GCC_OPTIMIZATION_LEVEL = 0; 360 | GCC_PREPROCESSOR_DEFINITIONS = ( 361 | "DEBUG=1", 362 | "$(inherited)", 363 | ); 364 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 365 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 366 | GCC_WARN_UNDECLARED_SELECTOR = YES; 367 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 368 | GCC_WARN_UNUSED_FUNCTION = YES; 369 | GCC_WARN_UNUSED_VARIABLE = YES; 370 | IPHONEOS_DEPLOYMENT_TARGET = 12.1; 371 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 372 | MTL_FAST_MATH = YES; 373 | ONLY_ACTIVE_ARCH = YES; 374 | SDKROOT = iphoneos; 375 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 376 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 377 | }; 378 | name = Debug; 379 | }; 380 | 22E4ADA222361C2400164CE0 /* Release */ = { 381 | isa = XCBuildConfiguration; 382 | buildSettings = { 383 | ALWAYS_SEARCH_USER_PATHS = NO; 384 | CLANG_ANALYZER_NONNULL = YES; 385 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 386 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 387 | CLANG_CXX_LIBRARY = "libc++"; 388 | CLANG_ENABLE_MODULES = YES; 389 | CLANG_ENABLE_OBJC_ARC = YES; 390 | CLANG_ENABLE_OBJC_WEAK = YES; 391 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 392 | CLANG_WARN_BOOL_CONVERSION = YES; 393 | CLANG_WARN_COMMA = YES; 394 | CLANG_WARN_CONSTANT_CONVERSION = YES; 395 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 396 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 397 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 398 | CLANG_WARN_EMPTY_BODY = YES; 399 | CLANG_WARN_ENUM_CONVERSION = YES; 400 | CLANG_WARN_INFINITE_RECURSION = YES; 401 | CLANG_WARN_INT_CONVERSION = YES; 402 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 403 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 404 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 405 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 406 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 407 | CLANG_WARN_STRICT_PROTOTYPES = YES; 408 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 409 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 410 | CLANG_WARN_UNREACHABLE_CODE = YES; 411 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 412 | CODE_SIGN_IDENTITY = "iPhone Developer"; 413 | COPY_PHASE_STRIP = NO; 414 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 415 | ENABLE_NS_ASSERTIONS = NO; 416 | ENABLE_STRICT_OBJC_MSGSEND = YES; 417 | GCC_C_LANGUAGE_STANDARD = gnu11; 418 | GCC_NO_COMMON_BLOCKS = YES; 419 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 420 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 421 | GCC_WARN_UNDECLARED_SELECTOR = YES; 422 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 423 | GCC_WARN_UNUSED_FUNCTION = YES; 424 | GCC_WARN_UNUSED_VARIABLE = YES; 425 | IPHONEOS_DEPLOYMENT_TARGET = 12.1; 426 | MTL_ENABLE_DEBUG_INFO = NO; 427 | MTL_FAST_MATH = YES; 428 | SDKROOT = iphoneos; 429 | SWIFT_COMPILATION_MODE = wholemodule; 430 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 431 | VALIDATE_PRODUCT = YES; 432 | }; 433 | name = Release; 434 | }; 435 | 22E4ADA422361C2400164CE0 /* Debug */ = { 436 | isa = XCBuildConfiguration; 437 | baseConfigurationReference = 855A86287F5C3FF8EB4C681B /* Pods-CharlesGuide.debug.xcconfig */; 438 | buildSettings = { 439 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 440 | CODE_SIGN_STYLE = Automatic; 441 | DEVELOPMENT_TEAM = D4Q882ZZC3; 442 | INFOPLIST_FILE = CharlesGuide/Info.plist; 443 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 444 | LD_RUNPATH_SEARCH_PATHS = ( 445 | "$(inherited)", 446 | "@executable_path/Frameworks", 447 | ); 448 | PRODUCT_BUNDLE_IDENTIFIER = dev.nmint8m.charlesguide; 449 | PRODUCT_NAME = "$(TARGET_NAME)"; 450 | SWIFT_VERSION = 4.2; 451 | TARGETED_DEVICE_FAMILY = "1,2"; 452 | }; 453 | name = Debug; 454 | }; 455 | 22E4ADA522361C2400164CE0 /* Release */ = { 456 | isa = XCBuildConfiguration; 457 | baseConfigurationReference = 3091CF309803E9A527B71F24 /* Pods-CharlesGuide.release.xcconfig */; 458 | buildSettings = { 459 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 460 | CODE_SIGN_STYLE = Automatic; 461 | DEVELOPMENT_TEAM = D4Q882ZZC3; 462 | INFOPLIST_FILE = CharlesGuide/Info.plist; 463 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 464 | LD_RUNPATH_SEARCH_PATHS = ( 465 | "$(inherited)", 466 | "@executable_path/Frameworks", 467 | ); 468 | PRODUCT_BUNDLE_IDENTIFIER = dev.nmint8m.charlesguide; 469 | PRODUCT_NAME = "$(TARGET_NAME)"; 470 | SWIFT_VERSION = 4.2; 471 | TARGETED_DEVICE_FAMILY = "1,2"; 472 | }; 473 | name = Release; 474 | }; 475 | /* End XCBuildConfiguration section */ 476 | 477 | /* Begin XCConfigurationList section */ 478 | 22E4AD8C22361C2100164CE0 /* Build configuration list for PBXProject "CharlesGuide" */ = { 479 | isa = XCConfigurationList; 480 | buildConfigurations = ( 481 | 22E4ADA122361C2400164CE0 /* Debug */, 482 | 22E4ADA222361C2400164CE0 /* Release */, 483 | ); 484 | defaultConfigurationIsVisible = 0; 485 | defaultConfigurationName = Release; 486 | }; 487 | 22E4ADA322361C2400164CE0 /* Build configuration list for PBXNativeTarget "CharlesGuide" */ = { 488 | isa = XCConfigurationList; 489 | buildConfigurations = ( 490 | 22E4ADA422361C2400164CE0 /* Debug */, 491 | 22E4ADA522361C2400164CE0 /* Release */, 492 | ); 493 | defaultConfigurationIsVisible = 0; 494 | defaultConfigurationName = Release; 495 | }; 496 | /* End XCConfigurationList section */ 497 | }; 498 | rootObject = 22E4AD8922361C2100164CE0 /* Project object */; 499 | } 500 | -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide.xcodeproj/project.xcworkspace/xcuserdata/nmint8m.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide.xcodeproj/project.xcworkspace/xcuserdata/nmint8m.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide.xcodeproj/xcuserdata/nmint8m.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | CharlesGuide.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 4 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // CharlesGuide 4 | // 5 | // Created by Tam Nguyen M. on 3/11/19. 6 | // Copyright © 2019 Tam Nguyen M. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Config not cache data 18 | URLCache.shared = URLCache(memoryCapacity: 0, diskCapacity: 0, diskPath: nil) 19 | 20 | // Config network indicator 21 | UIApplication.shared.isNetworkActivityIndicatorVisible = true 22 | 23 | // Config window 24 | window = UIWindow(frame: UIScreen.main.bounds) 25 | window?.backgroundColor = .white 26 | window?.makeKeyAndVisible() 27 | window?.rootViewController = ChecklistVC() 28 | return true 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "ItunesArtwork@2x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Assets.xcassets/img-check-manual-device.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "img-config-device4.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Assets.xcassets/img-check-manual-device.imageset/img-config-device4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/img-check-manual-device.imageset/img-config-device4.png -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Assets.xcassets/img-check-proxy.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "img-proxy3.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Assets.xcassets/img-check-proxy.imageset/img-proxy3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/img-check-proxy.imageset/img-proxy3.png -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Assets.xcassets/img-check-ssl-cer.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "img-ssl5.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Assets.xcassets/img-check-ssl-cer.imageset/img-ssl5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/img-check-ssl-cer.imageset/img-ssl5.png -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Assets.xcassets/img-check-ssl-install.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "img-ssl1.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Assets.xcassets/img-check-ssl-install.imageset/img-ssl1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/img-check-ssl-install.imageset/img-ssl1.png -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Assets.xcassets/img-check-ssl.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "img-ssl21.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Assets.xcassets/img-check-ssl.imageset/img-ssl21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/img-check-ssl.imageset/img-ssl21.png -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Assets.xcassets/img-check-trust-ssl-simu.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "img-config-simu3.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Assets.xcassets/img-check-trust-ssl-simu.imageset/img-config-simu3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/img-check-trust-ssl-simu.imageset/img-config-simu3.png -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Assets.xcassets/img-pokemon-ball.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "img-pokemon-ball@1x.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Assets.xcassets/img-pokemon-ball.imageset/img-pokemon-ball@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/img-pokemon-ball.imageset/img-pokemon-ball@1x.png -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Assets.xcassets/img-simu8.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "img-simu8.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Assets.xcassets/img-simu8.imageset/img-simu8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/CharlesGuide/CharlesGuide/Assets.xcassets/img-simu8.imageset/img-simu8.png -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/CheckList/Cell/ChecklistCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChecklistCell.swift 3 | // CharlesGuide 4 | // 5 | // Created by Tam Nguyen M. on 4/7/19. 6 | // Copyright © 2019 Tam Nguyen M. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol ChecklistCellDelegate: class { 12 | func cell(_ cell: ChecklistCell, perform action: ChecklistCell.Action) 13 | } 14 | 15 | final class ChecklistCell: UICollectionViewCell { 16 | 17 | weak var delegate: ChecklistCellDelegate? 18 | var viewModel = ChecklistCellViewModel() { 19 | didSet { 20 | configCell() 21 | } 22 | } 23 | 24 | @IBOutlet private weak var imageView: UIImageView! 25 | @IBOutlet private weak var titleLabel: UILabel! 26 | @IBOutlet private weak var subtitleLabel: UILabel! 27 | @IBOutlet weak var startButton: UIButton! 28 | 29 | override func awakeFromNib() { 30 | super.awakeFromNib() 31 | } 32 | 33 | func configCell() { 34 | imageView.image = viewModel.image 35 | titleLabel.text = viewModel.title 36 | subtitleLabel.text = viewModel.subtitle 37 | startButton.setTitle(viewModel.buttonString, for: .normal) 38 | } 39 | 40 | @IBAction func startButtonTouchUpInside(_ sender: Any) { 41 | delegate?.cell(self, perform: .start) 42 | } 43 | 44 | enum Action { 45 | case start 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/CheckList/Cell/ChecklistCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 38 | 44 | 45 | 46 | 47 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/CheckList/Cell/ChecklistCellViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChecklistCellViewModel.swift 3 | // CharlesGuide 4 | // 5 | // Created by Tam Nguyen M. on 4/9/19. 6 | // Copyright © 2019 Tam Nguyen M. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class ChecklistCellViewModel { 12 | 13 | private(set) var image: UIImage? 14 | private(set) var title: String = "" 15 | private(set) var subtitle: String = "" 16 | private var isLastCell: Bool = false 17 | 18 | var buttonString: String { 19 | return isLastCell ? "Bắt đầu" : "Bỏ qua" 20 | } 21 | 22 | init(image: UIImage? = nil, 23 | title: String = "", 24 | subtitle: String = "", 25 | isLastCell: Bool = false) { 26 | self.image = image 27 | self.title = title 28 | self.subtitle = subtitle 29 | self.isLastCell = isLastCell 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/CheckList/CheckListViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CheckListViewModel.swift 3 | // CharlesGuide 4 | // 5 | // Created by Tam Nguyen M. on 4/9/19. 6 | // Copyright © 2019 Tam Nguyen M. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class CheckListViewModel { 12 | func numberOfCell() -> Int { 13 | return 6 14 | } 15 | 16 | func viewModelForCell(indexPath: IndexPath) -> ChecklistCellViewModel { 17 | guard let checkList = CheckList(rawValue: indexPath.row) else { return ChecklistCellViewModel() } 18 | return ChecklistCellViewModel(image: checkList.image, 19 | title: "Bước \(indexPath.row + 1): \(checkList.title)", 20 | subtitle: checkList.subtilte, 21 | isLastCell: indexPath.row == 5) 22 | } 23 | } 24 | 25 | extension CheckListViewModel { 26 | enum CheckList: Int { 27 | case configProxy = 0 28 | case configProxyForDevice 29 | case configSSLProxyingCertificates 30 | case addCharlesCACertificate 31 | case turnOnSSLProxy 32 | case trustSSLCertificateOnDeviceSimu 33 | 34 | var title: String { 35 | switch self { 36 | case .configProxy: 37 | return "Config Proxy, bật macOS Proxy cho Charles" 38 | case .configProxyForDevice: 39 | return "Config proxy cho device" 40 | case .configSSLProxyingCertificates: 41 | return "Config Configuring SSL Proxying Certificates" 42 | case .addCharlesCACertificate: 43 | return "Thêm Charles CA Certificate cho máy mac" 44 | case .turnOnSSLProxy: 45 | return "Bật SSL Proxy cho máy mac và cài đặt SSL cho tất cả host name" 46 | case .trustSSLCertificateOnDeviceSimu: 47 | return "Bật trust SSL Certificate cho device/simulator" 48 | } 49 | } 50 | 51 | var subtilte: String { 52 | switch self { 53 | case .configProxy: 54 | return "Config: View > Proxy Settings...\nTurn on: View > macOS Proxy" 55 | case .configProxyForDevice: 56 | return "Settings > Wifi > Choosen wifi > Config Proxy > Manual" 57 | case .configSSLProxyingCertificates: 58 | return "Help > SSL Proxying > Install Charles Root Certificates" 59 | case .addCharlesCACertificate: 60 | return "Certificates > Charles Proxy CA > Always Trust" 61 | case .turnOnSSLProxy: 62 | return "Proxy > SSL Proxying Settings..." 63 | case .trustSSLCertificateOnDeviceSimu: 64 | return "Device: Settings > General > About > Certificate Trust Settings\nSimulator: Help > SSL Proxying > Install Charles Root Certificate in iOS Simulators" 65 | } 66 | } 67 | 68 | var image: UIImage? { 69 | let name: String 70 | switch self { 71 | case .configProxy: 72 | name = "img-check-proxy" 73 | case .configProxyForDevice: 74 | name = "img-check-manual-device" 75 | case .configSSLProxyingCertificates: 76 | name = "img-check-ssl-install" 77 | case .addCharlesCACertificate: 78 | name = "img-check-ssl-cer" 79 | case .turnOnSSLProxy: 80 | name = "img-check-ssl" 81 | case .trustSSLCertificateOnDeviceSimu: 82 | name = "img-check-trust-ssl-simu" 83 | } 84 | guard let image = UIImage(named: name) else { return nil } 85 | return image 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/CheckList/ChecklistVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChecklistVC.swift 3 | // CharlesGuide 4 | // 5 | // Created by Tam Nguyen M. on 4/7/19. 6 | // Copyright © 2019 Tam Nguyen M. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class ChecklistVC: UIViewController { 12 | var viewModel = CheckListViewModel() 13 | 14 | static private let cellID = "ChecklistCell" 15 | 16 | @IBOutlet private weak var collectionView: UICollectionView! 17 | 18 | override var preferredStatusBarStyle: UIStatusBarStyle { 19 | return .lightContent 20 | } 21 | 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | configCollectionView() 25 | } 26 | 27 | private func configCollectionView() { 28 | let nib = UINib(nibName: ChecklistVC.cellID, bundle: nil) 29 | collectionView.register(nib, 30 | forCellWithReuseIdentifier: ChecklistVC.cellID) 31 | collectionView.dataSource = self 32 | collectionView.delegate = self 33 | } 34 | } 35 | 36 | extension ChecklistVC: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { 37 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 38 | return 6 39 | } 40 | 41 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 42 | guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ChecklistVC.cellID, 43 | for: indexPath) as? ChecklistCell 44 | else { return UICollectionViewCell() } 45 | cell.viewModel = viewModel.viewModelForCell(indexPath: indexPath) 46 | cell.delegate = self 47 | return cell 48 | } 49 | 50 | func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 51 | guard let window = UIApplication.shared.keyWindow else { 52 | return UIScreen.main.bounds.size 53 | } 54 | let screenSize = UIScreen.main.bounds.size 55 | return CGSize(width: screenSize.width, 56 | height: screenSize.height - window.safeAreaInsets.top - window.safeAreaInsets.bottom - 45) 57 | } 58 | } 59 | 60 | extension ChecklistVC: ChecklistCellDelegate { 61 | func cell(_ cell: ChecklistCell, perform action: ChecklistCell.Action) { 62 | let vc = PokemonListVC() 63 | present(vc, animated: true, completion: nil) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/CheckList/ChecklistVC.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 30 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Pokemon 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UIStatusBarStyle 32 | UIStatusBarStyleLightContent 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/PokemonList/Cell/PokemonCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PokemonCell.swift 3 | // CharlesGuide 4 | // 5 | // Created by Tam Nguyen M. on 4/9/19. 6 | // Copyright © 2019 Tam Nguyen M. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class PokemonCell: UITableViewCell { 12 | 13 | @IBOutlet private weak var containerView: UIView! 14 | @IBOutlet private weak var nameLabel: UILabel! 15 | @IBOutlet private weak var urlLabel: UILabel! 16 | 17 | var viewModel = PokemonCellViewModel(Pokemon()) { 18 | didSet { 19 | updateCell() 20 | } 21 | } 22 | 23 | override func awakeFromNib() { 24 | super.awakeFromNib() 25 | containerView.layer.cornerRadius = 5 26 | containerView.clipsToBounds = true 27 | } 28 | 29 | override func setSelected(_ selected: Bool, animated: Bool) { 30 | super.setSelected(false, animated: false) 31 | } 32 | 33 | func updateCell() { 34 | nameLabel.text = viewModel.pokemonName 35 | urlLabel.text = viewModel.pokemonURLString 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/PokemonList/Cell/PokemonCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 41 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/PokemonList/Cell/PokemonCellViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PokemonCellViewModel.swift 3 | // CharlesGuide 4 | // 5 | // Created by Tam Nguyen M. on 4/9/19. 6 | // Copyright © 2019 Tam Nguyen M. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | final class PokemonCellViewModel { 12 | let pokemon: Pokemon 13 | 14 | var pokemonName: String { return pokemon.name } 15 | 16 | var pokemonURLString: String { return pokemon.urlString } 17 | 18 | init(_ pokemon: Pokemon) { 19 | self.pokemon = pokemon 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/PokemonList/PokemonListVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PokemonListVC.swift 3 | // CharlesGuide 4 | // 5 | // Created by Tam Nguyen M. on 4/7/19. 6 | // Copyright © 2019 Tam Nguyen M. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class PokemonListVC: UIViewController { 12 | 13 | // MARK: - Properties 14 | var viewModel = PokemonListViewModel() 15 | private let cellIDString = "PokemonCell" 16 | 17 | // MARK: - IBOutlet 18 | @IBOutlet private weak var statusLabel: UILabel! 19 | @IBOutlet private weak var tableView: UITableView! 20 | @IBOutlet private weak var previousButton: UIButton! 21 | 22 | override var preferredStatusBarStyle: UIStatusBarStyle { 23 | return .lightContent 24 | } 25 | 26 | override func viewDidLoad() { 27 | super.viewDidLoad() 28 | configTableView() 29 | configStatusLabel() 30 | configButtons() 31 | loadData(pageType: .current) 32 | } 33 | 34 | private func configTableView() { 35 | let nib = UINib(nibName: cellIDString, bundle: Bundle.main) 36 | tableView.register(nib, forCellReuseIdentifier: cellIDString) 37 | tableView.dataSource = self 38 | } 39 | 40 | private func configStatusLabel() { 41 | statusLabel.text = viewModel.status 42 | } 43 | 44 | private func configButtons() { 45 | previousButton.isEnabled = viewModel.previousURLString != "" 46 | previousButton.alpha = viewModel.previousURLString != "" ? 1 : 0.5 47 | } 48 | 49 | private func loadData(pageType: PokemonListViewModel.PageType) { 50 | viewModel.getListPokemon(pageType: pageType) { [weak self] (result) in 51 | guard let this = self else { return } 52 | switch result { 53 | case .success: 54 | this.tableView.reloadData() 55 | this.configStatusLabel() 56 | this.configButtons() 57 | case .failure(let error): 58 | let alert = UIAlertController(title: "Error", 59 | message: "Error happened with code: \(error.code)", 60 | preferredStyle: .alert) 61 | let action = UIAlertAction(title: "OK", 62 | style: .default, 63 | handler: 64 | { _ in 65 | this.loadData(pageType: .current) 66 | }) 67 | alert.addAction(action) 68 | this.present(alert, 69 | animated: true, 70 | completion: nil) 71 | } 72 | } 73 | } 74 | 75 | @IBAction func closeButtonTouchUpInside(_ sender: Any) { 76 | dismiss(animated: true, completion: nil) 77 | } 78 | 79 | @IBAction func pageButtonTouchUpInside(_ sender: UIButton) { 80 | let pageType: PokemonListViewModel.PageType = sender.tag == 0 ? .previous : .next 81 | loadData(pageType: pageType) 82 | } 83 | } 84 | 85 | extension PokemonListVC: UITableViewDataSource { 86 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 87 | return viewModel.numberOfPokemon() 88 | } 89 | 90 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 91 | guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIDString, for: indexPath) as? PokemonCell else { return UITableViewCell() } 92 | cell.viewModel = viewModel.viewModelForCell(indexPath: indexPath) 93 | return cell 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/PokemonList/PokemonListVC.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 41 | 47 | 48 | 49 | 50 | 51 | 57 | 58 | 59 | 60 | 75 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/PokemonList/PokemonListViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PokemonListViewModel.swift 3 | // CharlesGuide 4 | // 5 | // Created by Tam Nguyen M. on 4/9/19. 6 | // Copyright © 2019 Tam Nguyen M. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | final class PokemonListViewModel { 12 | 13 | var listResult = ListResultPokemon() 14 | var currentURLString = API.Path.Pokemon.pokemon + "?offset=0&limit=20" 15 | 16 | var status: String { 17 | if listResult.count == 0 { 18 | return "Không có pokemon." 19 | } 20 | return listResult.count > 1 ? "Hiện có \(listResult.count) pokemon." : "Hiện chỉ có 1 pokemon." 21 | } 22 | 23 | var previousURLString: String { return listResult.previous } 24 | 25 | var nextURLString: String { return listResult.next } 26 | 27 | var pokemons: [Pokemon] { return listResult.results } 28 | 29 | func getListPokemon(pageType: PageType, completion: CompletionHandler?) { 30 | switch pageType { 31 | case .previous: getListPokemon(urlString: previousURLString, completion: completion) 32 | case .current: getListPokemon(urlString: currentURLString, completion: completion) 33 | case .next: getListPokemon(urlString: nextURLString, completion: completion) 34 | } 35 | } 36 | 37 | func getListPokemon(urlString: String, completion: CompletionHandler?) { 38 | PokemonService.getListPokemon(urlString: urlString) { [weak self] (result) in 39 | guard let this = self else { return } 40 | switch result { 41 | case .success(let listResult): 42 | this.listResult = listResult 43 | this.currentURLString = urlString 44 | completion?(.success) 45 | case .failure(let error): 46 | completion?(.failure(error)) 47 | } 48 | } 49 | } 50 | 51 | func numberOfPokemon() -> Int { 52 | return pokemons.count 53 | } 54 | 55 | func viewModelForCell(indexPath: IndexPath) -> PokemonCellViewModel { 56 | guard indexPath.row < pokemons.count else { return PokemonCellViewModel(Pokemon()) } 57 | return PokemonCellViewModel(pokemons[indexPath.row]) 58 | } 59 | } 60 | 61 | extension PokemonListViewModel { 62 | enum PageType { 63 | case previous 64 | case current 65 | case next 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Service/API.swift: -------------------------------------------------------------------------------- 1 | // 2 | // API.swift 3 | // CharlesGuide 4 | // 5 | // Created by Tam Nguyen M. on 4/7/19. 6 | // Copyright © 2019 Tam Nguyen M. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Alamofire 11 | import ObjectMapper 12 | 13 | struct API { 14 | struct Path { 15 | static let baseURL = "https://pokeapi.co/api/v2" 16 | 17 | struct Pokemon { 18 | static var pokemon: String { return API.Path.baseURL / "pokemon" } 19 | } 20 | } 21 | 22 | enum Errors: Error { 23 | case badRequest 24 | case notFound 25 | case authentication 26 | case noResponse 27 | case jsonFormat 28 | case networkNotConnect 29 | 30 | // TODO: - Define more errors https://en.wikipedia.org/wiki/List_of_HTTP_status_codes 31 | 32 | var code: Int { 33 | switch self { 34 | case .badRequest: return 400 35 | case .notFound: return 404 36 | case .authentication: return 500 37 | case .noResponse: return 1000 38 | case .jsonFormat: return 1001 39 | case .networkNotConnect: return 1002 40 | } 41 | } 42 | 43 | var description: String { 44 | switch self { 45 | case .badRequest: return "Bad request" 46 | case .notFound: return "Not found" 47 | case .authentication: return "This authentication is not valid" 48 | case .noResponse: return "No response" 49 | case .jsonFormat: return "JSON format is wrong" 50 | case .networkNotConnect: return "Cannot connect network" 51 | } 52 | } 53 | } 54 | } 55 | 56 | infix operator /: AdditionPrecedence 57 | 58 | extension String { 59 | static func / (left: String, right: String) -> String { 60 | return left + "/" + right 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Service/APIManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // APIManager.swift 3 | // CharlesGuide 4 | // 5 | // Created by Tam Nguyen M. on 4/7/19. 6 | // Copyright © 2019 Tam Nguyen M. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Alamofire 11 | 12 | enum CompletionResult { 13 | case success 14 | case failure(Error) 15 | } 16 | typealias CompletionHandler = (CompletionResult) -> Void 17 | typealias Completion = (Result) -> Void 18 | typealias JSObject = [String: Any] 19 | 20 | final class APIManager { 21 | 22 | static let shared = APIManager() 23 | 24 | private init() {} 25 | 26 | // Chứa header mặc định cần phải có trong mỗi request 27 | static var defaultHeader: [String: String] = { 28 | var header: [String: String] = [:] 29 | /* Header mặc định như là 30 | - Access token 31 | - App version 32 | - OS device 33 | - ... 34 | */ 35 | return header 36 | }() 37 | 38 | static let networkManager: NetworkReachabilityManager = { 39 | guard let networkManager = NetworkReachabilityManager() else { 40 | fatalError("Network manager cannot be generated") 41 | } 42 | return networkManager 43 | }() 44 | 45 | @discardableResult 46 | func request(path: String, 47 | method: HTTPMethod, 48 | parameters: Parameters? = nil, 49 | encoding: ParameterEncoding = URLEncoding.default, 50 | headers: HTTPHeaders? = nil, 51 | completion: Completion? = nil) -> Request? { 52 | 53 | // Kiểm tra path và network -> URL có tồn tại hay không, nếu không thì return nil 54 | guard APIManager.networkManager.isReachable, 55 | let url = URL(string: path) else { 56 | return nil 57 | } 58 | 59 | // Thêm header riêng vào defaultHeader 60 | var allHeaders: HTTPHeaders = APIManager.defaultHeader 61 | if let headers = headers { 62 | for pair in headers { 63 | allHeaders[pair.key] = pair.value 64 | } 65 | } 66 | 67 | // Tạo DataRequest 68 | let request = Alamofire.request(url, 69 | method: method, 70 | parameters: parameters, 71 | encoding: encoding, 72 | headers: allHeaders 73 | ).responseJSON { response in 74 | completion?(response.result) 75 | } 76 | return request 77 | } 78 | } 79 | 80 | // MARK: - Xử lý response của data request 81 | extension DataRequest { 82 | 83 | // Chuyển việc serializer của alamofire -> serializer của mình 84 | func responseJSON(completion: @escaping (DataResponse) -> Void) -> Self { 85 | return response(responseSerializer: responseSerializer(), 86 | completionHandler: completion) 87 | } 88 | 89 | /** Serialize của mình tự define có nhiều nhiệm vụ: 90 | - Ví dụ như xem thử cấu trúc JSON trả về đúng hay không 91 | - Đối với một số status code error nhất định thì mình sẽ xử lý khác hẳn với những status khác. 92 | - Giả dụ như yêu cầu của KH đối với status 500 -> luôn luôn là lỗi authen -> mình phải show pop up, log out tài khoản đang sử dụng và trở lại màn hình splash chả hạn. --> TH này mình có thể bắn Notification đến AppDelegate để show pop up và change root view controller... 93 | */ 94 | func responseSerializer() -> DataResponseSerializer { 95 | return DataResponseSerializer(serializeResponse: { _, response , data, error in 96 | return self.responseJSONSerializer(response: response, 97 | data: data, 98 | error: error) 99 | }) 100 | } 101 | } 102 | 103 | // MARK: - Xử lý JSON trả về 104 | extension Request { 105 | /* Vì sao không phải DataRequest mà là Request? 106 | - Vì mình muốn sử dụng chung 1 json serialize cho tất cả các request: data, download,... 107 | - class DataRequest thừa kế Request*/ 108 | 109 | func responseJSONSerializer(response: HTTPURLResponse?, 110 | data: Data?, 111 | error: Error?) -> Result { 112 | 113 | guard APIManager.networkManager.isReachable else { 114 | return .failure(API.Errors.networkNotConnect) 115 | } 116 | 117 | // Khi response không trả về và không có lỗi error --> failure với error no response 118 | if let error = error { 119 | return .failure(error) 120 | } 121 | 122 | guard let response = response else { 123 | return .failure(API.Errors.noResponse) 124 | } 125 | 126 | let statusCode = response.statusCode 127 | 128 | guard statusCode == 200 else { 129 | if statusCode == API.Errors.badRequest.code { 130 | return .failure(API.Errors.badRequest) 131 | } else if statusCode == API.Errors.authentication.code { 132 | return .failure(API.Errors.authentication) 133 | } 134 | return .failure(API.Errors.notFound) 135 | } 136 | 137 | if let data = data, let json = data.toJSON() { 138 | // Success with JSON response 139 | return .success(json) 140 | } else if let data = data, let string = data.toString(), string.isEmpty { 141 | // Success but no JSON response 142 | return .success([:]) 143 | } 144 | // Failure vì JSON format 145 | return .failure(API.Errors.jsonFormat) 146 | } 147 | } 148 | 149 | // MARK: - Xử lý error 150 | extension Error { 151 | public var code: Int { 152 | if let this = self as? API.Errors { 153 | return this.code 154 | } 155 | let this = self as NSError 156 | return this.code 157 | } 158 | } 159 | 160 | // MARK: - Xử lý Data 161 | extension Data { 162 | public func toJSON() -> Any? { 163 | do { 164 | return try JSONSerialization.jsonObject(with: self, options: JSONSerialization.ReadingOptions.allowFragments) 165 | } catch { 166 | return nil 167 | } 168 | } 169 | 170 | public func toString(_ encoding: String.Encoding = String.Encoding.utf8) -> String? { 171 | return String(data: self, encoding: encoding) 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Service/Models.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Models.swift 3 | // CharlesGuide 4 | // 5 | // Created by Tam Nguyen M. on 4/7/19. 6 | // Copyright © 2019 Tam Nguyen M. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ObjectMapper 11 | 12 | final class Pokemon: Mappable { 13 | var name: String = "" 14 | var urlString: String = "" 15 | 16 | init() {} 17 | 18 | init?(map: Map) {} 19 | 20 | func mapping(map: Map) { 21 | name <- map["name"] 22 | urlString <- map["url"] 23 | } 24 | } 25 | 26 | final class ListResultPokemon: Mappable { 27 | var count: Int = 0 28 | var previous: String = "" 29 | var next: String = "" 30 | var results: [Pokemon] = [] 31 | 32 | init() {} 33 | 34 | init?(map: Map) {} 35 | 36 | func mapping(map: Map) { 37 | count <- map["count"] 38 | previous <- map["previous"] 39 | next <- map["next"] 40 | results <- map["results"] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /CharlesGuide/CharlesGuide/Service/PokemonService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PokemonService.swift 3 | // CharlesGuide 4 | // 5 | // Created by Tam Nguyen M. on 4/9/19. 6 | // Copyright © 2019 Tam Nguyen M. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Alamofire 11 | import ObjectMapper 12 | 13 | final class PokemonService { 14 | @discardableResult 15 | static func getListPokemon(urlString: String, completion: Completion?) -> Request? { 16 | return APIManager.shared.request(path: urlString, 17 | method: .get, 18 | completion: 19 | { result in 20 | DispatchQueue.main.async { 21 | switch result { 22 | case .success(let value): 23 | guard let json = value as? JSObject, 24 | let listResult = Mapper().map(JSON: json) else { 25 | completion?(.failure(API.Errors.jsonFormat)) 26 | return 27 | } 28 | completion?(.success(listResult)) 29 | case .failure(let error): 30 | completion?(.failure(error)) 31 | } 32 | } 33 | }) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /CharlesGuide/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem 'cocoapods', '1.5.3' -------------------------------------------------------------------------------- /CharlesGuide/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.0) 5 | activesupport (4.2.11.1) 6 | i18n (~> 0.7) 7 | minitest (~> 5.1) 8 | thread_safe (~> 0.3, >= 0.3.4) 9 | tzinfo (~> 1.1) 10 | atomos (0.1.3) 11 | claide (1.0.2) 12 | cocoapods (1.5.3) 13 | activesupport (>= 4.0.2, < 5) 14 | claide (>= 1.0.2, < 2.0) 15 | cocoapods-core (= 1.5.3) 16 | cocoapods-deintegrate (>= 1.0.2, < 2.0) 17 | cocoapods-downloader (>= 1.2.0, < 2.0) 18 | cocoapods-plugins (>= 1.0.0, < 2.0) 19 | cocoapods-search (>= 1.0.0, < 2.0) 20 | cocoapods-stats (>= 1.0.0, < 2.0) 21 | cocoapods-trunk (>= 1.3.0, < 2.0) 22 | cocoapods-try (>= 1.1.0, < 2.0) 23 | colored2 (~> 3.1) 24 | escape (~> 0.0.4) 25 | fourflusher (~> 2.0.1) 26 | gh_inspector (~> 1.0) 27 | molinillo (~> 0.6.5) 28 | nap (~> 1.0) 29 | ruby-macho (~> 1.1) 30 | xcodeproj (>= 1.5.7, < 2.0) 31 | cocoapods-core (1.5.3) 32 | activesupport (>= 4.0.2, < 6) 33 | fuzzy_match (~> 2.0.4) 34 | nap (~> 1.0) 35 | cocoapods-deintegrate (1.0.4) 36 | cocoapods-downloader (1.2.2) 37 | cocoapods-plugins (1.0.0) 38 | nap 39 | cocoapods-search (1.0.0) 40 | cocoapods-stats (1.1.0) 41 | cocoapods-trunk (1.3.1) 42 | nap (>= 0.8, < 2.0) 43 | netrc (~> 0.11) 44 | cocoapods-try (1.1.0) 45 | colored2 (3.1.2) 46 | concurrent-ruby (1.1.5) 47 | escape (0.0.4) 48 | fourflusher (2.0.1) 49 | fuzzy_match (2.0.4) 50 | gh_inspector (1.1.3) 51 | i18n (0.9.5) 52 | concurrent-ruby (~> 1.0) 53 | minitest (5.11.3) 54 | molinillo (0.6.6) 55 | nanaimo (0.2.6) 56 | nap (1.1.0) 57 | netrc (0.11.0) 58 | ruby-macho (1.4.0) 59 | thread_safe (0.3.6) 60 | tzinfo (1.2.5) 61 | thread_safe (~> 0.1) 62 | xcodeproj (1.8.2) 63 | CFPropertyList (>= 2.3.3, < 4.0) 64 | atomos (~> 0.1.3) 65 | claide (>= 1.0.2, < 2.0) 66 | colored2 (~> 3.1) 67 | nanaimo (~> 0.2.6) 68 | 69 | PLATFORMS 70 | ruby 71 | 72 | DEPENDENCIES 73 | cocoapods (= 1.5.3) 74 | 75 | BUNDLED WITH 76 | 2.0.1 77 | -------------------------------------------------------------------------------- /CharlesGuide/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '9.0' 2 | 3 | target 'CharlesGuide' do 4 | use_frameworks! 5 | 6 | pod 'ObjectMapper', '3.3.0' 7 | pod 'SDWebImage', '4.4.1' 8 | pod 'Alamofire', '4.7.3' 9 | 10 | end 11 | -------------------------------------------------------------------------------- /CharlesGuide/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (4.7.3) 3 | - ObjectMapper (3.3.0) 4 | - SDWebImage (4.4.1): 5 | - SDWebImage/Core (= 4.4.1) 6 | - SDWebImage/Core (4.4.1) 7 | 8 | DEPENDENCIES: 9 | - Alamofire (= 4.7.3) 10 | - ObjectMapper (= 3.3.0) 11 | - SDWebImage (= 4.4.1) 12 | 13 | SPEC REPOS: 14 | https://github.com/cocoapods/specs.git: 15 | - Alamofire 16 | - ObjectMapper 17 | - SDWebImage 18 | 19 | SPEC CHECKSUMS: 20 | Alamofire: c7287b6e5d7da964a70935e5db17046b7fde6568 21 | ObjectMapper: b612bf8c8e99c4dc0bb6013a51f7c27966ed5da9 22 | SDWebImage: 47e9b5b925cbce75946c23f0c42dd19464189af4 23 | 24 | PODFILE CHECKSUM: 820cd939f86f597a8240b6051c8943af41fe604c 25 | 26 | COCOAPODS: 1.5.3 27 | -------------------------------------------------------------------------------- /Images/img-access1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-access1.png -------------------------------------------------------------------------------- /Images/img-access2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-access2.png -------------------------------------------------------------------------------- /Images/img-breakpoint1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-breakpoint1.png -------------------------------------------------------------------------------- /Images/img-breakpoint2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-breakpoint2.png -------------------------------------------------------------------------------- /Images/img-breakpoint3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-breakpoint3.png -------------------------------------------------------------------------------- /Images/img-config-device1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-config-device1.png -------------------------------------------------------------------------------- /Images/img-config-device2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-config-device2.png -------------------------------------------------------------------------------- /Images/img-config-device3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-config-device3.png -------------------------------------------------------------------------------- /Images/img-config-device4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-config-device4.png -------------------------------------------------------------------------------- /Images/img-config-simu0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-config-simu0.png -------------------------------------------------------------------------------- /Images/img-config-simu1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-config-simu1.png -------------------------------------------------------------------------------- /Images/img-config-simu2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-config-simu2.png -------------------------------------------------------------------------------- /Images/img-config-simu3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-config-simu3.png -------------------------------------------------------------------------------- /Images/img-ex1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-ex1.png -------------------------------------------------------------------------------- /Images/img-ex2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-ex2.png -------------------------------------------------------------------------------- /Images/img-ex3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-ex3.png -------------------------------------------------------------------------------- /Images/img-ex4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-ex4.png -------------------------------------------------------------------------------- /Images/img-ex5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-ex5.png -------------------------------------------------------------------------------- /Images/img-ex6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-ex6.png -------------------------------------------------------------------------------- /Images/img-ex7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-ex7.png -------------------------------------------------------------------------------- /Images/img-ex8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-ex8.png -------------------------------------------------------------------------------- /Images/img-handle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-handle.png -------------------------------------------------------------------------------- /Images/img-host1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-host1.png -------------------------------------------------------------------------------- /Images/img-host2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-host2.png -------------------------------------------------------------------------------- /Images/img-host3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-host3.png -------------------------------------------------------------------------------- /Images/img-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-icon.png -------------------------------------------------------------------------------- /Images/img-install1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-install1.png -------------------------------------------------------------------------------- /Images/img-install2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-install2.png -------------------------------------------------------------------------------- /Images/img-install3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-install3.png -------------------------------------------------------------------------------- /Images/img-install4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-install4.png -------------------------------------------------------------------------------- /Images/img-interface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-interface.png -------------------------------------------------------------------------------- /Images/img-local-ip1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-local-ip1.png -------------------------------------------------------------------------------- /Images/img-local-ip2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-local-ip2.png -------------------------------------------------------------------------------- /Images/img-pikachu@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-pikachu@1x.png -------------------------------------------------------------------------------- /Images/img-pokemon-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-pokemon-1.jpg -------------------------------------------------------------------------------- /Images/img-pokemon-ball@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-pokemon-ball@1x.png -------------------------------------------------------------------------------- /Images/img-proxy1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-proxy1.png -------------------------------------------------------------------------------- /Images/img-proxy2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-proxy2.png -------------------------------------------------------------------------------- /Images/img-proxy3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-proxy3.png -------------------------------------------------------------------------------- /Images/img-record1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-record1.png -------------------------------------------------------------------------------- /Images/img-record2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-record2.png -------------------------------------------------------------------------------- /Images/img-record3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-record3.png -------------------------------------------------------------------------------- /Images/img-record4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-record4.png -------------------------------------------------------------------------------- /Images/img-record5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-record5.png -------------------------------------------------------------------------------- /Images/img-register1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-register1.png -------------------------------------------------------------------------------- /Images/img-register2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-register2.png -------------------------------------------------------------------------------- /Images/img-register3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-register3.png -------------------------------------------------------------------------------- /Images/img-simu1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-simu1.png -------------------------------------------------------------------------------- /Images/img-simu2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-simu2.png -------------------------------------------------------------------------------- /Images/img-simu3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-simu3.png -------------------------------------------------------------------------------- /Images/img-simu4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-simu4.png -------------------------------------------------------------------------------- /Images/img-simu5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-simu5.png -------------------------------------------------------------------------------- /Images/img-simu6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-simu6.png -------------------------------------------------------------------------------- /Images/img-simu7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-simu7.png -------------------------------------------------------------------------------- /Images/img-simu8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-simu8.png -------------------------------------------------------------------------------- /Images/img-ssl0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-ssl0.png -------------------------------------------------------------------------------- /Images/img-ssl1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-ssl1.png -------------------------------------------------------------------------------- /Images/img-ssl2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-ssl2.png -------------------------------------------------------------------------------- /Images/img-ssl21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-ssl21.png -------------------------------------------------------------------------------- /Images/img-ssl22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-ssl22.png -------------------------------------------------------------------------------- /Images/img-ssl23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-ssl23.png -------------------------------------------------------------------------------- /Images/img-ssl3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-ssl3.png -------------------------------------------------------------------------------- /Images/img-ssl4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-ssl4.png -------------------------------------------------------------------------------- /Images/img-ssl5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-ssl5.png -------------------------------------------------------------------------------- /Images/img-throttlle1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-throttlle1.png -------------------------------------------------------------------------------- /Images/img-throttlle2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nmint8m/guide-charles-proxy/ac370642c8d8f595602392d404a938dc8134e6fa/Images/img-throttlle2.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Tam Nguyen M. 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Charles Guide 2 | 3 | _Written by **Nguyen Minh Tam**_ 4 | 5 | Tài liệu này đề cập đến cách **cài đặt**, những **chức năng cơ bản** và thường được sử dụng nhất của Charles. Hi vọng mọi người sẽ tìm được những kiến thức hữu ích khi đọc tài liệu này. 👍 6 | 7 |
8 | 9 |
10 | 11 | ## Tài liệu hướng dẫn: 12 | 13 | - [x] [Hướng dẫn cài đặt Charles](./CharlesGuide-Install.md) 🔧 14 | 15 | - [x] [Hướng dẫn sử dụng Charles](./CharlesGuide-Usage.md) 🗒 16 | 17 | - [x] [Demo debugging an iOS app with dummy data from Charles](./CharlesGuide-Example.md) 🐞 18 | 19 | Ủng hộ mình một ⭐️ để có thêm động lực viết bài nhé! Cảm ơn mọi người. 🎉 20 | 21 | || 22 | |---| 23 | || 24 | | | --------------------------------------------------------------------------------