├── .github
└── workflows
│ └── main.yml
├── .gitignore
├── .swiftlint.yml
├── CHANGELOG.md
├── Dangerfile
├── DeviceKit.podspec
├── DeviceKit.svg
├── DeviceKit.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── WorkspaceSettings.xcsettings
└── xcshareddata
│ └── xcschemes
│ └── DeviceKit.xcscheme
├── DeviceKit.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ ├── IDEWorkspaceChecks.plist
│ └── WorkspaceSettings.xcsettings
├── Example
└── DeviceKitPlayground.playground
│ ├── Contents.swift
│ ├── contents.xcplayground
│ └── playground.xcworkspace
│ └── contents.xcworkspacedata
├── Gemfile
├── Gemfile.lock
├── LICENSE
├── Package.swift
├── README.md
├── Scripts
└── push.sh
├── Source
├── Device.generated.swift
├── Device.swift.gyb
├── Info.plist
└── PrivacyInfo.xcprivacy
├── Tests
├── Info.plist
└── Tests.swift
└── Utils
├── gyb
└── gyb.py
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | workflow_dispatch:
10 |
11 | jobs:
12 | danger:
13 | runs-on: macos-latest
14 | if: github.event_name == 'pull_request'
15 | steps:
16 | - uses: actions/checkout@v2
17 |
18 | - uses: ruby/setup-ruby@v1
19 | with:
20 | bundler-cache: true
21 | ruby-version: 2.7.8
22 |
23 | - uses: MeilCli/danger-action@v5
24 | with:
25 | plugins_file: Gemfile
26 | install_path: vendor/bundle
27 | danger_file: Dangerfile
28 | danger_id: danger-pr
29 | env:
30 | DANGER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
31 |
32 | test:
33 | runs-on: macos-14
34 |
35 | strategy:
36 | matrix:
37 | platform:
38 | - platform=iOS Simulator,name=iPhone 16 Pro Max
39 | - platform=iOS Simulator,name=iPhone 16 Pro
40 | - platform=iOS Simulator,name=iPhone 16
41 | - platform=iOS Simulator,name=iPhone 16 Plus
42 | - platform=iOS Simulator,name=iPhone SE (3rd generation)
43 | - platform=tvOS Simulator,name=Apple TV
44 | - platform=tvOS Simulator,name=Apple TV 4K (3rd generation)
45 | - platform=watchOS Simulator,name=Apple Watch Series 10 (46mm)
46 |
47 | steps:
48 | - uses: actions/checkout@v2
49 |
50 | - uses: maxim-lobanov/setup-xcode@v1
51 | with:
52 | xcode-version: latest-stable
53 |
54 | - uses: ruby/setup-ruby@v1
55 | with:
56 | bundler-cache: true
57 | ruby-version: 2.7.8
58 |
59 | - name: xcodebuild
60 | run: set -o pipefail && xcodebuild -scheme DeviceKit -destination "${{ matrix.platform }}" -configuration Debug ONLY_ACTIVE_ARCH=YES -enableCodeCoverage YES test
61 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.gitignore.io/api/xcode
2 |
3 | ### Xcode ###
4 | # Xcode
5 | #
6 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
7 |
8 | ## Build generated
9 | build/
10 | DerivedData
11 | *.profraw
12 | *.profdata
13 |
14 | ## Various settings
15 | *.pbxuser
16 | !default.pbxuser
17 | *.mode1v3
18 | !default.mode1v3
19 | *.mode2v3
20 | !default.mode2v3
21 | *.perspectivev3
22 | !default.perspectivev3
23 | xcuserdata
24 |
25 | ## Other
26 | *.xccheckout
27 | *.moved-aside
28 | *.xcuserstate
29 | *.DS_Store
30 | Utils/gyb.pyc
31 |
32 | .build
33 | /.previous-build
34 | xcuserdata
35 | .DS_Store
36 | *~
37 | \#*
38 | .\#*
39 | *.xcscmblueprint
40 | /default.profraw
41 | Utilities/Docker/*.tar.gz
42 | .swiftpm
43 | Package.resolved
44 | vendor
45 |
46 | ## Generated Files
47 | # *.generated.swift # we have to check it in because of CocoaPods ...
48 |
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | disabled_rules:
2 | - file_length
3 | - line_length
4 | - switch_case_alignment
5 | - type_body_length
6 | - identifier_name
7 |
8 | opt_in_rules:
9 | - anyobject_protocol
10 | - array_init
11 | - attributes
12 | - closure_end_indentation
13 | - closure_spacing
14 | - contains_over_first_not_nil
15 | - convenience_type
16 | - empty_count
17 | - empty_string
18 | - empty_xctest_method
19 | - explicit_init
20 | - fallthrough
21 | - first_where
22 | - force_unwrapping
23 | - function_default_parameter_at_end
24 | - inert_defer
25 | - no_extension_access_modifier
26 | - overridden_super_call
27 | - prohibited_super_call
28 | - redundant_nil_coalescing
29 | - vertical_parameter_alignment_on_call
30 | - pattern_matching_keywords
31 | - fatal_error_message
32 | - implicitly_unwrapped_optional
33 | - joined_default_parameter
34 | - let_var_whitespace
35 | - literal_expression_end_indentation
36 | - lower_acl_than_parent
37 | - modifier_order
38 | - multiline_arguments
39 | - multiline_function_chains
40 | - multiline_parameters
41 | - multiple_closures_with_trailing_closure
42 | - nesting
43 | - notification_center_detachment
44 | - object_literal
45 | - operator_usage_whitespace
46 | - override_in_extension
47 | - private_action
48 | - private_outlet
49 | - redundant_type_annotation
50 | - single_test_class
51 | - sorted_imports
52 | - sorted_first_last
53 | - trailing_closure
54 | - unavailable_function
55 | - unneeded_parentheses_in_closure_argument
56 | - yoda_condition
57 |
58 | analyzer_rules:
59 | - unused_import
60 | - unused_private_declaration
61 |
62 | reporter: "xcode"
63 |
64 | identifier_name:
65 | excluded:
66 | - tv
67 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## Version 5.6.0
4 |
5 | Releasedate: 2025-03-27
6 |
7 | ```ruby
8 | pod 'DeviceKit', '~> 5.6'
9 | ```
10 |
11 | ### New October 2024 devices
12 |
13 | This version adds support for the devices announced in October 2024: ([#429](https://github.com/devicekit/DeviceKit/pull/429))
14 |
15 | | Device | Case value |
16 | | --- | --- |
17 | | iPad Mini (A17 Pro) | `Device.iPadMiniA17Pro` |
18 |
19 | ### New February 2025 devices
20 |
21 | This version adds support for the devices announced in February 2025: ([#436](https://github.com/devicekit/DeviceKit/pull/436))
22 |
23 | | Device | Case value |
24 | | --- | --- |
25 | | iPhone 16e | `Device.iPhone16e` |
26 |
27 | ### New March 2025 devices
28 |
29 | This version adds support for the devices announced in March 2025: ([#436](https://github.com/devicekit/DeviceKit/pull/436))
30 |
31 | | Device | Case value |
32 | | --- | --- |
33 | | iPad (A16) | `Device.iPadA16` |
34 | | iPad Air 11-inch (M3) | `Device.iPadAir11M3` |
35 | | iPad Air 13-inch (M3) | `Device.iPadAir13M3` |
36 |
37 | ### Fixes
38 |
39 | - Fix iPhone 16 Plus PPI. ([#423](https://github.com/devicekit/DeviceKit/pull/423))
40 |
41 | ## Version 5.5.0
42 |
43 | Releasedate: 2024-09-22
44 |
45 | ```ruby
46 | pod 'DeviceKit', '~> 5.5'
47 | ```
48 |
49 | ### New September 2024 devices
50 |
51 | This version adds support for the devices announced at the September 2024 Apple Event: ([#417](https://github.com/devicekit/DeviceKit/pull/417))
52 |
53 | | Device | Case value |
54 | | --- | --- |
55 | | iPhone 16 | `Device.iPhone16` |
56 | | iPhone 16 Plus | `Device.iPhone16Plus` |
57 | | iPhone 16 Pro | `Device.iPhone16Pro` |
58 | | iPhone 16 Pro Max | `Device.iPhone16ProMax` |
59 | | Apple Watch Series 10 | `Device.appleWatchSeries10_42mm`, `Device.appleWatchSeries10_46mm` |
60 |
61 | ### Fixes
62 |
63 | - Fix incorrect PPI for iPhone 14 Plus and iPhone 15 Plus. ([#418](https://github.com/devicekit/DeviceKit/pull/418))
64 |
65 | ### Contributors
66 |
67 | Thanks to all the contributers of this release!
68 | - [arindamxd](https://github.com/arindamxd)
69 |
70 | ## Version 5.4.0
71 |
72 | Releasedate: 2024-05-30
73 |
74 | ```ruby
75 | pod 'DeviceKit', '~> 5.4'
76 | ```
77 |
78 | ### New features
79 |
80 | - Add `Orientation.unknown` for when the device is neither in landscape or portrait orientation. ([#410](https://github.com/devicekit/DeviceKit/pull/410))
81 |
82 | ### Contributors
83 |
84 | Thanks to all the contributers of this release!
85 | - [GeorgeElsham](https://github.com/GeorgeElsham)
86 |
87 | ## Version 5.3.1
88 |
89 | Releasedate: 2024-05-30
90 |
91 | ```ruby
92 | pod 'DeviceKit', '~> 5.3'
93 | ```
94 |
95 | ### Bugfixes
96 |
97 | - Process Privacy manifest instead of copy on SPM. ([#409](https://github.com/devicekit/DeviceKit/pull/409))
98 |
99 | ## Version 5.3.0
100 |
101 | Releasedate: 2024-05-21
102 |
103 | ```ruby
104 | pod 'DeviceKit', '~> 5.3'
105 | ```
106 |
107 | ### New May 2024 devices
108 |
109 | This version adds support for the devices announced at the May 2024 Apple Event: ([#412](https://github.com/devicekit/DeviceKit/pull/412))
110 |
111 | | Device | Case value |
112 | | --- | --- |
113 | | iPad Air (11-inch) (M2) | `Device.iPadAir11M2` |
114 | | iPad Air (13-inch) (M2) | `Device.iPadAir13M2` |
115 | | iPad Pro (11-inch) (M4) | `Device.iPadPro11M4` |
116 | | iPad Pro (13-inch) (M4) | `Device.iPadPro13M4` |
117 |
118 | ### New features
119 |
120 | - Added `ApplePencilSupport.firstGenerationUsbC` and `ApplePencilSupport.pro`. ([#412](https://github.com/devicekit/DeviceKit/pull/412))
121 |
122 | ## Version 5.2.4
123 |
124 | Releasedate: 2024-04-30
125 |
126 | ```ruby
127 | pod 'DeviceKit', '~> 5.2'
128 | ```
129 |
130 | ### Bugfixes
131 |
132 | - Enable MERGEABLE_LIBRARY flag. ([#407](https://github.com/devicekit/DeviceKit/pull/407))
133 |
134 | ## Version 5.2.3
135 |
136 | Releasedate: 2024-04-15
137 |
138 | ```ruby
139 | pod 'DeviceKit', '~> 5.2'
140 | ```
141 |
142 | ### Bugfixes
143 |
144 | - Fix PrivacyInfo for Cocoapods. ([#397](https://github.com/devicekit/DeviceKit/pull/397))
145 |
146 | ### Contributors
147 |
148 | Thanks to all the contributers of this release!
149 | - [RayJiang16](https://github.com/RayJiang16)
150 |
151 | ## Version 5.2.2
152 |
153 | Releasedate: 2024-02-06
154 |
155 | ```ruby
156 | pod 'DeviceKit', '~> 5.2'
157 | ```
158 |
159 | ### Bugfixes
160 |
161 | - Fix building SwiftUI previews on macOS properly. ([#389](https://github.com/devicekit/DeviceKit/pull/389))
162 | - Fix building for visionOS. ([#390](https://github.com/devicekit/DeviceKit/pull/390))
163 |
164 | ### Contributors
165 |
166 | Thanks to all the contributers of this release!
167 | - [honghaoz](https://github.com/honghaoz)
168 | - [chrisvasselli](https://github.com/chrisvasselli)
169 |
170 | ## Version 5.2.1
171 |
172 | Releasedate: 2024-01-17
173 |
174 | ```ruby
175 | pod 'DeviceKit', '~> 5.2'
176 | ```
177 |
178 | ### Bugfixes
179 |
180 | - Update swift-tools-version in Package.swift to 5.3 to support Resource files. ([#381](https://github.com/devicekit/DeviceKit/pull/381))
181 | - Fix PrivacyInfo.xcprivacy not being detected on SPM. ([#384](https://github.com/devicekit/DeviceKit/pull/384))
182 |
183 | ### Contributors
184 |
185 | Thanks to all the contributers of this release!
186 | - [Semty](https://github.com/Semty)
187 | - Everyone who reported the SPM issues.
188 |
189 | ## Version 5.2.0
190 |
191 | Releasedate: 2024-01-15
192 |
193 | ```ruby
194 | pod 'DeviceKit', '~> 5.2'
195 | ```
196 |
197 | ### New features
198 |
199 | - Add `.hasUSBCConnectivity` to `Device` to indicate whether the device has an USB-C port. ([#373](https://github.com/devicekit/DeviceKit/pull/373))
200 | - Add `.hasDynamicIsland` to `Device` to indicate whether the device has a dynamic island. ([#368](https://github.com/devicekit/DeviceKit/pull/368))
201 | - Add `.has5gSupport` to `Device` to indicate whether the device has 5G support. ([#340](https://github.com/devicekit/DeviceKit/pull/340))
202 | - Added Privacy Manifest to DeviceKit. ([#367](https://github.com/devicekit/DeviceKit/pull/367))
203 | - Fix compilation issues when building SwiftUI Previews for macOS. ([#376](https://github.com/devicekit/DeviceKit/pull/376))
204 |
205 | ### Contributors
206 |
207 | Thanks to all the contributers of this release!
208 | - [mhausherr](https://github.com/mhausherr)
209 | - [417-72KI](https://github.com/417-72KI)
210 | - [furiosFast](https://github.com/furiosFast)
211 | - [DenTelezhkin](https://github.com/DenTelezhkin)
212 | - [honghaoz](https://github.com/honghaoz)
213 |
214 | ## Version 5.1.0
215 |
216 | Releasedate: 2023-09-21
217 |
218 | ```ruby
219 | pod 'DeviceKit', '~> 5.1'
220 | ```
221 |
222 | ### New September 2023 devices
223 |
224 | This version adds support for the devices announced at the September 2023 Apple Event: ([#360](https://github.com/devicekit/DeviceKit/pull/360), [#361](https://github.com/devicekit/DeviceKit/pull/361))
225 |
226 | | Device | Case value |
227 | | --- | --- |
228 | | iPhone 15 | `Device.iPhone15` |
229 | | iPhone 15 Plus | `Device.iPhone15Plus` |
230 | | iPhone 15 Pro | `Device.iPhone15Pro` |
231 | | iPhone 15 Pro Max | `Device.iPhone15ProMax` |
232 | | Apple Watch Series 9 | `Device.appleWatchSeries9_41mm`, `Device.appleWatchSeries9_45mm` |
233 | | Apple Watch Ultra 2 | `Device.appleWatchUltra2` |
234 |
235 | ### Fixes
236 |
237 | - Fixes compilation errors that occur when compiling DeviceKit for visionOS. ([#356](https://github.com/devicekit/DeviceKit/pull/356))
238 |
239 | ### Important notes
240 |
241 | - Note that this version does not add full visionOS support to DeviceKit. It just allows DeviceKit to compile for visionOS.
242 | - When compiling this version of DeviceKit with Xcode 14 or lower, it will produce the following warning 3 times: `Unknown operating system for build configuration 'os'`
243 |
244 | ### Contributors
245 |
246 | Thanks to all the contributers of this release!
247 | - [wo-ist-henry](https://github.com/wo-ist-henry)
248 |
249 | ## Version 5.0.0
250 |
251 | Releasedate: 2022-11-01
252 |
253 | ```ruby
254 | pod 'DeviceKit', '~> 5.0'
255 | ```
256 |
257 | ### Breaking changes
258 |
259 | - DeviceKit v5.0.0 drops support for the platforms that Xcode 14 also no longer supports: ([#337](https://github.com/devicekit/DeviceKit/pull/337))
260 |
261 | | Platform | Previous | Now |
262 | |----------|----------|------|
263 | | iOS | 9.0 | 11.0 |
264 | | tvOS | 9.0 | 11.0 |
265 | | watchOS | 2.0 | 4.0 |
266 |
267 | ### New features
268 |
269 | - Retrieve a device's CPU info using eg. `Device.current.cpu`. ([#330](https://github.com/devicekit/DeviceKit/pull/330))
270 | - Add `.isSimulator` to tvOS and watchOS. ([#245](https://github.com/devicekit/DeviceKit/pull/245))
271 | - Add `.isCanvas` which indicates whether the app is running in a SwiftUI preview. ([#303](https://github.com/devicekit/DeviceKit/pull/303))
272 |
273 | ### Contributors
274 |
275 | Thanks to all the contributers of this release!
276 | - [parski](https://github.com/parski)
277 | - [guidev](https://github.com/guidev)
278 | - [JackYoustra](https://github.com/JackYoustra)
279 |
280 | ## Version 4.9.0
281 |
282 | Releasedate: 2022-11-01
283 |
284 | ```ruby
285 | pod 'DeviceKit', '~> 4.9'
286 | ```
287 |
288 | ### Apple TV 4K (3rd generation) support.
289 |
290 | This version adds support for the new Apple TV (3rd generation) that is released on the 4th of November 2022. ([#335](https://github.com/devicekit/DeviceKit/pull/335))
291 |
292 | | Device | Case value |
293 | | --- | --- |
294 | | Apple TV 4K (3rd generation) | `Device.appleTV4K3` |
295 |
296 | ### New features
297 |
298 | - Add ability to get current device's thermal state. ([#332](https://github.com/devicekit/DeviceKit/pull/332))
299 |
300 | ### Bugfixes
301 |
302 | - Fix Apple Watch Series 7 device identifier being incorrect. ([#329](https://github.com/devicekit/DeviceKit/pull/329))
303 |
304 | ### Contributors
305 |
306 | Thanks to all the contributers of this release!
307 | - [guidev](https://github.com/guidev)
308 | - [chedabob](https://github.com/chedabob)
309 |
310 | ## Version 4.8.0
311 |
312 | Releasedate: 2022-10-28
313 |
314 | ```ruby
315 | pod 'DeviceKit', '~> 4.8'
316 | ```
317 |
318 | ### New October 2022 devices
319 |
320 | This version adds support for the devices that were released in October 2022: ([#334](https://github.com/devicekit/DeviceKit/pull/334))
321 |
322 | | Device | Case value |
323 | | --- | --- |
324 | | iPad (10th generation) | `Device.iPad10` |
325 | | iPad Pro 11-inch (4th generation) | `Device.iPadPro11Inch4` |
326 | | iPad Pro 12.9-inch (6th generation) | `Device.iPadPro12Inch6` |
327 |
328 | ## Version 4.7.0
329 |
330 | Releasedate: 2022-09-13
331 |
332 | ```ruby
333 | pod 'DeviceKit', '~> 4.7'
334 | ```
335 |
336 | ### New September 2022 devices
337 |
338 | This version adds support for the devices announced at the September 2022 Apple Event: ([#324](https://github.com/devicekit/DeviceKit/pull/324))
339 |
340 | | Device | Case value |
341 | | --- | --- |
342 | | iPhone 14 | `Device.iPhone14` |
343 | | iPhone 14 Plus | `Device.iPhone14Plus` |
344 | | iPhone 14 Pro | `Device.iPhone14Pro` |
345 | | iPhone 14 Pro Max | `Device.iPhone14ProMax` |
346 | | Apple Watch Series 7 (Missing from DeviceKit, from last year) | `Device.appleWatchSeries7_41mm`, `Device.appleWatchSeries7_45mm` |
347 | | Apple Watch Series 8 | `Device.appleWatchSeries8_41mm`, `Device.appleWatchSeries8_45mm` |
348 | | Apple Watch SE (2nd generation) | `Device.appleWatchSE2_40mm`, `Device.appleWatchSE2_44mm` |
349 | | Apple Watch Ultra | `Device.appleWatchUltra` |
350 |
351 | ## Version 4.6.1
352 |
353 | Releasedate: 2022-07-15
354 |
355 | ```ruby
356 | pod 'DeviceKit', '~> 4.6'
357 | ```
358 |
359 | ### Fixes
360 |
361 | - Fixes Carthage support. ([#288](https://github.com/devicekit/DeviceKit/pull/288))
362 |
363 | ## Version 4.6.0
364 |
365 | Releasedate: 2022-03-18
366 |
367 | ```ruby
368 | pod 'DeviceKit', '~> 4.6'
369 | ```
370 |
371 | ### New March 2022 devices
372 |
373 | This version adds support for the devices announced at the March 2022 Apple Event: ([308](https://github.com/devicekit/DeviceKit/pull/308))
374 |
375 | | Device | Case value |
376 | | --- | --- |
377 | | iPhone SE (3rd generation) | `Device.iPhoneSE3` |
378 | | iPad Air (5th generation) | `Device.iPadAir5` |
379 |
380 | ## Version 4.5.2
381 |
382 | Releasedate: 2021-10-24
383 |
384 | ```ruby
385 | pod 'DeviceKit', '~> 4.5'
386 | ```
387 |
388 | ### Fixes
389 |
390 | - Fix iPad mini (6th generation) screen size and aspect ratio again. ([#300](https://github.com/devicekit/DeviceKit/pull/300))
391 | - Add missing device support URLs and images. ([#300](https://github.com/devicekit/DeviceKit/pull/300))
392 |
393 | ## Version 4.5.1
394 |
395 | Releasedate: 2021-10-15
396 |
397 | ```ruby
398 | pod 'DeviceKit', '~> 4.5'
399 | ```
400 |
401 | ### Fixes
402 |
403 | - Fix iPad mini (6th generation) screen size and aspect ratio. ([#294](https://github.com/devicekit/DeviceKit/pull/294))
404 |
405 | ## Version 4.5.0
406 |
407 | Releasedate: 2021-09-16
408 |
409 | ```ruby
410 | pod 'DeviceKit', '~> 4.5'
411 | ```
412 |
413 | ### New September 2021 devices
414 |
415 | This version adds support for the devices announced at the September 2021 Apple Event: ([#286](https://github.com/devicekit/DeviceKit/pull/286))
416 |
417 | | Device | Case value |
418 | | --- | --- |
419 | | iPhone 13 | `Device.iPhone13` |
420 | | iPhone 13 mini | `Device.iPhone13Mini` |
421 | | iPhone 13 Pro | `Device.iPhone13Pro` |
422 | | iPhone 13 Pro Max | `Device.iPhone13ProMax` |
423 | | iPad (9th generation) | `Device.iPad9` |
424 | | iPad mini (6th generation) | `Device.iPadMini6` |
425 |
426 | ### Changes
427 |
428 | - Switched from Travis CI to GitHub Actions.
429 |
430 | ## Version 4.4.0
431 |
432 | Releasedate: 2021-04-29
433 |
434 | ```ruby
435 | pod 'DeviceKit', '~> 4.4'
436 | ```
437 |
438 | This version adds support for the devices announced at the April 2021 Apple Event: ([#279](https://github.com/devicekit/DeviceKit/pull/279))
439 |
440 | - iPad Pro (11-inch) (3rd generation) `Device.iPadPro11Inch3`
441 | - iPad Pro (12.9-inch) (5th generation) `Device.iPadPro12Inch5`
442 | - Apple TV 4K (2nd generation) `Device.appleTV4K2`
443 |
444 | ## Version 4.3.0
445 |
446 | Releasedate: 2021-02-12
447 |
448 | ```ruby
449 | pod 'DeviceKit', '~> 4.3'
450 | ```
451 |
452 | This version adds support for the Simulator running on Apple Silicon and fixes documentation:
453 |
454 | - Support for running in Simulator on Apple Silicon. ([#273](https://github.com/devicekit/DeviceKit/pull/273))
455 | - Fix tech specs link and images for iPhone 12 models and iPad Air (4th generation). ([#272](https://github.com/devicekit/DeviceKit/pull/272))
456 |
457 | ## Version 4.2.1
458 |
459 | Releasedate: 2020-10-22
460 |
461 | ```ruby
462 | pod 'DeviceKit', '~> 4.2'
463 | ```
464 |
465 | This version fixes a couple of bugs introduced in the v4.2.0 release:
466 |
467 | - `Device.allDevicesWithALidarSensor` didn't include iPhone 12 Pro and iPhone 12 Pro Max. ([#268](https://github.com/devicekit/DeviceKit/pull/268) [#266](https://github.com/devicekit/DeviceKit/issues/266))
468 | - `Device.iPadAir4.screenRatio` returned an invalid screen ratio. ([#268](https://github.com/devicekit/DeviceKit/pull/268) [#267](https://github.com/devicekit/DeviceKit/issues/267))
469 |
470 | ## Version 4.2.0
471 |
472 | Releasedate: 2020-10-21
473 |
474 | ```ruby
475 | pod 'DeviceKit', '~> 4.2'
476 | ```
477 |
478 | This release will add support for the October 2020 devices. ([#262](https://github.com/devicekit/DeviceKit/pull/262))
479 |
480 | - iPad Air (4th generation)
481 | - iPhone 12
482 | - iPhone 12 mini
483 | - iPhone 12 Pro
484 | - iPhone 12 Pro Max
485 | ```swift
486 | Device.iPadAir4
487 |
488 | Device.iPhone12
489 | Device.iPhone12Mini
490 |
491 | Device.iPhone12Pro
492 | Device.iPhone12ProMax
493 | ```
494 |
495 | ## Version 4.1.0
496 |
497 | Releasedate: 2020-09-21
498 |
499 | ```ruby
500 | pod 'DeviceKit', '~> 4.1'
501 | ```
502 |
503 | This release will add support for the September 2020 devices, which will be released on the 18th of September: ([#256](https://github.com/devicekit/DeviceKit/pull/256))
504 | - iPad (8th generation)
505 | - Apple Watch Series 6
506 | - Apple Watch SE
507 | ```swift
508 | Device.iPad8
509 |
510 | Device.appleWatchSeries6_40mm
511 | Device.appleWatchSeries6_44mm
512 |
513 | Device.appleWatchSE_40mm
514 | Device.appleWatchSE_44mm
515 | ```
516 |
517 | Support for iPad Air (4th generation) will be added in a later version since it will be a long time before we know its device identifiers.
518 |
519 | ## Version 4.0.0
520 |
521 | Releasedate: 2020-09-04
522 |
523 | ```ruby
524 | pod 'DeviceKit', '~> 4.0'
525 | ```
526 |
527 | This is a v4.0.0 release because of the possibly breaking change of no longer supporting iOS 9. This decision was made because of Xcode 12 no longer supporting iOS 8.
528 |
529 | - Dropped support for iOS 8. Lowest supported version is now iOS 9. ([#249](https://github.com/devicekit/DeviceKit/pull/249))
530 | - Updated project settings for Xcode 12. ([#248](https://github.com/devicekit/DeviceKit/pull/248))
531 |
532 | ## Version 3.2.0
533 |
534 | Releasedate: 2020-04-29
535 |
536 | ```ruby
537 | pod 'DeviceKit', '~> 3.2'
538 | ```
539 |
540 | ### iPhone SE (2nd generation)
541 | - Added support for the iPhone SE (2nd generation). ([#238](https://github.com/devicekit/DeviceKit/pull/238))
542 | ```swift
543 | Device.iPhoneSE2
544 | ```
545 |
546 | ## Version 3.1.0
547 |
548 | Releasedate: 2020-03-29
549 |
550 | ```ruby
551 | pod 'DeviceKit', '~> 3.1'
552 | ```
553 |
554 | ### 2020 iPad Pro
555 | - Added support for the new 2020 iPad Pro. ([#235](https://github.com/devicekit/DeviceKit/pull/235))
556 | ```swift
557 | Device.iPadPro11Inch2 // iPad Pro (11-inch) (2nd generation)
558 | Device.iPadPro12inch4 // iPad Pro (12.9-inch) (4th generation)
559 | ```
560 |
561 | ### New features
562 | - Added new functions for detecting LiDAR support.
563 | - `Device.allDevicesWithALidarSensor` and `Device.current.hasLidarSensor`
564 |
565 | ## Version 3.0.0
566 |
567 | Releasedate: 2020-01-19
568 |
569 | ```ruby
570 | pod 'DeviceKit', '~> 3.0'
571 | ```
572 |
573 | ### Breaking changes
574 | - The enum for the Apple TV HD has been renamed from `.appleTV4` to `.appleTVHD`. ([#211](https://github.com/devicekit/DeviceKit/pull/211))
575 | - `.allSimulatorXSeriesDevices` has been deprecated and replaced by `.allSimulatorDevicesWithSensorHousing`. ([#212](https://github.com/devicekit/DeviceKit/pull/212))
576 | - `.allXSeriesDevices` has been deprecated and replaced by `.allDevicesWithSensorHousing`. ([#212](https://github.com/devicekit/DeviceKit/pull/212))
577 |
578 | #### Camera
579 | - `CameraTypes` has been renamed to `CameraType`. ([#212](https://github.com/devicekit/DeviceKit/pull/212))
580 | - `CameraType.normal` has been deprecated and replaced by `CameraType.wide`. ([#212](https://github.com/devicekit/DeviceKit/pull/212))
581 | - `.allDevicesWithNormalCamera` has been deprecated and replaced by `.allDevicesWithWideCamera`. ([#212](https://github.com/devicekit/DeviceKit/pull/212))
582 | - `.hasNormalCamera` has been deprecated and replaced by `.hasWideCamera`. ([#212](https://github.com/devicekit/DeviceKit/pull/212))
583 |
584 | ### New features
585 | - You can now check which devices support wireless charging through the following variables: `Device.allDevicesWithWirelessChargingSupport` and `Device.current.supportsWirelessCharging` ([#209](https://github.com/devicekit/DeviceKit/pull/209))
586 | - New `.safeDescription` variable that will provide you with a safe version of the `.description` variable. ([#212](https://github.com/devicekit/DeviceKit/pull/212))
587 | - Example: "iPhone Xʀ" vs "iPhone XR"
588 |
589 | ### Bugfixes
590 | - `.allDevicesWith3dTouchSupport` contained `.iPhoneSE` which was incorrect. ([#226](https://github.com/devicekit/DeviceKit/pull/226))
591 | - Some variables would return incorrect values when running on the simulator. ([#227](https://github.com/devicekit/DeviceKit/pull/227))
592 |
593 | ## Version 2.3.0
594 |
595 | Releasedate: 2019-10-02
596 |
597 | ```ruby
598 | pod 'DeviceKit', '~> 2.3'
599 | ```
600 |
601 | ### New devices
602 | - Added support for the new september 2019 devices:
603 | - iPad (7th generation)
604 |
605 | ## Version 2.2.0
606 |
607 | Releasedate: 2019-09-24
608 |
609 | ```ruby
610 | pod 'DeviceKit', '~> 2.2'
611 | ```
612 |
613 | ### New devices
614 | - Added support for the new september 2019 devices:
615 | - iPhone 11
616 | - iPhone 11 Pro
617 | - iPhone 11 Pro Max
618 | - Apple Watch Series 5
619 |
620 | ### New features
621 | - `Device.current.cameras` now has the `.ultraWide` camera type added for devices with that camera.
622 |
623 | ## Version 2.1.0
624 |
625 | Releasedate: 2019-09-01
626 |
627 | ```ruby
628 | pod 'DeviceKit', '~> 2.1'
629 | ```
630 |
631 | ### New features
632 | - Add support for the new iPod touch (7th generation) ([#189](https://github.com/devicekit/DeviceKit/pull/189))
633 | - Added `Device.allApplePencilCapableDevices` and `Device.current.applePencilSupport` variables for checking Apple Pencil support. ([#179](https://github.com/devicekit/DeviceKit/pull/179))
634 | - `.applePencilSupport` returns `ApplePencilSupport.firstGeneration` or `ApplePencilSupport.secondGeneration` for checking which Apple Pencil is supported.
635 | - Added 3D Touch (iOS) and Force Touch (watchOS) support variables: ([#183](https://github.com/devicekit/DeviceKit/pull/183))
636 | - iOS
637 | - `Device.allDevicesWith3dTouchSupport`
638 | - `Device.current.has3dTouchSupport`
639 | - watchOS
640 | - `Device.allWatchesWithForceTouchSupport`
641 | - `Device.current.hasForceTouchSupport`
642 | - Added variable to check for the camera's a device has. ([#188](https://github.com/devicekit/DeviceKit/pull/188))
643 | - Example: `Device.iPhoneXS.cameras` should return `CameraTypes.normal` and `CameraTypes.telephoto`.
644 |
645 | ### Fixes
646 | - Rename iPod touch 5 and 6 to iPod touch (5th generation) and iPod touch (6th generation) respectively. ([#189](https://github.com/devicekit/DeviceKit/pull/189))
647 | - Rename Apple TV (4th generation) to Apple TV HD to comply with Apple's rename of the device. ([#196](https://github.com/devicekit/DeviceKit/pull/196))
648 | - Improve support for Swift Package Manager. ([#193](https://github.com/devicekit/DeviceKit/pull/193))
649 | - Fixed the `Device.current.isZoomed` variable. ([#59 comment](https://github.com/devicekit/DeviceKit/issues/59#issuecomment-519457674) and [#198](https://github.com/devicekit/DeviceKit/pull/198))
650 |
651 |
652 | ## Version 2.0.0
653 |
654 | Releasedate: 2019-04-10
655 |
656 | ```ruby
657 | pod 'DeviceKit', '~> 2.0'
658 | ```
659 |
660 | ### Breaking changes
661 | - The original `Device()` constructor has been made private in favour of using `Device.current` to match `UIDevice.current`.
662 | - The enum values for the iPhone Xs, iPhone Xs Max and iPhone Xʀ have been renamed to be `.iPhoneXS`, `.iPhoneXSMax` and `.iPhoneXR` to match proper formatting.
663 | - `.description` for the iPhone Xs, iPhone Xs Max and iPhone Xʀ have been changed to contain small caps formatting for the s and the ʀ part.
664 | - `.description` for the iPad 5 and iPad 6 have been changed to the proper names; iPad (5th generation) and iPad (6th generation).
665 | - `.name`, `.systemName`, `.systemVersion`, `.model`, `.localizedModel`, `.batteryState` and `.batteryLevel` will now all return nil when you try to get its value when the device you are getting it from isn't the current one. (eg. `Device.iPad6.name` while running on iPad 5)
666 |
667 | ### New features
668 | - Updated to Swift 5!
669 | - New `.allDevicesWithRoundedDisplayCorners` and `.hasRoundedDisplayCorners` values to check if a device has rounded display corners. (eg. iPhone Xs and iPad Pro (3rd generation))
670 | - new `.allDevicesWithSensorHousing` and `.hasSensorHousing` values to check if a device has a screen cutout for the sensor housing. (eg. iPhone Xs)
671 |
672 | ### Bugfixes
673 | - `.isPad` and `.isPhone` are now giving correct outputs again.
674 |
675 | ## Version 1.13.0 (Last Swift 4.2 release)
676 |
677 | Releasedate: 2019-03-29
678 |
679 | ```ruby
680 | pod 'DeviceKit', '~> 1.13'
681 | ```
682 |
683 | ### New iPads
684 | Added new iPad Mini (5th generation) and iPad Air (3rd generation)
685 | ```swift
686 | Device.iPadMini5 // iPad Mini (5th generation)
687 | Device.iPadAir3 // iPad Air (3rd generation)
688 | ```
689 |
--------------------------------------------------------------------------------
/Dangerfile:
--------------------------------------------------------------------------------
1 | # Source: https://gist.github.com/candostdagdeviren/e49271e6a4b80f93f3193af89d10f4b1
2 |
3 | # PR is a work in progress and shouldn't be merged yet
4 | warn "PR is classed as Work in Progress" if github.pr_title.include? "[WIP]"
5 |
6 | # Warn when there is a big PR
7 | warn "Big PR, consider splitting into smaller" if git.lines_of_code > 500
8 |
9 | # Ensure a clean commits history
10 | if git.commits.any? { |c| c.message =~ /^Merge branch '#{github.branch_for_base}'/ }
11 | fail "Please rebase to get rid of the merge commits in this PR"
12 | end
13 |
14 | # Mainly to encourage writing up some reasoning about the PR, rather than
15 | # just leaving a title
16 | if github.pr_body.length < 5
17 | fail "Please provide a summary in the Pull Request description"
18 | end
19 |
20 | # If these are all empty something has gone wrong, better to raise it in a comment
21 | if git.modified_files.empty? && git.added_files.empty? && git.deleted_files.empty?
22 | fail "This PR has no changes at all, this is likely an issue during development."
23 | end
24 |
25 | has_app_changes = !git.modified_files.grep(/ProjectName/).empty?
26 | has_test_changes = !git.modified_files.grep(/ProjectNameTests/).empty?
27 |
28 | # If changes are more than 10 lines of code, tests need to be updated too
29 | if has_app_changes && !has_test_changes && git.lines_of_code > 10
30 | fail("Tests were not updated", sticky: false)
31 | end
32 |
33 | # Info.plist file shouldn't change often. Leave warning if it changes.
34 | is_plist_change = git.modified_files.sort == ["ProjectName/Info.plist"].sort
35 |
36 | if !is_plist_change
37 | warn "Plist changed, don't forget to localize your plist values"
38 | end
39 |
40 | podfile_updated = !git.modified_files.grep(/Podfile/).empty?
41 |
42 | # Leave warning, if Podfile changes
43 | if podfile_updated
44 | warn "The `Podfile` was updated"
45 | end
46 |
47 | # This is swiftlint plugin. More info: https://github.com/ashfurrow/danger-swiftlint
48 | #
49 | # This lints all Swift files and leave comments in PR if
50 | # there is any issue with linting
51 | swiftlint.lint_files
52 | swiftlint.lint_files inline_mode: true
53 |
--------------------------------------------------------------------------------
/DeviceKit.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'DeviceKit'
3 | s.version = '5.6.0'
4 | s.summary = 'DeviceKit is a µ-framework that provides a value-type replacement of UIDevice.'
5 |
6 | s.description = <<-DESC
7 | `DeviceKit` is a value-type wrapper and extension of [`UIDevice`](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIDevice_Class/). It detects both devices and different simulators.
8 | DESC
9 |
10 | s.homepage = 'https://github.com/devicekit/DeviceKit'
11 | s.license = 'MIT'
12 | s.author = 'DeviceKit'
13 | s.social_media_url = 'https://twitter.com/dennis_weissman'
14 |
15 | s.requires_arc = true
16 | s.ios.deployment_target = '13.0'
17 | s.tvos.deployment_target = '13.0'
18 | s.watchos.deployment_target = '7.0'
19 |
20 | s.swift_version = '5.0'
21 |
22 | s.source = { :git => 'https://github.com/devicekit/DeviceKit.git', :tag => s.version }
23 | s.source_files = 'Source/Device.generated.swift'
24 |
25 | s.resource_bundles = { 'DeviceKit' => 'Source/PrivacyInfo.xcprivacy' }
26 |
27 | s.requires_arc = true
28 | end
29 |
--------------------------------------------------------------------------------
/DeviceKit.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
25 |
--------------------------------------------------------------------------------
/DeviceKit.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXAggregateTarget section */
10 | 6D7666191F1A083200F59630 /* Run SwiftLint */ = {
11 | isa = PBXAggregateTarget;
12 | buildConfigurationList = 6D76661A1F1A083300F59630 /* Build configuration list for PBXAggregateTarget "Run SwiftLint" */;
13 | buildPhases = (
14 | 6D76661D1F1A083A00F59630 /* Run SwiftLint */,
15 | );
16 | dependencies = (
17 | );
18 | name = "Run SwiftLint";
19 | productName = "Run SwiftLint";
20 | };
21 | /* End PBXAggregateTarget section */
22 |
23 | /* Begin PBXBuildFile section */
24 | 6D29C0C01F122C7A005B52BD /* Device.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D29C0BF1F122C77005B52BD /* Device.generated.swift */; };
25 | 955EE5A31D5E581B008C3DA8 /* DeviceKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 955EE59A1D5E581B008C3DA8 /* DeviceKit.framework */; };
26 | 955EE5B41D5E5A90008C3DA8 /* Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C7E8451C61253900B0189E /* Tests.swift */; };
27 | FCF1EAD423381E5700B609AA /* CHANGELOG.md in Resources */ = {isa = PBXBuildFile; fileRef = FCF1EAD323381E5700B609AA /* CHANGELOG.md */; };
28 | /* End PBXBuildFile section */
29 |
30 | /* Begin PBXContainerItemProxy section */
31 | 955EE5A41D5E581B008C3DA8 /* PBXContainerItemProxy */ = {
32 | isa = PBXContainerItemProxy;
33 | containerPortal = 95CBDB641BFD2B440065FC66 /* Project object */;
34 | proxyType = 1;
35 | remoteGlobalIDString = 955EE5991D5E581B008C3DA8;
36 | remoteInfo = DeviceKit;
37 | };
38 | /* End PBXContainerItemProxy section */
39 |
40 | /* Begin PBXFileReference section */
41 | 6D29C0BC1F122863005B52BD /* Device.swift.gyb */ = {isa = PBXFileReference; explicitFileType = text.script.python; fileEncoding = 4; lineEnding = 0; name = Device.swift.gyb; path = Source/Device.swift.gyb; sourceTree = ""; wrapsLines = 0; };
42 | 6D29C0BF1F122C77005B52BD /* Device.generated.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Device.generated.swift; path = Source/Device.generated.swift; sourceTree = ""; };
43 | 6D8442931EED755900F2864D /* Gemfile.lock */ = {isa = PBXFileReference; lastKnownFileType = text; path = Gemfile.lock; sourceTree = ""; };
44 | 6D9B30FC1F1A2DC50008F7E0 /* gyb.py */ = {isa = PBXFileReference; lastKnownFileType = text.script.python; path = gyb.py; sourceTree = ""; };
45 | 6D9B30FD1F1A2DC50008F7E0 /* gyb */ = {isa = PBXFileReference; lastKnownFileType = text; path = gyb; sourceTree = ""; };
46 | 6DAAE66C1EDDC53800074892 /* Dangerfile */ = {isa = PBXFileReference; lastKnownFileType = text; path = Dangerfile; sourceTree = ""; };
47 | 6DAAE66D1EDDF06700074892 /* Gemfile */ = {isa = PBXFileReference; lastKnownFileType = text; path = Gemfile; sourceTree = ""; };
48 | 951E3A0E1C61549400261610 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Source/Info.plist; sourceTree = ""; };
49 | 954977FA1E748DC600D6FAEB /* push.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; name = push.sh; path = Scripts/push.sh; sourceTree = ""; };
50 | 955EE59A1D5E581B008C3DA8 /* DeviceKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DeviceKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
51 | 955EE5A21D5E581B008C3DA8 /* DeviceKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DeviceKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
52 | 957D18251C28C1E90067D203 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; };
53 | 957D18261C28C1F30067D203 /* DeviceKit.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = DeviceKit.podspec; sourceTree = ""; };
54 | 95C7E83F1C61239D00B0189E /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitignore; sourceTree = ""; };
55 | 95C7E8451C61253900B0189E /* Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Tests.swift; path = Tests/Tests.swift; sourceTree = ""; };
56 | 95C7E8471C612ABA00B0189E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Tests/Info.plist; sourceTree = ""; };
57 | 95C7E84D1C6130DB00B0189E /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = .swiftlint.yml; sourceTree = ""; };
58 | 95C7E84E1C61332300B0189E /* Package.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; };
59 | FC5C20FA2A5197E2009406EE /* .github */ = {isa = PBXFileReference; lastKnownFileType = folder; path = .github; sourceTree = ""; };
60 | FCC5A8F22B5820A6004E159B /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = Source/PrivacyInfo.xcprivacy; sourceTree = ""; };
61 | FCF1EAD323381E5700B609AA /* CHANGELOG.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; };
62 | /* End PBXFileReference section */
63 |
64 | /* Begin PBXFrameworksBuildPhase section */
65 | 955EE5961D5E581B008C3DA8 /* Frameworks */ = {
66 | isa = PBXFrameworksBuildPhase;
67 | buildActionMask = 2147483647;
68 | files = (
69 | );
70 | runOnlyForDeploymentPostprocessing = 0;
71 | };
72 | 955EE59F1D5E581B008C3DA8 /* Frameworks */ = {
73 | isa = PBXFrameworksBuildPhase;
74 | buildActionMask = 2147483647;
75 | files = (
76 | 955EE5A31D5E581B008C3DA8 /* DeviceKit.framework in Frameworks */,
77 | );
78 | runOnlyForDeploymentPostprocessing = 0;
79 | };
80 | /* End PBXFrameworksBuildPhase section */
81 |
82 | /* Begin PBXGroup section */
83 | 6D9B30FB1F1A2DC50008F7E0 /* Utils */ = {
84 | isa = PBXGroup;
85 | children = (
86 | 6D9B30FC1F1A2DC50008F7E0 /* gyb.py */,
87 | 6D9B30FD1F1A2DC50008F7E0 /* gyb */,
88 | );
89 | path = Utils;
90 | sourceTree = "";
91 | };
92 | 954977F91E748DB000D6FAEB /* Scripts */ = {
93 | isa = PBXGroup;
94 | children = (
95 | 954977FA1E748DC600D6FAEB /* push.sh */,
96 | );
97 | name = Scripts;
98 | sourceTree = "";
99 | };
100 | 95C7E83B1C6122BF00B0189E /* Source */ = {
101 | isa = PBXGroup;
102 | children = (
103 | 6D29C0BF1F122C77005B52BD /* Device.generated.swift */,
104 | 6D29C0BC1F122863005B52BD /* Device.swift.gyb */,
105 | 951E3A0E1C61549400261610 /* Info.plist */,
106 | FCC5A8F22B5820A6004E159B /* PrivacyInfo.xcprivacy */,
107 | );
108 | name = Source;
109 | sourceTree = "";
110 | };
111 | 95C7E8411C61241200B0189E /* Tests */ = {
112 | isa = PBXGroup;
113 | children = (
114 | 95C7E8451C61253900B0189E /* Tests.swift */,
115 | 95C7E8471C612ABA00B0189E /* Info.plist */,
116 | );
117 | name = Tests;
118 | sourceTree = "";
119 | };
120 | 95CBDB631BFD2B440065FC66 = {
121 | isa = PBXGroup;
122 | children = (
123 | FC5C20FA2A5197E2009406EE /* .github */,
124 | 95C7E83B1C6122BF00B0189E /* Source */,
125 | 95C7E8411C61241200B0189E /* Tests */,
126 | 954977F91E748DB000D6FAEB /* Scripts */,
127 | 957D18251C28C1E90067D203 /* README.md */,
128 | FCF1EAD323381E5700B609AA /* CHANGELOG.md */,
129 | 95C7E84E1C61332300B0189E /* Package.swift */,
130 | 957D18261C28C1F30067D203 /* DeviceKit.podspec */,
131 | 95C7E84D1C6130DB00B0189E /* .swiftlint.yml */,
132 | 6DAAE66C1EDDC53800074892 /* Dangerfile */,
133 | 6DAAE66D1EDDF06700074892 /* Gemfile */,
134 | 6D8442931EED755900F2864D /* Gemfile.lock */,
135 | 95C7E83F1C61239D00B0189E /* .gitignore */,
136 | 6D9B30FB1F1A2DC50008F7E0 /* Utils */,
137 | 95CBDB701BFD2B5F0065FC66 /* Products */,
138 | );
139 | indentWidth = 2;
140 | sourceTree = "";
141 | tabWidth = 2;
142 | usesTabs = 0;
143 | };
144 | 95CBDB701BFD2B5F0065FC66 /* Products */ = {
145 | isa = PBXGroup;
146 | children = (
147 | 955EE59A1D5E581B008C3DA8 /* DeviceKit.framework */,
148 | 955EE5A21D5E581B008C3DA8 /* DeviceKitTests.xctest */,
149 | );
150 | name = Products;
151 | sourceTree = "";
152 | };
153 | /* End PBXGroup section */
154 |
155 | /* Begin PBXHeadersBuildPhase section */
156 | 955EE5971D5E581B008C3DA8 /* Headers */ = {
157 | isa = PBXHeadersBuildPhase;
158 | buildActionMask = 2147483647;
159 | files = (
160 | );
161 | runOnlyForDeploymentPostprocessing = 0;
162 | };
163 | /* End PBXHeadersBuildPhase section */
164 |
165 | /* Begin PBXNativeTarget section */
166 | 955EE5991D5E581B008C3DA8 /* DeviceKit */ = {
167 | isa = PBXNativeTarget;
168 | buildConfigurationList = 955EE5AB1D5E581B008C3DA8 /* Build configuration list for PBXNativeTarget "DeviceKit" */;
169 | buildPhases = (
170 | 6D29C0BE1F1228BE005B52BD /* Generate Device */,
171 | 955EE5951D5E581B008C3DA8 /* Sources */,
172 | 955EE5961D5E581B008C3DA8 /* Frameworks */,
173 | 955EE5971D5E581B008C3DA8 /* Headers */,
174 | 955EE5981D5E581B008C3DA8 /* Resources */,
175 | );
176 | buildRules = (
177 | );
178 | dependencies = (
179 | );
180 | name = DeviceKit;
181 | productName = DeviceKit;
182 | productReference = 955EE59A1D5E581B008C3DA8 /* DeviceKit.framework */;
183 | productType = "com.apple.product-type.framework";
184 | };
185 | 955EE5A11D5E581B008C3DA8 /* DeviceKitTests */ = {
186 | isa = PBXNativeTarget;
187 | buildConfigurationList = 955EE5AE1D5E581B008C3DA8 /* Build configuration list for PBXNativeTarget "DeviceKitTests" */;
188 | buildPhases = (
189 | 955EE59E1D5E581B008C3DA8 /* Sources */,
190 | 955EE59F1D5E581B008C3DA8 /* Frameworks */,
191 | 955EE5A01D5E581B008C3DA8 /* Resources */,
192 | );
193 | buildRules = (
194 | );
195 | dependencies = (
196 | 955EE5A51D5E581B008C3DA8 /* PBXTargetDependency */,
197 | );
198 | name = DeviceKitTests;
199 | productName = DeviceKitTests;
200 | productReference = 955EE5A21D5E581B008C3DA8 /* DeviceKitTests.xctest */;
201 | productType = "com.apple.product-type.bundle.unit-test";
202 | };
203 | /* End PBXNativeTarget section */
204 |
205 | /* Begin PBXProject section */
206 | 95CBDB641BFD2B440065FC66 /* Project object */ = {
207 | isa = PBXProject;
208 | attributes = {
209 | LastSwiftUpdateCheck = 0800;
210 | LastUpgradeCheck = 1200;
211 | ORGANIZATIONNAME = "Dennis Weissmann";
212 | TargetAttributes = {
213 | 6D7666191F1A083200F59630 = {
214 | CreatedOnToolsVersion = 9.0;
215 | };
216 | 955EE5991D5E581B008C3DA8 = {
217 | CreatedOnToolsVersion = 8.0;
218 | LastSwiftMigration = 1020;
219 | ProvisioningStyle = Automatic;
220 | };
221 | 955EE5A11D5E581B008C3DA8 = {
222 | CreatedOnToolsVersion = 8.0;
223 | LastSwiftMigration = 1020;
224 | ProvisioningStyle = Automatic;
225 | };
226 | };
227 | };
228 | buildConfigurationList = 95CBDB671BFD2B440065FC66 /* Build configuration list for PBXProject "DeviceKit" */;
229 | compatibilityVersion = "Xcode 9.3";
230 | developmentRegion = English;
231 | hasScannedForEncodings = 0;
232 | knownRegions = (
233 | English,
234 | en,
235 | );
236 | mainGroup = 95CBDB631BFD2B440065FC66;
237 | productRefGroup = 95CBDB701BFD2B5F0065FC66 /* Products */;
238 | projectDirPath = "";
239 | projectRoot = "";
240 | targets = (
241 | 955EE5991D5E581B008C3DA8 /* DeviceKit */,
242 | 955EE5A11D5E581B008C3DA8 /* DeviceKitTests */,
243 | 6D7666191F1A083200F59630 /* Run SwiftLint */,
244 | );
245 | };
246 | /* End PBXProject section */
247 |
248 | /* Begin PBXResourcesBuildPhase section */
249 | 955EE5981D5E581B008C3DA8 /* Resources */ = {
250 | isa = PBXResourcesBuildPhase;
251 | buildActionMask = 2147483647;
252 | files = (
253 | FCF1EAD423381E5700B609AA /* CHANGELOG.md in Resources */,
254 | );
255 | runOnlyForDeploymentPostprocessing = 0;
256 | };
257 | 955EE5A01D5E581B008C3DA8 /* Resources */ = {
258 | isa = PBXResourcesBuildPhase;
259 | buildActionMask = 2147483647;
260 | files = (
261 | );
262 | runOnlyForDeploymentPostprocessing = 0;
263 | };
264 | /* End PBXResourcesBuildPhase section */
265 |
266 | /* Begin PBXShellScriptBuildPhase section */
267 | 6D29C0BE1F1228BE005B52BD /* Generate Device */ = {
268 | isa = PBXShellScriptBuildPhase;
269 | buildActionMask = 2147483647;
270 | files = (
271 | );
272 | inputPaths = (
273 | "$(SRCROOT)/Source/Device.swift.gyb",
274 | );
275 | name = "Generate Device";
276 | outputPaths = (
277 | "$(SRCROOT)/Source/Device.generated.swift",
278 | );
279 | runOnlyForDeploymentPostprocessing = 0;
280 | shellPath = /bin/sh;
281 | shellScript = "./Utils/gyb --line-directive '' -o ./Source/Device.generated.swift ./Source/Device.swift.gyb\n";
282 | };
283 | 6D76661D1F1A083A00F59630 /* Run SwiftLint */ = {
284 | isa = PBXShellScriptBuildPhase;
285 | buildActionMask = 2147483647;
286 | files = (
287 | );
288 | inputPaths = (
289 | );
290 | name = "Run SwiftLint";
291 | outputPaths = (
292 | );
293 | runOnlyForDeploymentPostprocessing = 0;
294 | shellPath = /bin/sh;
295 | shellScript = "if which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n";
296 | };
297 | /* End PBXShellScriptBuildPhase section */
298 |
299 | /* Begin PBXSourcesBuildPhase section */
300 | 955EE5951D5E581B008C3DA8 /* Sources */ = {
301 | isa = PBXSourcesBuildPhase;
302 | buildActionMask = 2147483647;
303 | files = (
304 | 6D29C0C01F122C7A005B52BD /* Device.generated.swift in Sources */,
305 | );
306 | runOnlyForDeploymentPostprocessing = 0;
307 | };
308 | 955EE59E1D5E581B008C3DA8 /* Sources */ = {
309 | isa = PBXSourcesBuildPhase;
310 | buildActionMask = 2147483647;
311 | files = (
312 | 955EE5B41D5E5A90008C3DA8 /* Tests.swift in Sources */,
313 | );
314 | runOnlyForDeploymentPostprocessing = 0;
315 | };
316 | /* End PBXSourcesBuildPhase section */
317 |
318 | /* Begin PBXTargetDependency section */
319 | 955EE5A51D5E581B008C3DA8 /* PBXTargetDependency */ = {
320 | isa = PBXTargetDependency;
321 | target = 955EE5991D5E581B008C3DA8 /* DeviceKit */;
322 | targetProxy = 955EE5A41D5E581B008C3DA8 /* PBXContainerItemProxy */;
323 | };
324 | /* End PBXTargetDependency section */
325 |
326 | /* Begin XCBuildConfiguration section */
327 | 6D76661B1F1A083300F59630 /* Debug */ = {
328 | isa = XCBuildConfiguration;
329 | buildSettings = {
330 | CLANG_ENABLE_OBJC_WEAK = YES;
331 | PRODUCT_NAME = "$(TARGET_NAME)";
332 | };
333 | name = Debug;
334 | };
335 | 6D76661C1F1A083300F59630 /* Release */ = {
336 | isa = XCBuildConfiguration;
337 | buildSettings = {
338 | CLANG_ENABLE_OBJC_WEAK = YES;
339 | PRODUCT_NAME = "$(TARGET_NAME)";
340 | };
341 | name = Release;
342 | };
343 | 955EE5AC1D5E581B008C3DA8 /* Debug */ = {
344 | isa = XCBuildConfiguration;
345 | buildSettings = {
346 | ALWAYS_SEARCH_USER_PATHS = NO;
347 | APPLICATION_EXTENSION_API_ONLY = YES;
348 | BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
349 | CLANG_ANALYZER_NONNULL = YES;
350 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
351 | CLANG_CXX_LIBRARY = "libc++";
352 | CLANG_ENABLE_MODULES = YES;
353 | CLANG_ENABLE_OBJC_ARC = YES;
354 | CLANG_STATIC_ANALYZER_MODE = deep;
355 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
356 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
357 | CLANG_WARN_INFINITE_RECURSION = YES;
358 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
359 | CLANG_WARN_SUSPICIOUS_MOVES = YES;
360 | CODE_SIGN_IDENTITY = "";
361 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
362 | COPY_PHASE_STRIP = NO;
363 | CURRENT_PROJECT_VERSION = 1;
364 | DEBUG_INFORMATION_FORMAT = dwarf;
365 | DEFINES_MODULE = YES;
366 | DEVELOPMENT_TEAM = "";
367 | DYLIB_COMPATIBILITY_VERSION = 1;
368 | DYLIB_CURRENT_VERSION = 1;
369 | DYLIB_INSTALL_NAME_BASE = "@rpath";
370 | GCC_C_LANGUAGE_STANDARD = gnu99;
371 | GCC_DYNAMIC_NO_PIC = NO;
372 | GCC_OPTIMIZATION_LEVEL = 0;
373 | GCC_PREPROCESSOR_DEFINITIONS = (
374 | "DEBUG=1",
375 | "$(inherited)",
376 | );
377 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
378 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
379 | INFOPLIST_FILE = Source/Info.plist;
380 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
381 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
382 | LD_RUNPATH_SEARCH_PATHS = (
383 | "$(inherited)",
384 | "@executable_path/Frameworks",
385 | "@loader_path/Frameworks",
386 | );
387 | MARKETING_VERSION = 5.6.0;
388 | MERGEABLE_LIBRARY = YES;
389 | MTL_ENABLE_DEBUG_INFO = YES;
390 | PRODUCT_BUNDLE_IDENTIFIER = me.dennisweissmann.DeviceKit;
391 | PRODUCT_NAME = "$(TARGET_NAME)";
392 | RUN_CLANG_STATIC_ANALYZER = YES;
393 | SDKROOT = iphoneos;
394 | SKIP_INSTALL = YES;
395 | SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator watchos watchsimulator xros xrsimulator";
396 | SUPPORTS_MACCATALYST = YES;
397 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
398 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
399 | SWIFT_VERSION = 5.0;
400 | TARGETED_DEVICE_FAMILY = "1,2,3,4,7";
401 | TVOS_DEPLOYMENT_TARGET = 13.0;
402 | VERSIONING_SYSTEM = "apple-generic";
403 | VERSION_INFO_PREFIX = "";
404 | WATCHOS_DEPLOYMENT_TARGET = 4.0;
405 | };
406 | name = Debug;
407 | };
408 | 955EE5AD1D5E581B008C3DA8 /* Release */ = {
409 | isa = XCBuildConfiguration;
410 | buildSettings = {
411 | ALWAYS_SEARCH_USER_PATHS = NO;
412 | APPLICATION_EXTENSION_API_ONLY = YES;
413 | BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
414 | CLANG_ANALYZER_NONNULL = YES;
415 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
416 | CLANG_CXX_LIBRARY = "libc++";
417 | CLANG_ENABLE_MODULES = YES;
418 | CLANG_ENABLE_OBJC_ARC = YES;
419 | CLANG_STATIC_ANALYZER_MODE = deep;
420 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
421 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
422 | CLANG_WARN_INFINITE_RECURSION = YES;
423 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
424 | CLANG_WARN_SUSPICIOUS_MOVES = YES;
425 | CODE_SIGN_IDENTITY = "";
426 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
427 | COPY_PHASE_STRIP = NO;
428 | CURRENT_PROJECT_VERSION = 1;
429 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
430 | DEFINES_MODULE = YES;
431 | DEVELOPMENT_TEAM = "";
432 | DYLIB_COMPATIBILITY_VERSION = 1;
433 | DYLIB_CURRENT_VERSION = 1;
434 | DYLIB_INSTALL_NAME_BASE = "@rpath";
435 | ENABLE_NS_ASSERTIONS = NO;
436 | GCC_C_LANGUAGE_STANDARD = gnu99;
437 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
438 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
439 | INFOPLIST_FILE = Source/Info.plist;
440 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
441 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
442 | LD_RUNPATH_SEARCH_PATHS = (
443 | "$(inherited)",
444 | "@executable_path/Frameworks",
445 | "@loader_path/Frameworks",
446 | );
447 | MARKETING_VERSION = 5.6.0;
448 | MERGEABLE_LIBRARY = YES;
449 | MTL_ENABLE_DEBUG_INFO = NO;
450 | PRODUCT_BUNDLE_IDENTIFIER = me.dennisweissmann.DeviceKit;
451 | PRODUCT_NAME = "$(TARGET_NAME)";
452 | RUN_CLANG_STATIC_ANALYZER = YES;
453 | SDKROOT = iphoneos;
454 | SKIP_INSTALL = YES;
455 | SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator watchos watchsimulator xros xrsimulator";
456 | SUPPORTS_MACCATALYST = YES;
457 | SWIFT_VERSION = 5.0;
458 | TARGETED_DEVICE_FAMILY = "1,2,3,4,7";
459 | TVOS_DEPLOYMENT_TARGET = 13.0;
460 | VALIDATE_PRODUCT = YES;
461 | VERSIONING_SYSTEM = "apple-generic";
462 | VERSION_INFO_PREFIX = "";
463 | WATCHOS_DEPLOYMENT_TARGET = 4.0;
464 | };
465 | name = Release;
466 | };
467 | 955EE5AF1D5E581B008C3DA8 /* Debug */ = {
468 | isa = XCBuildConfiguration;
469 | buildSettings = {
470 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
471 | ALWAYS_SEARCH_USER_PATHS = NO;
472 | CLANG_ANALYZER_NONNULL = YES;
473 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
474 | CLANG_CXX_LIBRARY = "libc++";
475 | CLANG_ENABLE_MODULES = YES;
476 | CLANG_ENABLE_OBJC_ARC = YES;
477 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
478 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
479 | CLANG_WARN_INFINITE_RECURSION = YES;
480 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
481 | CLANG_WARN_SUSPICIOUS_MOVES = YES;
482 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
483 | COPY_PHASE_STRIP = NO;
484 | DEBUG_INFORMATION_FORMAT = dwarf;
485 | DEVELOPMENT_TEAM = SWR98KT795;
486 | GCC_C_LANGUAGE_STANDARD = gnu99;
487 | GCC_DYNAMIC_NO_PIC = NO;
488 | GCC_OPTIMIZATION_LEVEL = 0;
489 | GCC_PREPROCESSOR_DEFINITIONS = (
490 | "DEBUG=1",
491 | "$(inherited)",
492 | );
493 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
494 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
495 | INFOPLIST_FILE = Tests/Info.plist;
496 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
497 | LD_RUNPATH_SEARCH_PATHS = (
498 | "$(inherited)",
499 | "@executable_path/Frameworks",
500 | "@loader_path/Frameworks",
501 | );
502 | MTL_ENABLE_DEBUG_INFO = YES;
503 | PRODUCT_BUNDLE_IDENTIFIER = me.dennisweissmann.DeviceKitTests;
504 | PRODUCT_NAME = "$(TARGET_NAME)";
505 | SDKROOT = iphoneos;
506 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
507 | SWIFT_OBJC_INTERFACE_HEADER_NAME = "";
508 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
509 | SWIFT_VERSION = 5.0;
510 | TVOS_DEPLOYMENT_TARGET = 13.0;
511 | };
512 | name = Debug;
513 | };
514 | 955EE5B01D5E581B008C3DA8 /* Release */ = {
515 | isa = XCBuildConfiguration;
516 | buildSettings = {
517 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
518 | ALWAYS_SEARCH_USER_PATHS = NO;
519 | CLANG_ANALYZER_NONNULL = YES;
520 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
521 | CLANG_CXX_LIBRARY = "libc++";
522 | CLANG_ENABLE_MODULES = YES;
523 | CLANG_ENABLE_OBJC_ARC = YES;
524 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
525 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
526 | CLANG_WARN_INFINITE_RECURSION = YES;
527 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
528 | CLANG_WARN_SUSPICIOUS_MOVES = YES;
529 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
530 | COPY_PHASE_STRIP = NO;
531 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
532 | DEVELOPMENT_TEAM = SWR98KT795;
533 | ENABLE_NS_ASSERTIONS = NO;
534 | GCC_C_LANGUAGE_STANDARD = gnu99;
535 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
536 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
537 | INFOPLIST_FILE = Tests/Info.plist;
538 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
539 | LD_RUNPATH_SEARCH_PATHS = (
540 | "$(inherited)",
541 | "@executable_path/Frameworks",
542 | "@loader_path/Frameworks",
543 | );
544 | MTL_ENABLE_DEBUG_INFO = NO;
545 | PRODUCT_BUNDLE_IDENTIFIER = me.dennisweissmann.DeviceKitTests;
546 | PRODUCT_NAME = "$(TARGET_NAME)";
547 | SDKROOT = iphoneos;
548 | SWIFT_COMPILATION_MODE = wholemodule;
549 | SWIFT_OBJC_INTERFACE_HEADER_NAME = "";
550 | SWIFT_OPTIMIZATION_LEVEL = "-O";
551 | SWIFT_VERSION = 5.0;
552 | TVOS_DEPLOYMENT_TARGET = 13.0;
553 | VALIDATE_PRODUCT = YES;
554 | };
555 | name = Release;
556 | };
557 | 95CBDB681BFD2B440065FC66 /* Debug */ = {
558 | isa = XCBuildConfiguration;
559 | buildSettings = {
560 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
561 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
562 | CLANG_WARN_BOOL_CONVERSION = YES;
563 | CLANG_WARN_COMMA = YES;
564 | CLANG_WARN_CONSTANT_CONVERSION = YES;
565 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
566 | CLANG_WARN_EMPTY_BODY = YES;
567 | CLANG_WARN_ENUM_CONVERSION = YES;
568 | CLANG_WARN_INFINITE_RECURSION = YES;
569 | CLANG_WARN_INT_CONVERSION = YES;
570 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
571 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
572 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
573 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
574 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
575 | CLANG_WARN_STRICT_PROTOTYPES = YES;
576 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
577 | CLANG_WARN_UNREACHABLE_CODE = YES;
578 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
579 | ENABLE_STRICT_OBJC_MSGSEND = YES;
580 | ENABLE_TESTABILITY = YES;
581 | GCC_NO_COMMON_BLOCKS = YES;
582 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
583 | GCC_WARN_ABOUT_RETURN_TYPE = YES;
584 | GCC_WARN_UNDECLARED_SELECTOR = YES;
585 | GCC_WARN_UNINITIALIZED_AUTOS = YES;
586 | GCC_WARN_UNUSED_FUNCTION = YES;
587 | GCC_WARN_UNUSED_VARIABLE = YES;
588 | LD_RUNPATH_SEARCH_PATHS = (
589 | "@loader_path/Frameworks",
590 | "@executable_path/Frameworks",
591 | "@loader_path/../Frameworks",
592 | "@executable_path/../Frameworks",
593 | );
594 | ONLY_ACTIVE_ARCH = YES;
595 | SUPPORTED_PLATFORMS = "iphonesimulator iphoneos appletvos appletvsimulator watchsimulator watchos";
596 | SWIFT_ENFORCE_EXCLUSIVE_ACCESS = "compile-time";
597 | SWIFT_VERSION = 4.2;
598 | };
599 | name = Debug;
600 | };
601 | 95CBDB691BFD2B440065FC66 /* Release */ = {
602 | isa = XCBuildConfiguration;
603 | buildSettings = {
604 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
605 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
606 | CLANG_WARN_BOOL_CONVERSION = YES;
607 | CLANG_WARN_COMMA = YES;
608 | CLANG_WARN_CONSTANT_CONVERSION = YES;
609 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
610 | CLANG_WARN_EMPTY_BODY = YES;
611 | CLANG_WARN_ENUM_CONVERSION = YES;
612 | CLANG_WARN_INFINITE_RECURSION = YES;
613 | CLANG_WARN_INT_CONVERSION = YES;
614 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
615 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
616 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
617 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
618 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
619 | CLANG_WARN_STRICT_PROTOTYPES = YES;
620 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
621 | CLANG_WARN_UNREACHABLE_CODE = YES;
622 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
623 | ENABLE_STRICT_OBJC_MSGSEND = YES;
624 | GCC_NO_COMMON_BLOCKS = YES;
625 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
626 | GCC_WARN_ABOUT_RETURN_TYPE = YES;
627 | GCC_WARN_UNDECLARED_SELECTOR = YES;
628 | GCC_WARN_UNINITIALIZED_AUTOS = YES;
629 | GCC_WARN_UNUSED_FUNCTION = YES;
630 | GCC_WARN_UNUSED_VARIABLE = YES;
631 | LD_RUNPATH_SEARCH_PATHS = (
632 | "@loader_path/Frameworks",
633 | "@executable_path/Frameworks",
634 | "@loader_path/../Frameworks",
635 | "@executable_path/../Frameworks",
636 | );
637 | SUPPORTED_PLATFORMS = "iphonesimulator iphoneos appletvos appletvsimulator watchsimulator watchos";
638 | SWIFT_COMPILATION_MODE = wholemodule;
639 | SWIFT_ENFORCE_EXCLUSIVE_ACCESS = "compile-time";
640 | SWIFT_OPTIMIZATION_LEVEL = "-O";
641 | SWIFT_VERSION = 4.2;
642 | };
643 | name = Release;
644 | };
645 | /* End XCBuildConfiguration section */
646 |
647 | /* Begin XCConfigurationList section */
648 | 6D76661A1F1A083300F59630 /* Build configuration list for PBXAggregateTarget "Run SwiftLint" */ = {
649 | isa = XCConfigurationList;
650 | buildConfigurations = (
651 | 6D76661B1F1A083300F59630 /* Debug */,
652 | 6D76661C1F1A083300F59630 /* Release */,
653 | );
654 | defaultConfigurationIsVisible = 0;
655 | defaultConfigurationName = Release;
656 | };
657 | 955EE5AB1D5E581B008C3DA8 /* Build configuration list for PBXNativeTarget "DeviceKit" */ = {
658 | isa = XCConfigurationList;
659 | buildConfigurations = (
660 | 955EE5AC1D5E581B008C3DA8 /* Debug */,
661 | 955EE5AD1D5E581B008C3DA8 /* Release */,
662 | );
663 | defaultConfigurationIsVisible = 0;
664 | defaultConfigurationName = Release;
665 | };
666 | 955EE5AE1D5E581B008C3DA8 /* Build configuration list for PBXNativeTarget "DeviceKitTests" */ = {
667 | isa = XCConfigurationList;
668 | buildConfigurations = (
669 | 955EE5AF1D5E581B008C3DA8 /* Debug */,
670 | 955EE5B01D5E581B008C3DA8 /* Release */,
671 | );
672 | defaultConfigurationIsVisible = 0;
673 | defaultConfigurationName = Release;
674 | };
675 | 95CBDB671BFD2B440065FC66 /* Build configuration list for PBXProject "DeviceKit" */ = {
676 | isa = XCConfigurationList;
677 | buildConfigurations = (
678 | 95CBDB681BFD2B440065FC66 /* Debug */,
679 | 95CBDB691BFD2B440065FC66 /* Release */,
680 | );
681 | defaultConfigurationIsVisible = 0;
682 | defaultConfigurationName = Release;
683 | };
684 | /* End XCConfigurationList section */
685 | };
686 | rootObject = 95CBDB641BFD2B440065FC66 /* Project object */;
687 | }
688 |
--------------------------------------------------------------------------------
/DeviceKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/DeviceKit.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/DeviceKit.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/DeviceKit.xcodeproj/xcshareddata/xcschemes/DeviceKit.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
32 |
33 |
37 |
43 |
44 |
45 |
47 |
48 |
49 |
50 |
51 |
52 |
62 |
63 |
69 |
70 |
71 |
72 |
78 |
79 |
85 |
86 |
87 |
88 |
90 |
91 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/DeviceKit.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/DeviceKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/DeviceKit.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Example/DeviceKitPlayground.playground/Contents.swift:
--------------------------------------------------------------------------------
1 | //: Playground - noun: a place where people can play
2 | // To use this playground, build DeviceKit.framework for any simulator first.
3 |
4 | import DeviceKit
5 | import UIKit
6 |
7 | let device = Device.current
8 |
9 | print(device) // prints, for example, "iPhone 6 Plus"
10 |
11 | /// Get the Device You're Running On
12 | if device == .iPhone6Plus {
13 | // Do something
14 | } else {
15 | // Do something else
16 | }
17 |
18 | /// Get the Device Family
19 | if device.isPod {
20 | // iPods (real or simulator)
21 | } else if device.isPhone {
22 | // iPhone (real or simulator)
23 | } else if device.isPad {
24 | // iPad (real or simulator)
25 | }
26 |
27 | /// Check If Running on Simulator
28 | if device.isSimulator {
29 | // Running on one of the simulators(iPod/iPhone/iPad)
30 | // Skip doing something irrelevant for Simulator
31 | }
32 |
33 | /// Get the Simulator Device
34 | switch device {
35 | case .simulator(.iPhone6s): break // You're running on the iPhone 6s simulator
36 | case .simulator(.iPadAir2): break // You're running on the iPad Air 2 simulator
37 | default: break
38 | }
39 |
40 | /// Make Sure the Device Is Contained in a Preconfigured Group
41 | let groupOfAllowedDevices: [Device] = [.iPhone6,
42 | .iPhone6Plus,
43 | .iPhone6s,
44 | .iPhone6sPlus,
45 | .simulator(.iPhone6),
46 | .simulator(.iPhone6Plus),
47 | .simulator(.iPhone6s),
48 | .simulator(.iPhone6sPlus)]
49 |
50 | if device.isOneOf(groupOfAllowedDevices) {
51 | // Do your action
52 | }
53 |
54 | /// Get the Current Battery State
55 | if let batteryState = device.batteryState,
56 | batteryState == .full || batteryState >= .charging(75) {
57 | print("Your battery is happy! 😊")
58 | }
59 |
60 | /// Get the Current Battery Level
61 | if device.batteryLevel ?? 0 >= 50 {
62 | // install_iOS()
63 | } else {
64 | // showError()
65 | }
66 |
67 | /// Get Low Power mode status
68 | if let batteryState = device.batteryState,
69 | batteryState.lowPowerMode {
70 | print("Low Power mode is enabled! 🔋")
71 | } else {
72 | print("Low Power mode is disabled! 😊")
73 | }
74 |
75 | /// Check if a Guided Access session is currently active
76 | if device.isGuidedAccessSessionActive {
77 | print("Guided Access session is currently active")
78 | } else {
79 | print("No Guided Access session is currently active")
80 | }
81 |
82 | /// Get Screen Brightness
83 | if device.screenBrightness > 50 {
84 | print("Take care of your eyes!")
85 | }
86 |
87 | /// Get Available Disk Space
88 | if Device.volumeAvailableCapacityForOpportunisticUsage ?? 0 > Int64(1_000_000) {
89 | // download that nice-to-have huge file
90 | }
91 |
92 | if Device.volumeAvailableCapacityForImportantUsage ?? 0 > Int64(1_000) {
93 | // download that file you really need
94 | }
95 |
96 | // Get the underlying device
97 | let simulator = Device.simulator(.iPhone8Plus)
98 | let realDevice = Device.iPhone8Plus
99 | simulator.realDevice == realDevice // true
100 | realDevice.realDevice == realDevice // true
101 |
102 | // Get device thermal state
103 | if device.thermalState == .nominal {
104 | print("Thermal state is nominal")
105 | }
106 |
--------------------------------------------------------------------------------
/Example/DeviceKitPlayground.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/Example/DeviceKitPlayground.playground/playground.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gem 'xcpretty'
4 | gem 'cocoapods', '1.15.2'
5 | gem 'danger'
6 | gem 'danger-swiftlint'
7 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | CFPropertyList (3.0.7)
5 | base64
6 | nkf
7 | rexml
8 | activesupport (7.1.3.2)
9 | base64
10 | bigdecimal
11 | concurrent-ruby (~> 1.0, >= 1.0.2)
12 | connection_pool (>= 2.2.5)
13 | drb
14 | i18n (>= 1.6, < 2)
15 | minitest (>= 5.1)
16 | mutex_m
17 | tzinfo (~> 2.0)
18 | addressable (2.8.0)
19 | public_suffix (>= 2.0.2, < 5.0)
20 | algoliasearch (1.27.5)
21 | httpclient (~> 2.8, >= 2.8.3)
22 | json (>= 1.5.1)
23 | atomos (0.1.3)
24 | base64 (0.2.0)
25 | bigdecimal (3.1.7)
26 | claide (1.0.3)
27 | claide-plugins (0.9.2)
28 | cork
29 | nap
30 | open4 (~> 1.3)
31 | cocoapods (1.15.2)
32 | addressable (~> 2.8)
33 | claide (>= 1.0.2, < 2.0)
34 | cocoapods-core (= 1.15.2)
35 | cocoapods-deintegrate (>= 1.0.3, < 2.0)
36 | cocoapods-downloader (>= 2.1, < 3.0)
37 | cocoapods-plugins (>= 1.0.0, < 2.0)
38 | cocoapods-search (>= 1.0.0, < 2.0)
39 | cocoapods-trunk (>= 1.6.0, < 2.0)
40 | cocoapods-try (>= 1.1.0, < 2.0)
41 | colored2 (~> 3.1)
42 | escape (~> 0.0.4)
43 | fourflusher (>= 2.3.0, < 3.0)
44 | gh_inspector (~> 1.0)
45 | molinillo (~> 0.8.0)
46 | nap (~> 1.0)
47 | ruby-macho (>= 2.3.0, < 3.0)
48 | xcodeproj (>= 1.23.0, < 2.0)
49 | cocoapods-core (1.15.2)
50 | activesupport (>= 5.0, < 8)
51 | addressable (~> 2.8)
52 | algoliasearch (~> 1.0)
53 | concurrent-ruby (~> 1.1)
54 | fuzzy_match (~> 2.0.4)
55 | nap (~> 1.0)
56 | netrc (~> 0.11)
57 | public_suffix (~> 4.0)
58 | typhoeus (~> 1.0)
59 | cocoapods-deintegrate (1.0.5)
60 | cocoapods-downloader (2.1)
61 | cocoapods-plugins (1.0.0)
62 | nap
63 | cocoapods-search (1.0.1)
64 | cocoapods-trunk (1.6.0)
65 | nap (>= 0.8, < 2.0)
66 | netrc (~> 0.11)
67 | cocoapods-try (1.2.0)
68 | colored2 (3.1.2)
69 | concurrent-ruby (1.2.3)
70 | connection_pool (2.4.1)
71 | cork (0.3.0)
72 | colored2 (~> 3.1)
73 | danger (8.3.1)
74 | claide (~> 1.0)
75 | claide-plugins (>= 0.9.2)
76 | colored2 (~> 3.1)
77 | cork (~> 0.1)
78 | faraday (>= 0.9.0, < 2.0)
79 | faraday-http-cache (~> 2.0)
80 | git (~> 1.7)
81 | kramdown (~> 2.3)
82 | kramdown-parser-gfm (~> 1.0)
83 | no_proxy_fix
84 | octokit (~> 4.7)
85 | terminal-table (>= 1, < 4)
86 | danger-swiftlint (0.26.0)
87 | danger
88 | rake (> 10)
89 | thor (~> 0.19)
90 | drb (2.2.1)
91 | escape (0.0.4)
92 | ethon (0.16.0)
93 | ffi (>= 1.15.0)
94 | faraday (1.5.1)
95 | faraday-em_http (~> 1.0)
96 | faraday-em_synchrony (~> 1.0)
97 | faraday-excon (~> 1.1)
98 | faraday-httpclient (~> 1.0.1)
99 | faraday-net_http (~> 1.0)
100 | faraday-net_http_persistent (~> 1.1)
101 | faraday-patron (~> 1.0)
102 | multipart-post (>= 1.2, < 3)
103 | ruby2_keywords (>= 0.0.4)
104 | faraday-em_http (1.0.0)
105 | faraday-em_synchrony (1.0.0)
106 | faraday-excon (1.1.0)
107 | faraday-http-cache (2.2.0)
108 | faraday (>= 0.8)
109 | faraday-httpclient (1.0.1)
110 | faraday-net_http (1.0.1)
111 | faraday-net_http_persistent (1.2.0)
112 | faraday-patron (1.0.0)
113 | ffi (1.16.3)
114 | fourflusher (2.3.1)
115 | fuzzy_match (2.0.4)
116 | gh_inspector (1.1.3)
117 | git (1.9.1)
118 | rchardet (~> 1.8)
119 | httpclient (2.8.3)
120 | i18n (1.14.4)
121 | concurrent-ruby (~> 1.0)
122 | json (2.7.2)
123 | kramdown (2.3.1)
124 | rexml
125 | kramdown-parser-gfm (1.1.0)
126 | kramdown (~> 2.0)
127 | minitest (5.22.3)
128 | molinillo (0.8.0)
129 | multipart-post (2.1.1)
130 | mutex_m (0.2.0)
131 | nanaimo (0.3.0)
132 | nap (1.1.0)
133 | netrc (0.11.0)
134 | nkf (0.2.0)
135 | no_proxy_fix (0.1.2)
136 | octokit (4.21.0)
137 | faraday (>= 0.9)
138 | sawyer (~> 0.8.0, >= 0.5.3)
139 | open4 (1.3.4)
140 | public_suffix (4.0.6)
141 | rake (13.0.6)
142 | rchardet (1.8.0)
143 | rexml (3.2.5)
144 | rouge (2.0.7)
145 | ruby-macho (2.5.1)
146 | ruby2_keywords (0.0.4)
147 | sawyer (0.8.2)
148 | addressable (>= 2.3.5)
149 | faraday (> 0.8, < 2.0)
150 | terminal-table (3.0.1)
151 | unicode-display_width (>= 1.1.1, < 3)
152 | thor (0.20.3)
153 | typhoeus (1.4.1)
154 | ethon (>= 0.9.0)
155 | tzinfo (2.0.6)
156 | concurrent-ruby (~> 1.0)
157 | unicode-display_width (2.0.0)
158 | xcodeproj (1.24.0)
159 | CFPropertyList (>= 2.3.3, < 4.0)
160 | atomos (~> 0.1.3)
161 | claide (>= 1.0.2, < 2.0)
162 | colored2 (~> 3.1)
163 | nanaimo (~> 0.3.0)
164 | rexml (~> 3.2.4)
165 | xcpretty (0.3.0)
166 | rouge (~> 2.0.7)
167 |
168 | PLATFORMS
169 | ruby
170 |
171 | DEPENDENCIES
172 | cocoapods (= 1.15.2)
173 | danger
174 | danger-swiftlint
175 | xcpretty
176 |
177 | BUNDLED WITH
178 | 2.1.4
179 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015 Dennis Weissmann
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.3
2 | //===----------------------------------------------------------------------===//
3 | //
4 | // This source file is part of the DeviceKit open source project
5 | //
6 | // Copyright © 2014 - 2018 Dennis Weissmann and the DeviceKit project authors
7 | //
8 | // License: https://github.com/dennisweissmann/DeviceKit/blob/master/LICENSE
9 | // Contributors: https://github.com/dennisweissmann/DeviceKit#contributors
10 | //
11 | //===----------------------------------------------------------------------===//
12 |
13 | import PackageDescription
14 |
15 | let package = Package(
16 | name: "DeviceKit",
17 | platforms: [
18 | .iOS(.v13),
19 | .tvOS(.v13),
20 | .watchOS(.v4)
21 | ],
22 | products: [
23 | // Products define the executables and libraries produced by a package, and make them visible to other packages.
24 | .library(
25 | name: "DeviceKit",
26 | targets: ["DeviceKit"]
27 | )
28 | ],
29 | targets: [
30 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
31 | // Targets can depend on other targets in this package, and on products in packages which this package depends on.
32 | .target(
33 | name: "DeviceKit",
34 | dependencies: [],
35 | path: "Source",
36 | resources: [.process("PrivacyInfo.xcprivacy")]
37 | ),
38 | .testTarget(
39 | name: "DeviceKitTests",
40 | dependencies: ["DeviceKit"],
41 | path: "Tests",
42 | resources: [.process("../Source/PrivacyInfo.xcprivacy")]
43 | )
44 | ],
45 | swiftLanguageVersions: [.v5]
46 | )
47 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | [](https://raw.githubusercontent.com/devicekit/DeviceKit/master/LICENSE)
4 | [](https://cocoapods.org/pods/DeviceKit)
5 | [](https://github.com/Carthage/Carthage)
6 | [](https://codecov.io/gh/devicekit/DeviceKit)
7 | [](https://cocoapods.org/pods/DeviceKit)
8 | [](https://codeclimate.com/github/devicekit/DeviceKit/maintainability)
9 | [](http://cocoadocs.org/docsets/DeviceKit)
10 |
11 |
12 | | Branch | Versions |
13 | |:---------|:----------:|
14 | | **master** | ≥ 2.0 |
15 | | **Swift 4 - 4.2** | ≥ 1.3 < 1.13 |
16 | | **Swift 3** | ≥ 1.0 < 1.3 |
17 | | **Swift 2.3** | < 1.0 |
18 |
19 | `DeviceKit` is a value-type replacement of [`UIDevice`](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIDevice_Class/).
20 |
21 | ## Current version 5.6.0
22 | See our detailed [changelog](CHANGELOG.md) for the latest features, improvements and bug fixes.
23 |
24 | ## Features
25 |
26 | - [x] Equatable
27 | - [x] Device identification
28 | - [x] Device family detection
29 | - [x] Device group detection
30 | - [x] Simulator detection
31 | - [x] Battery state
32 | - [x] Battery level
33 | - [x] Various device metrics (e.g. screen size, screen ratio, PPI)
34 | - [x] Low Power Mode detection
35 | - [x] Guided Access Session detection
36 | - [x] Screen brightness
37 | - [x] Display Zoom detection
38 | - [x] Detect available sensors (Touch ID, Face ID)
39 | - [x] Detect available disk space
40 | - [x] Apple Pencil support detection
41 |
42 | ## Requirements
43 |
44 | - iOS 11.0+
45 | - tvOS 11.0+
46 | - watchOS 4.0+
47 |
48 | ## Installation
49 | DeviceKit can be installed in various ways.
50 |
51 | ### CocoaPods
52 |
53 | #### Swift 5
54 | ```ruby
55 | pod 'DeviceKit', '~> 5.2'
56 | ```
57 | #### iOS 8.0 support
58 | ```ruby
59 | pod 'DeviceKit', '3.2'
60 | ```
61 | #### Swift 4.0 - Swift 4.2
62 | ```ruby
63 | pod 'DeviceKit', '~> 1.3'
64 | ```
65 | #### Swift 3
66 | ```ruby
67 | pod 'DeviceKit', '~> 1.2.3'
68 | ```
69 | #### Swift 2.3 (Unsupported)
70 | ```ruby
71 | pod 'DeviceKit', :git => 'https://github.com/devicekit/DeviceKit.git', :branch => 'swift-2.3-unsupported'
72 | ```
73 |
74 | ### Swift Package Manager
75 |
76 | #### Swift 5
77 | ```swift
78 | dependencies: [
79 | .package(url: "https://github.com/devicekit/DeviceKit.git", from: "4.0.0"),
80 | /// ...
81 | ]
82 | ```
83 | #### iOS 8.0 support
84 | ```swift
85 | dependencies: [
86 | .package(url: "https://github.com/devicekit/DeviceKit.git", from: "3.2.0"),
87 | /// ...
88 | ]
89 | ```
90 |
91 | ### Carthage
92 |
93 | #### Swift 5
94 | ```ogdl
95 | github "devicekit/DeviceKit" ~> 4.0
96 | ```
97 | #### iOS 8.0 support
98 | ```ogdl
99 | github "devicekit/DeviceKit" ~> 3.2
100 | ```
101 | #### Swift 4.0 - Swift 4.2
102 | ```ogdl
103 | github "devicekit/DeviceKit" ~> 1.3
104 | ```
105 | #### Swift 3
106 | ```ogdl
107 | github "devicekit/DeviceKit" ~> 1.2.3
108 | ```
109 | #### Swift 2.3 (Unsupported)
110 | ```ogdl
111 | github "devicekit/DeviceKit" "swift-2.3-unsupported"
112 | ```
113 |
114 | ### Manually
115 | To install it manually, drag the `DeviceKit` project into your app project in Xcode. Or add it as a git submodule by running:
116 | ```bash
117 | $ git submodule add https://github.com/devicekit/DeviceKit.git
118 | ```
119 |
120 | ## Usage
121 | First make sure to import the framework:
122 | ```swift
123 | import DeviceKit
124 | ```
125 |
126 | Here are some usage examples. All devices are also available as simulators:
127 | ```swift
128 | .iPhone6 => .simulator(.iPhone6)
129 | .iPhone6s => .simulator(.iPhone6s)
130 | ```
131 |
132 | You can try these examples in Playground.
133 |
134 | **Note:**
135 |
136 | > To try DeviceKit in the playground, open the `DeviceKit.xcworkspace` and build DeviceKit.framework for any simulator first by selecting "DeviceKit" as your current scheme.
137 |
138 | ### Get the Device You're Running On
139 | ```swift
140 | let device = Device.current
141 |
142 | print(device) // prints, for example, "iPhone 6 Plus"
143 |
144 | if device == .iPhone6Plus {
145 | // Do something
146 | } else {
147 | // Do something else
148 | }
149 | ```
150 |
151 | ### Get the Device Family
152 | ```swift
153 | let device = Device.current
154 | if device.isPod {
155 | // iPods (real or simulator)
156 | } else if device.isPhone {
157 | // iPhone (real or simulator)
158 | } else if device.isPad {
159 | // iPad (real or simulator)
160 | }
161 | ```
162 |
163 | ### Check If Running on Simulator
164 | ```swift
165 | let device = Device.current
166 | if device.isSimulator {
167 | // Running on one of the simulators(iPod/iPhone/iPad)
168 | // Skip doing something irrelevant for Simulator
169 | }
170 | ```
171 |
172 | ### Get the Simulator Device
173 | ```swift
174 | let device = Device.current
175 | switch device {
176 | case .simulator(.iPhone6s): break // You're running on the iPhone 6s simulator
177 | case .simulator(.iPadAir2): break // You're running on the iPad Air 2 simulator
178 | default: break
179 | }
180 | ```
181 |
182 | ### Make Sure the Device Is Contained in a Preconfigured Group
183 | ```swift
184 | let groupOfAllowedDevices: [Device] = [.iPhone6, .iPhone6Plus, .iPhone6s, .iPhone6sPlus, .simulator(.iPhone6), .simulator(.iPhone6Plus),.simulator(.iPhone6s),.simulator(.iPhone6sPlus).simulator(.iPhone8),.simulator(.iPhone8Plus),.simulator(.iPhoneX),.simulator(.iPhoneXS),.simulator(.iPhoneXSMax),.simulator(.iPhoneXR)]
185 |
186 | let device = Device.current
187 |
188 | if device.isOneOf(groupOfAllowedDevices) {
189 | // Do your action
190 | }
191 | ```
192 |
193 | ### Get the Current Battery State
194 | **Note:**
195 |
196 | > To get the current battery state we need to set `UIDevice.current.isBatteryMonitoringEnabled` to `true`. To avoid any issues with your code, we read the current setting and reset it to what it was before when we're done.
197 |
198 | ```swift
199 | if device.batteryState == .full || device.batteryState >= .charging(75) {
200 | print("Your battery is happy! 😊")
201 | }
202 | ```
203 |
204 | ### Get the Current Battery Level
205 | ```swift
206 | if device.batteryLevel >= 50 {
207 | install_iOS()
208 | } else {
209 | showError()
210 | }
211 | ```
212 |
213 | ### Get Low Power mode status
214 | ```swift
215 | if device.batteryState.lowPowerMode {
216 | print("Low Power mode is enabled! 🔋")
217 | } else {
218 | print("Low Power mode is disabled! 😊")
219 | }
220 | ```
221 |
222 | ### Check if a Guided Access session is currently active
223 | ```swift
224 | if device.isGuidedAccessSessionActive {
225 | print("Guided Access session is currently active")
226 | } else {
227 | print("No Guided Access session is currently active")
228 | }
229 | ```
230 |
231 | ### Get Screen Brightness
232 | ```swift
233 | if device.screenBrightness > 50 {
234 | print("Take care of your eyes!")
235 | }
236 | ```
237 |
238 | ### Get Available Disk Space
239 | ```swift
240 | if Device.volumeAvailableCapacityForOpportunisticUsage ?? 0 > Int64(1_000_000) {
241 | // download that nice-to-have huge file
242 | }
243 |
244 | if Device.volumeAvailableCapacityForImportantUsage ?? 0 > Int64(1_000) {
245 | // download that file you really need
246 | }
247 | ```
248 |
249 | ## Source of Information
250 | All model identifiers are taken from the following website: https://www.theiphonewiki.com/wiki/Models or extracted from the simulator app bundled with Xcode.
251 |
252 | ## Contributing
253 | If you have the need for a specific feature that you want implemented or if you experienced a bug, please open an issue.
254 | If you extended the functionality of DeviceKit yourself and want others to use it too, please submit a pull request.
255 |
256 | ## Contributors
257 | The complete list of people who contributed to this project is available [here](https://github.com/devicekit/DeviceKit/graphs/contributors). DeviceKit wouldn't be what it is without you! Thank you very much! 🙏
258 |
--------------------------------------------------------------------------------
/Scripts/push.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | source ~/.rvm/scripts/rvm
4 | rvm use default
5 | pod trunk push
6 |
--------------------------------------------------------------------------------
/Source/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | $(MARKETING_VERSION)
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Source/PrivacyInfo.xcprivacy:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSPrivacyTrackingDomains
6 |
7 | NSPrivacyCollectedDataTypes
8 |
9 | NSPrivacyAccessedAPITypes
10 |
11 |
12 | NSPrivacyAccessedAPITypeReasons
13 |
14 | 85F4.1
15 | E174.1
16 |
17 | NSPrivacyAccessedAPIType
18 | NSPrivacyAccessedAPICategoryDiskSpace
19 |
20 |
21 | NSPrivacyTracking
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Tests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Tests/Tests.swift:
--------------------------------------------------------------------------------
1 | //===----------------------------------------------------------------------===//
2 | //
3 | // This source file is part of the DeviceKit open source project
4 | //
5 | // Copyright © 2014 - 2018 Dennis Weissmann and the DeviceKit project authors
6 | //
7 | // License: https://github.com/dennisweissmann/DeviceKit/blob/master/LICENSE
8 | // Contributors: https://github.com/dennisweissmann/DeviceKit#contributors
9 | //
10 | //===----------------------------------------------------------------------===//
11 |
12 | @testable import DeviceKit
13 | import XCTest
14 |
15 | class DeviceKitTests: XCTestCase {
16 |
17 | let device = Device.current
18 |
19 | func testDeviceSimulator() {
20 | #if os(macOS)
21 | XCTAssertFalse(device.isOneOf(Device.allSimulators))
22 | #else
23 | XCTAssertTrue(device.isOneOf(Device.allSimulators))
24 | #endif
25 | }
26 |
27 | func testIsSimulator() {
28 | #if os(macOS)
29 | XCTAssertFalse(device.isSimulator)
30 | #else
31 | XCTAssertTrue(device.isSimulator)
32 | #endif
33 | }
34 |
35 | func testDeviceDescription() {
36 | #if os(macOS)
37 | #else
38 | XCTAssertTrue(device.description.hasPrefix("Simulator"))
39 | XCTAssertTrue(device.description.contains("iPhone")
40 | || device.description.contains("iPad")
41 | || device.description.contains("iPod")
42 | || device.description.contains("TV")
43 | || device.description.contains("Apple Watch"))
44 | #endif
45 | }
46 |
47 | func testIsCanvas() {
48 | #if os(iOS)
49 | let otherDevice: Device = device == .iPhone14Pro ? .iPhone14ProMax : .iPhone14Pro
50 | #elseif os(tvOS)
51 | let otherDevice: Device = device == .appleTVHD ? .appleTV4K : .appleTVHD
52 | #elseif os(watchOS)
53 | let otherDevice: Device = device == .appleWatchUltra ? .appleWatchSeries8_41mm : .appleWatchUltra
54 | #else
55 | let otherDevice: Device = .unknown("mac")
56 | #endif
57 | XCTAssertEqual(otherDevice.isCanvas, nil)
58 | XCTAssertEqual(device.isCanvas, false)
59 | }
60 |
61 | #if os(iOS) || os(tvOS)
62 | func testDeviceCPU() {
63 | #if os(iOS)
64 | XCTAssertEqual(Device.iPhone12Mini.cpu, Device.CPU.a14Bionic)
65 | XCTAssertEqual(Device.iPhone12.cpu, Device.CPU.a14Bionic)
66 | XCTAssertEqual(Device.iPhone12Pro.cpu, Device.CPU.a14Bionic)
67 | XCTAssertEqual(Device.iPhone12ProMax.cpu, Device.CPU.a14Bionic)
68 | XCTAssertEqual(Device.iPhone13Mini.cpu, Device.CPU.a15Bionic)
69 | XCTAssertEqual(Device.iPhone13.cpu, Device.CPU.a15Bionic)
70 | XCTAssertEqual(Device.iPhone13Pro.cpu, Device.CPU.a15Bionic)
71 | XCTAssertEqual(Device.iPhone13ProMax.cpu, Device.CPU.a15Bionic)
72 | XCTAssertEqual(Device.iPhone14.cpu, Device.CPU.a15Bionic)
73 | XCTAssertEqual(Device.iPhone14Plus.cpu, Device.CPU.a15Bionic)
74 | XCTAssertEqual(Device.iPhone14Pro.cpu, Device.CPU.a16Bionic)
75 | XCTAssertEqual(Device.iPhone14ProMax.cpu, Device.CPU.a16Bionic)
76 |
77 | XCTAssertEqual(Device.iPad8.cpu, Device.CPU.a12Bionic)
78 | XCTAssertEqual(Device.iPad9.cpu, Device.CPU.a13Bionic)
79 | XCTAssertEqual(Device.iPad10.cpu, Device.CPU.a14Bionic)
80 | XCTAssertEqual(Device.iPadAir3.cpu, Device.CPU.a12Bionic)
81 | XCTAssertEqual(Device.iPadAir4.cpu, Device.CPU.a14Bionic)
82 | XCTAssertEqual(Device.iPadAir5.cpu, Device.CPU.m1)
83 | XCTAssertEqual(Device.iPadMini4.cpu, Device.CPU.a8)
84 | XCTAssertEqual(Device.iPadMini5.cpu, Device.CPU.a12Bionic)
85 | XCTAssertEqual(Device.iPadMini6.cpu, Device.CPU.a15Bionic)
86 | XCTAssertEqual(Device.iPadMiniA17Pro.cpu, Device.CPU.a17Pro)
87 | XCTAssertEqual(Device.iPadPro11Inch.cpu, Device.CPU.a12XBionic)
88 | XCTAssertEqual(Device.iPadPro11Inch2.cpu, Device.CPU.a12ZBionic)
89 | XCTAssertEqual(Device.iPadPro11Inch3.cpu, Device.CPU.m1)
90 | XCTAssertEqual(Device.iPadPro11Inch4.cpu, Device.CPU.m2)
91 | #elseif os(tvOS)
92 | XCTAssertEqual(Device.appleTVHD.cpu, Device.CPU.a8)
93 | XCTAssertEqual(Device.appleTV4K.cpu, Device.CPU.a10XFusion)
94 | XCTAssertEqual(Device.appleTV4K2.cpu, Device.CPU.a12Bionic)
95 | XCTAssertEqual(Device.appleTV4K3.cpu, Device.CPU.a15Bionic)
96 | #endif
97 | }
98 |
99 | func testCPUDescription() {
100 | XCTAssertEqual(Device.CPU.a15Bionic.description, "A15 Bionic")
101 | XCTAssertEqual(Device.CPU.a16Bionic.description, "A16 Bionic")
102 | XCTAssertEqual(Device.CPU.m1.description, "M1")
103 | XCTAssertEqual(Device.CPU.m2.description, "M2")
104 | }
105 | #endif
106 |
107 | // MARK: - iOS
108 | #if os(iOS)
109 |
110 | func testIsPhoneIsPadIsPod() {
111 | // Test for https://github.com/devicekit/DeviceKit/issues/165 to prevent it from happening in the future.
112 |
113 | if UIDevice.current.userInterfaceIdiom == .pad {
114 | XCTAssertTrue(device.isPad)
115 | XCTAssertFalse(device.isPhone)
116 | XCTAssertFalse(device.isPod)
117 | } else if UIDevice.current.userInterfaceIdiom == .phone {
118 | XCTAssertFalse(device.isPad)
119 | if device.description.contains("iPod") {
120 | XCTAssertFalse(device.isPhone)
121 | XCTAssertTrue(device.isPod)
122 | } else {
123 | XCTAssertTrue(device.isPhone)
124 | XCTAssertFalse(device.isPod)
125 | }
126 | }
127 |
128 | for pad in Device.allPads {
129 | XCTAssertTrue(pad.isPad)
130 | XCTAssertFalse(pad.isPhone)
131 | XCTAssertFalse(pad.isPod)
132 | }
133 | for phone in Device.allPhones {
134 | XCTAssertFalse(phone.isPad)
135 | XCTAssertTrue(phone.isPhone)
136 | XCTAssertFalse(phone.isPod)
137 | }
138 | for pod in Device.allPods {
139 | XCTAssertFalse(pod.isPad)
140 | XCTAssertFalse(pod.isPhone)
141 | XCTAssertTrue(pod.isPod)
142 | }
143 | }
144 |
145 | func testSystemName() {
146 | if UIDevice.current.userInterfaceIdiom == .pad {
147 | XCTAssertEqual(device.systemName, "iPadOS")
148 | } else if UIDevice.current.userInterfaceIdiom == .phone {
149 | XCTAssertEqual(device.systemName, "iOS")
150 | }
151 | }
152 |
153 | func testBattery() {
154 | XCTAssertTrue(Device.BatteryState.full > Device.BatteryState.charging(100))
155 | XCTAssertTrue(Device.BatteryState.charging(75) != Device.BatteryState.unplugged(75))
156 | XCTAssertTrue(Device.BatteryState.unplugged(2) > Device.BatteryState.charging(1))
157 | }
158 |
159 | func testMapFromIdentifier() { // swiftlint:disable:this function_body_length
160 | XCTAssertEqual(Device.mapToDevice(identifier: "iPod5,1"), .iPodTouch5)
161 | XCTAssertEqual(Device.mapToDevice(identifier: "iPod7,1"), .iPodTouch6)
162 | XCTAssertEqual(Device.mapToDevice(identifier: "iPhone3,1"), .iPhone4)
163 | XCTAssertEqual(Device.mapToDevice(identifier: "iPhone3,2"), .iPhone4)
164 | XCTAssertEqual(Device.mapToDevice(identifier: "iPhone3,3"), .iPhone4)
165 | XCTAssertEqual(Device.mapToDevice(identifier: "iPhone4,1"), .iPhone4s)
166 | XCTAssertEqual(Device.mapToDevice(identifier: "iPhone5,1"), .iPhone5)
167 | XCTAssertEqual(Device.mapToDevice(identifier: "iPhone5,2"), .iPhone5)
168 | XCTAssertEqual(Device.mapToDevice(identifier: "iPhone5,3"), .iPhone5c)
169 | XCTAssertEqual(Device.mapToDevice(identifier: "iPhone5,4"), .iPhone5c)
170 | XCTAssertEqual(Device.mapToDevice(identifier: "iPhone6,1"), .iPhone5s)
171 | XCTAssertEqual(Device.mapToDevice(identifier: "iPhone6,2"), .iPhone5s)
172 | XCTAssertEqual(Device.mapToDevice(identifier: "iPhone7,2"), .iPhone6)
173 | XCTAssertEqual(Device.mapToDevice(identifier: "iPhone7,1"), .iPhone6Plus)
174 | XCTAssertEqual(Device.mapToDevice(identifier: "iPhone8,1"), .iPhone6s)
175 | XCTAssertEqual(Device.mapToDevice(identifier: "iPhone8,2"), .iPhone6sPlus)
176 | XCTAssertEqual(Device.mapToDevice(identifier: "iPhone9,1"), .iPhone7)
177 | XCTAssertEqual(Device.mapToDevice(identifier: "iPhone9,3"), .iPhone7)
178 | XCTAssertEqual(Device.mapToDevice(identifier: "iPhone9,2"), .iPhone7Plus)
179 | XCTAssertEqual(Device.mapToDevice(identifier: "iPhone9,4"), .iPhone7Plus)
180 | XCTAssertEqual(Device.mapToDevice(identifier: "iPhone8,4"), .iPhoneSE)
181 | XCTAssertEqual(Device.mapToDevice(identifier: "iPhone10,1"), .iPhone8)
182 | XCTAssertEqual(Device.mapToDevice(identifier: "iPhone10,4"), .iPhone8)
183 | XCTAssertEqual(Device.mapToDevice(identifier: "iPhone10,2"), .iPhone8Plus)
184 | XCTAssertEqual(Device.mapToDevice(identifier: "iPhone10,5"), .iPhone8Plus)
185 | XCTAssertEqual(Device.mapToDevice(identifier: "iPhone10,3"), .iPhoneX)
186 | XCTAssertEqual(Device.mapToDevice(identifier: "iPhone10,6"), .iPhoneX)
187 | XCTAssertEqual(Device.mapToDevice(identifier: "iPhone11,2"), .iPhoneXS)
188 | XCTAssertEqual(Device.mapToDevice(identifier: "iPhone11,4"), .iPhoneXSMax)
189 | XCTAssertEqual(Device.mapToDevice(identifier: "iPhone11,6"), .iPhoneXSMax)
190 | XCTAssertEqual(Device.mapToDevice(identifier: "iPhone11,8"), .iPhoneXR)
191 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad2,1"), .iPad2)
192 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad2,2"), .iPad2)
193 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad2,3"), .iPad2)
194 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad2,4"), .iPad2)
195 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad3,1"), .iPad3)
196 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad3,2"), .iPad3)
197 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad3,3"), .iPad3)
198 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad3,4"), .iPad4)
199 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad3,5"), .iPad4)
200 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad3,6"), .iPad4)
201 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad4,1"), .iPadAir)
202 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad4,2"), .iPadAir)
203 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad4,3"), .iPadAir)
204 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad5,3"), .iPadAir2)
205 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad5,4"), .iPadAir2)
206 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad6,11"), .iPad5)
207 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad6,12"), .iPad5)
208 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad2,5"), .iPadMini)
209 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad2,6"), .iPadMini)
210 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad2,7"), .iPadMini)
211 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad4,4"), .iPadMini2)
212 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad4,5"), .iPadMini2)
213 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad4,6"), .iPadMini2)
214 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad4,7"), .iPadMini3)
215 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad4,8"), .iPadMini3)
216 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad4,9"), .iPadMini3)
217 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad5,1"), .iPadMini4)
218 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad5,2"), .iPadMini4)
219 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad6,3"), .iPadPro9Inch)
220 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad6,4"), .iPadPro9Inch)
221 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad6,7"), .iPadPro12Inch)
222 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad6,8"), .iPadPro12Inch)
223 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad7,1"), .iPadPro12Inch2)
224 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad7,2"), .iPadPro12Inch2)
225 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad7,3"), .iPadPro10Inch)
226 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad7,4"), .iPadPro10Inch)
227 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad8,1"), .iPadPro11Inch)
228 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad8,2"), .iPadPro11Inch)
229 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad8,3"), .iPadPro11Inch)
230 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad8,4"), .iPadPro11Inch)
231 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad8,5"), .iPadPro12Inch3)
232 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad8,6"), .iPadPro12Inch3)
233 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad8,7"), .iPadPro12Inch3)
234 | XCTAssertEqual(Device.mapToDevice(identifier: "iPad8,8"), .iPadPro12Inch3)
235 | }
236 |
237 | func testScreenRatio() {
238 | XCTAssertTrue(Device.iPodTouch5.screenRatio == (width: 9, height: 16))
239 | XCTAssertTrue(Device.iPodTouch6.screenRatio == (width: 9, height: 16))
240 | XCTAssertTrue(Device.iPhone4.screenRatio == (width: 2, height: 3))
241 | XCTAssertTrue(Device.iPhone4s.screenRatio == (width: 2, height: 3))
242 | XCTAssertTrue(Device.iPhone5.screenRatio == (width: 9, height: 16))
243 | XCTAssertTrue(Device.iPhone5c.screenRatio == (width: 9, height: 16))
244 | XCTAssertTrue(Device.iPhone5s.screenRatio == (width: 9, height: 16))
245 | XCTAssertTrue(Device.iPhone6.screenRatio == (width: 9, height: 16))
246 | XCTAssertTrue(Device.iPhone6Plus.screenRatio == (width: 9, height: 16))
247 | XCTAssertTrue(Device.iPhone6s.screenRatio == (width: 9, height: 16))
248 | XCTAssertTrue(Device.iPhone6sPlus.screenRatio == (width: 9, height: 16))
249 | XCTAssertTrue(Device.iPhone7.screenRatio == (width: 9, height: 16))
250 | XCTAssertTrue(Device.iPhone7Plus.screenRatio == (width: 9, height: 16))
251 | XCTAssertTrue(Device.iPhoneSE.screenRatio == (width: 9, height: 16))
252 | XCTAssertTrue(Device.iPhoneSE2.screenRatio == (width: 9, height: 16))
253 | XCTAssertTrue(Device.iPhone8.screenRatio == (width: 9, height: 16))
254 | XCTAssertTrue(Device.iPhone8Plus.screenRatio == (width: 9, height: 16))
255 | XCTAssertTrue(Device.iPhoneX.screenRatio == (width: 9, height: 19.5))
256 | XCTAssertTrue(Device.iPhoneXS.screenRatio == (width: 9, height: 19.5))
257 | XCTAssertTrue(Device.iPhoneXSMax.screenRatio == (width: 9, height: 19.5))
258 | XCTAssertTrue(Device.iPhoneXR.screenRatio == (width: 9, height: 19.5))
259 | XCTAssertTrue(Device.iPad2.screenRatio == (width: 3, height: 4))
260 | XCTAssertTrue(Device.iPad3.screenRatio == (width: 3, height: 4))
261 | XCTAssertTrue(Device.iPad4.screenRatio == (width: 3, height: 4))
262 | XCTAssertTrue(Device.iPadAir.screenRatio == (width: 3, height: 4))
263 | XCTAssertTrue(Device.iPadAir2.screenRatio == (width: 3, height: 4))
264 | XCTAssertTrue(Device.iPad5.screenRatio == (width: 3, height: 4))
265 | XCTAssertTrue(Device.iPadMini.screenRatio == (width: 3, height: 4))
266 | XCTAssertTrue(Device.iPadMini2.screenRatio == (width: 3, height: 4))
267 | XCTAssertTrue(Device.iPadMini3.screenRatio == (width: 3, height: 4))
268 | XCTAssertTrue(Device.iPadMini4.screenRatio == (width: 3, height: 4))
269 | XCTAssertTrue(Device.iPadPro9Inch.screenRatio == (width: 3, height: 4))
270 | XCTAssertTrue(Device.iPadPro12Inch.screenRatio == (width: 3, height: 4))
271 | XCTAssertTrue(Device.iPadPro12Inch2.screenRatio == (width: 3, height: 4))
272 | XCTAssertTrue(Device.iPadPro10Inch.screenRatio == (width: 3, height: 4))
273 | XCTAssertTrue(Device.iPadPro11Inch.screenRatio == (width: 139, height: 199))
274 | XCTAssertTrue(Device.iPadPro12Inch3.screenRatio == (width: 512, height: 683))
275 |
276 | XCTAssertTrue(Device.simulator(device).screenRatio == device.screenRatio)
277 | XCTAssertTrue(Device.unknown(UUID().uuidString).screenRatio == (width: -1, height: -1))
278 | }
279 |
280 | func testDiagonal() {
281 | XCTAssertEqual(Device.iPhone4.diagonal, 3.5)
282 | XCTAssertEqual(Device.iPhone4s.diagonal, 3.5)
283 |
284 | XCTAssertEqual(Device.iPodTouch5.diagonal, 4)
285 | XCTAssertEqual(Device.iPodTouch6.diagonal, 4)
286 | XCTAssertEqual(Device.iPhone5.diagonal, 4)
287 | XCTAssertEqual(Device.iPhone5c.diagonal, 4)
288 | XCTAssertEqual(Device.iPhone5s.diagonal, 4)
289 | XCTAssertEqual(Device.iPhoneSE.diagonal, 4)
290 |
291 | XCTAssertEqual(Device.iPhone6.diagonal, 4.7)
292 | XCTAssertEqual(Device.iPhone6s.diagonal, 4.7)
293 | XCTAssertEqual(Device.iPhone7.diagonal, 4.7)
294 | XCTAssertEqual(Device.iPhone8.diagonal, 4.7)
295 |
296 | XCTAssertEqual(Device.iPhone6Plus.diagonal, 5.5)
297 | XCTAssertEqual(Device.iPhone6sPlus.diagonal, 5.5)
298 | XCTAssertEqual(Device.iPhone7Plus.diagonal, 5.5)
299 | XCTAssertEqual(Device.iPhone8Plus.diagonal, 5.5)
300 | XCTAssertEqual(Device.iPhoneX.diagonal, 5.8)
301 | XCTAssertEqual(Device.iPhoneXS.diagonal, 5.8)
302 | XCTAssertEqual(Device.iPhoneXSMax.diagonal, 6.5)
303 | XCTAssertEqual(Device.iPhoneXR.diagonal, 6.1)
304 |
305 | XCTAssertEqual(Device.iPad2.diagonal, 9.7)
306 | XCTAssertEqual(Device.iPad3.diagonal, 9.7)
307 | XCTAssertEqual(Device.iPad4.diagonal, 9.7)
308 | XCTAssertEqual(Device.iPadAir.diagonal, 9.7)
309 | XCTAssertEqual(Device.iPadAir2.diagonal, 9.7)
310 | XCTAssertEqual(Device.iPad5.diagonal, 9.7)
311 |
312 | XCTAssertEqual(Device.iPadMini.diagonal, 7.9)
313 | XCTAssertEqual(Device.iPadMini2.diagonal, 7.9)
314 | XCTAssertEqual(Device.iPadMini3.diagonal, 7.9)
315 | XCTAssertEqual(Device.iPadMini4.diagonal, 7.9)
316 |
317 | XCTAssertEqual(Device.iPadPro9Inch.diagonal, 9.7)
318 | XCTAssertEqual(Device.iPadPro12Inch.diagonal, 12.9)
319 | XCTAssertEqual(Device.iPadPro12Inch2.diagonal, 12.9)
320 | XCTAssertEqual(Device.iPadPro10Inch.diagonal, 10.5)
321 | XCTAssertEqual(Device.iPadPro11Inch.diagonal, 11.0)
322 | XCTAssertEqual(Device.iPadPro12Inch3.diagonal, 12.9)
323 |
324 | XCTAssertEqual(Device.simulator(.iPadPro10Inch).diagonal, 10.5)
325 | XCTAssertEqual(Device.unknown(UUID().uuidString).diagonal, -1)
326 | }
327 |
328 | func testDescription() { // swiftlint:disable:this function_body_length
329 | XCTAssertEqual(Device.iPodTouch5.description, "iPod touch (5th generation)")
330 | XCTAssertEqual(Device.iPodTouch6.description, "iPod touch (6th generation)")
331 | XCTAssertEqual(Device.iPodTouch7.description, "iPod touch (7th generation)")
332 | XCTAssertEqual(Device.iPhone4.description, "iPhone 4")
333 | XCTAssertEqual(Device.iPhone4s.description, "iPhone 4s")
334 | XCTAssertEqual(Device.iPhone5.description, "iPhone 5")
335 | XCTAssertEqual(Device.iPhone5c.description, "iPhone 5c")
336 | XCTAssertEqual(Device.iPhone5s.description, "iPhone 5s")
337 | XCTAssertEqual(Device.iPhone6.description, "iPhone 6")
338 | XCTAssertEqual(Device.iPhone6Plus.description, "iPhone 6 Plus")
339 | XCTAssertEqual(Device.iPhone6s.description, "iPhone 6s")
340 | XCTAssertEqual(Device.iPhone6sPlus.description, "iPhone 6s Plus")
341 | XCTAssertEqual(Device.iPhone7.description, "iPhone 7")
342 | XCTAssertEqual(Device.iPhone7Plus.description, "iPhone 7 Plus")
343 | XCTAssertEqual(Device.iPhoneSE.description, "iPhone SE")
344 | XCTAssertEqual(Device.iPhone8.description, "iPhone 8")
345 | XCTAssertEqual(Device.iPhone8Plus.description, "iPhone 8 Plus")
346 | XCTAssertEqual(Device.iPhoneX.description, "iPhone X")
347 | XCTAssertEqual(Device.iPhoneXS.description, "iPhone Xs")
348 | XCTAssertEqual(Device.iPhoneXSMax.description, "iPhone Xs Max")
349 | XCTAssertEqual(Device.iPhoneXR.description, "iPhone Xʀ")
350 | XCTAssertEqual(Device.iPad2.description, "iPad 2")
351 | XCTAssertEqual(Device.iPad3.description, "iPad (3rd generation)")
352 | XCTAssertEqual(Device.iPad4.description, "iPad (4th generation)")
353 | XCTAssertEqual(Device.iPadAir.description, "iPad Air")
354 | XCTAssertEqual(Device.iPadAir2.description, "iPad Air 2")
355 | XCTAssertEqual(Device.iPad5.description, "iPad (5th generation)")
356 | XCTAssertEqual(Device.iPad6.description, "iPad (6th generation)")
357 | XCTAssertEqual(Device.iPadAir3.description, "iPad Air (3rd generation)")
358 | XCTAssertEqual(Device.iPadMini.description, "iPad Mini")
359 | XCTAssertEqual(Device.iPadMini2.description, "iPad Mini 2")
360 | XCTAssertEqual(Device.iPadMini3.description, "iPad Mini 3")
361 | XCTAssertEqual(Device.iPadMini4.description, "iPad Mini 4")
362 | XCTAssertEqual(Device.iPadMini5.description, "iPad Mini (5th generation)")
363 | XCTAssertEqual(Device.iPadPro9Inch.description, "iPad Pro (9.7-inch)")
364 | XCTAssertEqual(Device.iPadPro12Inch.description, "iPad Pro (12.9-inch)")
365 | XCTAssertEqual(Device.iPadPro12Inch2.description, "iPad Pro (12.9-inch) (2nd generation)")
366 | XCTAssertEqual(Device.iPadPro10Inch.description, "iPad Pro (10.5-inch)")
367 | XCTAssertEqual(Device.iPadPro11Inch.description, "iPad Pro (11-inch)")
368 | XCTAssertEqual(Device.iPadPro12Inch3.description, "iPad Pro (12.9-inch) (3rd generation)")
369 | XCTAssertEqual(Device.simulator(Device.iPadPro10Inch).description, "Simulator (\(Device.iPadPro10Inch))")
370 | let uuid = UUID().uuidString
371 | XCTAssertEqual(Device.unknown(uuid).description, uuid)
372 | }
373 |
374 | func testSafeDescription() {
375 | for device in Device.allRealDevices {
376 | switch device {
377 | case .iPhoneXR, .iPhoneXS, .iPhoneXSMax:
378 | XCTAssertNotEqual(device.description, device.safeDescription)
379 | default:
380 | XCTAssertEqual(device.description, device.safeDescription)
381 | }
382 | }
383 | }
384 |
385 | func testIsPad() {
386 | Device.allPads.forEach { XCTAssertTrue($0.isPad) }
387 | }
388 |
389 | // Test that all the ppi values for applicable devices match the public information available at wikipedia.
390 | // Non-applicable devices return nil.
391 | func testPPI() {
392 | // swiftlint:disable comma
393 | assertEqualDeviceAndSimulator(device: Device.iPodTouch5, property: \Device.ppi, value: 326)
394 | assertEqualDeviceAndSimulator(device: Device.iPodTouch6, property: \Device.ppi, value: 326)
395 |
396 | assertEqualDeviceAndSimulator(device: Device.iPhone4, property: \Device.ppi, value: 326)
397 | assertEqualDeviceAndSimulator(device: Device.iPhone4s, property: \Device.ppi, value: 326)
398 | assertEqualDeviceAndSimulator(device: Device.iPhone5, property: \Device.ppi, value: 326)
399 | assertEqualDeviceAndSimulator(device: Device.iPhone5c, property: \Device.ppi, value: 326)
400 | assertEqualDeviceAndSimulator(device: Device.iPhone5s, property: \Device.ppi, value: 326)
401 | assertEqualDeviceAndSimulator(device: Device.iPhone6, property: \Device.ppi, value: 326)
402 | assertEqualDeviceAndSimulator(device: Device.iPhone6Plus, property: \Device.ppi, value: 401)
403 | assertEqualDeviceAndSimulator(device: Device.iPhone6s, property: \Device.ppi, value: 326)
404 | assertEqualDeviceAndSimulator(device: Device.iPhone6sPlus, property: \Device.ppi, value: 401)
405 | assertEqualDeviceAndSimulator(device: Device.iPhone7, property: \Device.ppi, value: 326)
406 | assertEqualDeviceAndSimulator(device: Device.iPhone7Plus, property: \Device.ppi, value: 401)
407 | assertEqualDeviceAndSimulator(device: Device.iPhoneSE, property: \Device.ppi, value: 326)
408 | assertEqualDeviceAndSimulator(device: Device.iPhoneSE2, property: \Device.ppi, value: 326)
409 | assertEqualDeviceAndSimulator(device: Device.iPhone8, property: \Device.ppi, value: 326)
410 | assertEqualDeviceAndSimulator(device: Device.iPhone8Plus, property: \Device.ppi, value: 401)
411 | assertEqualDeviceAndSimulator(device: Device.iPhoneX, property: \Device.ppi, value: 458)
412 | assertEqualDeviceAndSimulator(device: Device.iPhoneXR, property: \Device.ppi, value: 326)
413 | assertEqualDeviceAndSimulator(device: Device.iPhoneXS, property: \Device.ppi, value: 458)
414 | assertEqualDeviceAndSimulator(device: Device.iPhoneXSMax, property: \Device.ppi, value: 458)
415 |
416 | assertEqualDeviceAndSimulator(device: Device.iPad2, property: \Device.ppi, value: 132)
417 | assertEqualDeviceAndSimulator(device: Device.iPad3, property: \Device.ppi, value: 264)
418 | assertEqualDeviceAndSimulator(device: Device.iPad4, property: \Device.ppi, value: 264)
419 | assertEqualDeviceAndSimulator(device: Device.iPadAir, property: \Device.ppi, value: 264)
420 | assertEqualDeviceAndSimulator(device: Device.iPadAir2, property: \Device.ppi, value: 264)
421 | assertEqualDeviceAndSimulator(device: Device.iPad5, property: \Device.ppi, value: 264)
422 | assertEqualDeviceAndSimulator(device: Device.iPad6, property: \Device.ppi, value: 264)
423 | assertEqualDeviceAndSimulator(device: Device.iPadMini, property: \Device.ppi, value: 163)
424 | assertEqualDeviceAndSimulator(device: Device.iPadMini2, property: \Device.ppi, value: 326)
425 | assertEqualDeviceAndSimulator(device: Device.iPadMini3, property: \Device.ppi, value: 326)
426 | assertEqualDeviceAndSimulator(device: Device.iPadMini4, property: \Device.ppi, value: 326)
427 | assertEqualDeviceAndSimulator(device: Device.iPadPro9Inch, property: \Device.ppi, value: 264)
428 | assertEqualDeviceAndSimulator(device: Device.iPadPro12Inch, property: \Device.ppi, value: 264)
429 | assertEqualDeviceAndSimulator(device: Device.iPadPro12Inch2, property: \Device.ppi, value: 264)
430 | assertEqualDeviceAndSimulator(device: Device.iPadPro10Inch, property: \Device.ppi, value: 264)
431 | assertEqualDeviceAndSimulator(device: Device.iPadPro11Inch, property: \Device.ppi, value: 264)
432 | assertEqualDeviceAndSimulator(device: Device.iPadPro12Inch3, property: \Device.ppi, value: 264)
433 | // swiftlint:enable comma
434 | }
435 |
436 | func assertEqualDeviceAndSimulator(device: Device,
437 | property: KeyPath,
438 | value: Value,
439 | file: StaticString = #file,
440 | line: UInt = #line) where Value: Equatable {
441 | let simulator = Device.simulator(device)
442 | XCTAssertTrue(device[keyPath: property] == value, file: file, line: line)
443 | XCTAssertTrue(simulator[keyPath: property] == value, file: file, line: line)
444 | }
445 |
446 | func testIsPlusSized() {
447 | XCTAssertEqual(Device.allPlusSizedDevices, [
448 | .iPhone6Plus,
449 | .iPhone6sPlus,
450 | .iPhone7Plus,
451 | .iPhone8Plus,
452 | .iPhoneXSMax,
453 | .iPhone11ProMax,
454 | .iPhone12ProMax,
455 | .iPhone13ProMax,
456 | .iPhone14Plus,
457 | .iPhone14ProMax,
458 | .iPhone15Plus,
459 | .iPhone15ProMax,
460 | .iPhone16Plus,
461 | .iPhone16ProMax,
462 | ])
463 | }
464 |
465 | func testIsPro() {
466 | XCTAssertEqual(Device.allProDevices, [
467 | .iPhone11Pro,
468 | .iPhone11ProMax,
469 | .iPhone12Pro,
470 | .iPhone12ProMax,
471 | .iPhone13Pro,
472 | .iPhone13ProMax,
473 | .iPhone14Pro,
474 | .iPhone14ProMax,
475 | .iPhone15Pro,
476 | .iPhone15ProMax,
477 | .iPhone16Pro,
478 | .iPhone16ProMax,
479 | .iPadPro9Inch,
480 | .iPadPro12Inch,
481 | .iPadPro12Inch2,
482 | .iPadPro10Inch,
483 | .iPadPro11Inch,
484 | .iPadPro12Inch3,
485 | .iPadPro11Inch2,
486 | .iPadPro12Inch4,
487 | .iPadPro11Inch3,
488 | .iPadPro12Inch5,
489 | .iPadPro11Inch4,
490 | .iPadPro12Inch6,
491 | .iPadPro11M4,
492 | .iPadPro13M4,
493 | ])
494 | }
495 |
496 | func testGuidedAccessSession() {
497 | XCTAssertFalse(Device.current.isGuidedAccessSessionActive)
498 | }
499 |
500 | // enable once unit tests can be run on device
501 | func testKeepsBatteryMonitoringState() {
502 | UIDevice.current.isBatteryMonitoringEnabled = true
503 | XCTAssertTrue(UIDevice.current.isBatteryMonitoringEnabled)
504 | _ = Device.current.batteryState
505 | XCTAssertTrue(UIDevice.current.isBatteryMonitoringEnabled)
506 |
507 | UIDevice.current.isBatteryMonitoringEnabled = false
508 | _ = Device.current.batteryState
509 | XCTAssertFalse(UIDevice.current.isBatteryMonitoringEnabled)
510 | }
511 |
512 | func testHasDynamicIsland() {
513 | let dynamicIslandDevices: [Device] = [
514 | .iPhone14Pro,
515 | .iPhone14ProMax,
516 | .iPhone15,
517 | .iPhone15Plus,
518 | .iPhone15Pro,
519 | .iPhone15ProMax,
520 | .iPhone16,
521 | .iPhone16Plus,
522 | .iPhone16Pro,
523 | .iPhone16ProMax,
524 | ]
525 | for device in Device.allRealDevices {
526 | XCTAssertTrue(device.hasDynamicIsland == device.isOneOf(dynamicIslandDevices), "testHasDynamicIsland failed for \(device.description)")
527 | }
528 | }
529 |
530 | func testHas5gSupport() {
531 | let has5gDevices: [Device] = [
532 | .iPhone12,
533 | .iPhone12Mini,
534 | .iPhone12Pro,
535 | .iPhone12ProMax,
536 | .iPhone13,
537 | .iPhone13Mini,
538 | .iPhone13Pro,
539 | .iPhone13ProMax,
540 | .iPhoneSE3,
541 | .iPhone14,
542 | .iPhone14Plus,
543 | .iPhone14Pro,
544 | .iPhone14ProMax,
545 | .iPhone15,
546 | .iPhone15Plus,
547 | .iPhone15Pro,
548 | .iPhone15ProMax,
549 | .iPhone16,
550 | .iPhone16Plus,
551 | .iPhone16Pro,
552 | .iPhone16ProMax,
553 | .iPhone16e,
554 | .iPad10,
555 | .iPadA16,
556 | .iPadAir5,
557 | .iPadAir11M2,
558 | .iPadAir13M2,
559 | .iPadAir11M3,
560 | .iPadAir13M3,
561 | .iPadMini6,
562 | .iPadMiniA17Pro,
563 | .iPadPro11Inch3,
564 | .iPadPro12Inch5,
565 | .iPadPro11Inch4,
566 | .iPadPro12Inch6,
567 | .iPadPro11M4,
568 | .iPadPro13M4,
569 | ]
570 | for device in Device.allRealDevices {
571 | XCTAssertTrue(device.has5gSupport == device.isOneOf(has5gDevices), "testHas5gSupport failed for \(device.description)")
572 | }
573 | }
574 |
575 | // MARK: - volumes
576 | @available(iOS 11.0, *)
577 | func testVolumeTotalCapacity() {
578 | XCTAssertNotNil(Device.volumeTotalCapacity)
579 | }
580 |
581 | @available(iOS 11.0, *)
582 | func testVolumeAvailableCapacity() {
583 | XCTAssertNotNil(Device.volumeAvailableCapacity)
584 | }
585 |
586 | @available(iOS 11.0, *)
587 | func testVolumeAvailableCapacityForImportantUsage() {
588 | XCTAssertNotNil(Device.volumeAvailableCapacityForImportantUsage)
589 | }
590 |
591 | @available(iOS 11.0, *)
592 | func testVolumeAvailableCapacityForOpportunisticUsage() {
593 | XCTAssertNotNil(Device.volumeAvailableCapacityForOpportunisticUsage)
594 | }
595 |
596 | @available(iOS 11.0, *)
597 | func testVolumes() {
598 | XCTAssertNotNil(Device.volumes)
599 | }
600 |
601 | func testCameras() {
602 | for device in Device.allDevicesWithCamera {
603 | XCTAssertTrue(device.cameras.contains(.wide) || device.cameras.contains(.telephoto) || device.cameras.contains(.ultraWide))
604 | XCTAssertTrue(device.hasCamera)
605 | XCTAssertTrue(device.hasWideCamera || device.hasTelephotoCamera || device.hasUltraWideCamera)
606 | }
607 | for device in Device.allPhones + Device.allPads + Device.allPods {
608 | if !Device.allDevicesWithCamera.contains(device) {
609 | XCTAssertFalse(device.cameras.contains(.wide))
610 | XCTAssertFalse(device.cameras.contains(.telephoto))
611 | XCTAssertFalse(device.cameras.contains(.ultraWide))
612 | XCTAssertFalse(device.hasCamera)
613 | XCTAssertFalse(device.hasWideCamera)
614 | XCTAssertFalse(device.hasTelephotoCamera)
615 | XCTAssertFalse(device.hasUltraWideCamera)
616 | }
617 | }
618 | for device in Device.allDevicesWithWideCamera {
619 | XCTAssertTrue(device.cameras.contains(.wide))
620 | XCTAssertTrue(device.hasCamera)
621 | XCTAssertTrue(device.hasWideCamera)
622 | }
623 | for device in Device.allDevicesWithTelephotoCamera {
624 | XCTAssertTrue(device.cameras.contains(.telephoto))
625 | XCTAssertTrue(device.hasCamera)
626 | XCTAssertTrue(device.hasTelephotoCamera)
627 | }
628 | for device in Device.allDevicesWithUltraWideCamera {
629 | XCTAssertTrue(device.cameras.contains(.ultraWide))
630 | XCTAssertTrue(device.hasCamera)
631 | XCTAssertTrue(device.hasUltraWideCamera)
632 | }
633 | }
634 |
635 | func testLidarValues() {
636 | let lidarDevices: [Device] = [
637 | .iPhone12Pro,
638 | .iPhone12ProMax,
639 | .iPhone13Pro,
640 | .iPhone13ProMax,
641 | .iPhone14Pro,
642 | .iPhone14ProMax,
643 | .iPhone15Pro,
644 | .iPhone15ProMax,
645 | .iPhone16Pro,
646 | .iPhone16ProMax,
647 | .iPadPro11Inch2,
648 | .iPadPro12Inch4,
649 | .iPadPro11Inch3,
650 | .iPadPro12Inch5,
651 | .iPadPro11Inch4,
652 | .iPadPro12Inch6,
653 | .iPadPro11M4,
654 | .iPadPro13M4,
655 | ]
656 | for device in Device.allRealDevices {
657 | XCTAssertTrue(device.hasLidarSensor == device.isOneOf(lidarDevices), "testLidarValues failed for \(device.description)")
658 | }
659 | }
660 |
661 | func testHasUSBCConnectivity() {
662 | let usbCDevices: [Device] = [
663 | .iPhone15,
664 | .iPhone15Plus,
665 | .iPhone15Pro,
666 | .iPhone15ProMax,
667 | .iPhone16,
668 | .iPhone16Plus,
669 | .iPhone16Pro,
670 | .iPhone16ProMax,
671 | .iPhone16e,
672 | .iPad10,
673 | .iPadA16,
674 | .iPadAir4,
675 | .iPadAir5,
676 | .iPadAir11M2,
677 | .iPadAir13M2,
678 | .iPadAir11M3,
679 | .iPadAir13M3,
680 | .iPadMini6,
681 | .iPadMiniA17Pro,
682 | .iPadPro11Inch,
683 | .iPadPro12Inch3,
684 | .iPadPro11Inch2,
685 | .iPadPro12Inch4,
686 | .iPadPro11Inch3,
687 | .iPadPro12Inch5,
688 | .iPadPro11Inch4,
689 | .iPadPro12Inch6,
690 | .iPadPro11M4,
691 | .iPadPro13M4,
692 | ]
693 | for device in Device.allRealDevices {
694 | XCTAssertTrue(device.hasUSBCConnectivity == device.isOneOf(usbCDevices), "testHasUSBCConnectivity failed for \(device.description)")
695 | }
696 | }
697 |
698 | #endif
699 |
700 | // MARK: - tvOS
701 | #if os(tvOS)
702 |
703 | func testDescriptionFromIdentifier() {
704 | XCTAssertEqual(Device.mapToDevice(identifier: "AppleTV5,3").description, "Apple TV HD")
705 | XCTAssertEqual(Device.mapToDevice(identifier: "AppleTV6,2").description, "Apple TV 4K")
706 | }
707 |
708 | func testSafeDescription() {
709 | for device in Device.allRealDevices {
710 | XCTAssertEqual(device.description, device.safeDescription)
711 | }
712 | }
713 |
714 | /// Test that all the ppi values for applicable devices match the public information available at wikipedia. Test non-applicable devices return nil.
715 | func testPPI() {
716 | // Non-applicable devices:
717 | // Apple TV
718 | XCTAssertEqual(Device.appleTVHD.ppi, nil)
719 | XCTAssertEqual(Device.appleTV4K.ppi, nil)
720 | // Simulators
721 | XCTAssertEqual(Device.simulator(Device.appleTVHD).ppi, nil)
722 | XCTAssertEqual(Device.simulator(Device.appleTV4K).ppi, nil)
723 | }
724 |
725 | #endif
726 |
727 | }
728 |
--------------------------------------------------------------------------------
/Utils/gyb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | import gyb
3 | gyb.main()
4 |
--------------------------------------------------------------------------------
/Utils/gyb.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # GYB: Generate Your Boilerplate (improved names welcome; at least
3 | # this one's short). See -h output for instructions
4 |
5 | from __future__ import print_function
6 |
7 | import os
8 | import re
9 | try:
10 | from cStringIO import StringIO
11 | except ImportError:
12 | from io import StringIO
13 | import textwrap
14 | import tokenize
15 |
16 | from bisect import bisect
17 |
18 | try:
19 | basestring
20 | except NameError:
21 | basestring = str
22 |
23 |
24 | def get_line_starts(s):
25 | """Return a list containing the start index of each line in s.
26 |
27 | The list also contains a sentinel index for the end of the string,
28 | so there will be one more element in the list than there are lines
29 | in the string
30 | """
31 | starts = [0]
32 |
33 | for line in s.split('\n'):
34 | starts.append(starts[-1] + len(line) + 1)
35 |
36 | starts[-1] -= 1
37 | return starts
38 |
39 |
40 | def strip_trailing_nl(s):
41 | """If s ends with a newline, drop it; else return s intact"""
42 | return s[:-1] if s.endswith('\n') else s
43 |
44 |
45 | def split_lines(s):
46 | """Split s into a list of lines, each of which has a trailing newline
47 |
48 | If the lines are later concatenated, the result is s, possibly
49 | with a single appended newline.
50 | """
51 | return [l + '\n' for l in s.split('\n')]
52 |
53 |
54 | # text on a line up to the first '$$', '${', or '%%'
55 | literalText = r'(?: [^$\n%] | \$(?![${]) | %(?!%) )*'
56 |
57 | # The part of an '%end' line that follows the '%' sign
58 | linesClose = r'[\ \t]* end [\ \t]* (?: \# .* )? $'
59 |
60 | # Note: Where "# Absorb" appears below, the regexp attempts to eat up
61 | # through the end of ${...} and %{...}% constructs. In reality we
62 | # handle this with the Python tokenizer, which avoids mis-detections
63 | # due to nesting, comments and strings. This extra absorption in the
64 | # regexp facilitates testing the regexp on its own, by preventing the
65 | # interior of some of these constructs from being treated as literal
66 | # text.
67 | tokenize_re = re.compile(
68 | r'''
69 | # %-lines and %{...}-blocks
70 | # \n? # absorb one preceding newline
71 | ^
72 | (?:
73 | (?P
74 | (?P<_indent> [\ \t]* % (?! [{%] ) [\ \t]* ) (?! [\ \t] | ''' +
75 | linesClose + r''' ) .*
76 | ( \n (?P=_indent) (?! ''' + linesClose + r''' ) .* ) *
77 | )
78 | | (?P [\ \t]* % [ \t]* ''' + linesClose + r''' )
79 | | [\ \t]* (?P %\{ )
80 | (?: [^}]| \} (?!%) )* \}% # Absorb
81 | )
82 | \n? # absorb one trailing newline
83 |
84 | # Substitutions
85 | | (?P \$\{ )
86 | [^}]* \} # Absorb
87 |
88 | # %% and $$ are literal % and $ respectively
89 | | (?P[$%]) (?P=symbol)
90 |
91 | # Literal text
92 | | (?P ''' + literalText + r'''
93 | (?:
94 | # newline that doesn't precede space+%
95 | (?: \n (?! [\ \t]* %[^%] ) )
96 | ''' + literalText + r'''
97 | )*
98 | \n?
99 | )
100 | ''', re.VERBOSE | re.MULTILINE)
101 |
102 | gyb_block_close = re.compile('\}%[ \t]*\n?')
103 |
104 |
105 | def token_pos_to_index(token_pos, start, line_starts):
106 | """Translate a tokenize (line, column) pair into an absolute
107 | position in source text given the position where we started
108 | tokenizing and a list that maps lines onto their starting
109 | character indexes.
110 | """
111 | relative_token_line_plus1, token_col = token_pos
112 |
113 | # line number where we started tokenizing
114 | start_line_num = bisect(line_starts, start) - 1
115 |
116 | # line number of the token in the whole text
117 | abs_token_line = relative_token_line_plus1 - 1 + start_line_num
118 |
119 | # if found in the first line, adjust the end column to account
120 | # for the extra text
121 | if relative_token_line_plus1 == 1:
122 | token_col += start - line_starts[start_line_num]
123 |
124 | # Sometimes tokenizer errors report a line beyond the last one
125 | if abs_token_line >= len(line_starts):
126 | return line_starts[-1]
127 |
128 | return line_starts[abs_token_line] + token_col
129 |
130 |
131 | def tokenize_python_to_unmatched_close_curly(source_text, start, line_starts):
132 | """Apply Python's tokenize to source_text starting at index start
133 | while matching open and close curly braces. When an unmatched
134 | close curly brace is found, return its index. If not found,
135 | return len(source_text). If there's a tokenization error, return
136 | the position of the error.
137 | """
138 | stream = StringIO(source_text)
139 | stream.seek(start)
140 | nesting = 0
141 |
142 | try:
143 | for kind, text, token_start, token_end, line_text \
144 | in tokenize.generate_tokens(stream.readline):
145 |
146 | if text == '{':
147 | nesting += 1
148 | elif text == '}':
149 | nesting -= 1
150 | if nesting < 0:
151 | return token_pos_to_index(token_start, start, line_starts)
152 |
153 | except tokenize.TokenError as error:
154 | (message, error_pos) = error.args
155 | return token_pos_to_index(error_pos, start, line_starts)
156 |
157 | return len(source_text)
158 |
159 |
160 | def tokenize_template(template_text):
161 | r"""Given the text of a template, returns an iterator over
162 | (tokenType, token, match) tuples.
163 |
164 | **Note**: this is template syntax tokenization, not Python
165 | tokenization.
166 |
167 | When a non-literal token is matched, a client may call
168 | iter.send(pos) on the iterator to reset the position in
169 | template_text at which scanning will resume.
170 |
171 | This function provides a base level of tokenization which is
172 | then refined by ParseContext.token_generator.
173 |
174 | >>> from pprint import *
175 | >>> pprint(list((kind, text) for kind, text, _ in tokenize_template(
176 | ... '%for x in range(10):\n% print x\n%end\njuicebox')))
177 | [('gybLines', '%for x in range(10):\n% print x'),
178 | ('gybLinesClose', '%end'),
179 | ('literal', 'juicebox')]
180 |
181 | >>> pprint(list((kind, text) for kind, text, _ in tokenize_template(
182 | ... '''Nothing
183 | ... % if x:
184 | ... % for i in range(3):
185 | ... ${i}
186 | ... % end
187 | ... % else:
188 | ... THIS SHOULD NOT APPEAR IN THE OUTPUT
189 | ... ''')))
190 | [('literal', 'Nothing\n'),
191 | ('gybLines', '% if x:\n% for i in range(3):'),
192 | ('substitutionOpen', '${'),
193 | ('literal', '\n'),
194 | ('gybLinesClose', '% end'),
195 | ('gybLines', '% else:'),
196 | ('literal', 'THIS SHOULD NOT APPEAR IN THE OUTPUT\n')]
197 |
198 | >>> for kind, text, _ in tokenize_template('''
199 | ... This is $some$ literal stuff containing a ${substitution}
200 | ... followed by a %{...} block:
201 | ... %{
202 | ... # Python code
203 | ... }%
204 | ... and here $${are} some %-lines:
205 | ... % x = 1
206 | ... % y = 2
207 | ... % if z == 3:
208 | ... % print '${hello}'
209 | ... % end
210 | ... % for x in zz:
211 | ... % print x
212 | ... % # different indentation
213 | ... % twice
214 | ... and some lines that literally start with a %% token
215 | ... %% first line
216 | ... %% second line
217 | ... '''):
218 | ... print((kind, text.strip().split('\n',1)[0]))
219 | ('literal', 'This is $some$ literal stuff containing a')
220 | ('substitutionOpen', '${')
221 | ('literal', 'followed by a %{...} block:')
222 | ('gybBlockOpen', '%{')
223 | ('literal', 'and here ${are} some %-lines:')
224 | ('gybLines', '% x = 1')
225 | ('gybLinesClose', '% end')
226 | ('gybLines', '% for x in zz:')
227 | ('gybLines', '% # different indentation')
228 | ('gybLines', '% twice')
229 | ('literal', 'and some lines that literally start with a % token')
230 | """
231 | pos = 0
232 | end = len(template_text)
233 |
234 | saved_literal = []
235 | literal_first_match = None
236 |
237 | while pos < end:
238 | m = tokenize_re.match(template_text, pos, end)
239 |
240 | # pull out the one matched key (ignoring internal patterns starting
241 | # with _)
242 | ((kind, text), ) = (
243 | (kind, text) for (kind, text) in m.groupdict().items()
244 | if text is not None and kind[0] != '_')
245 |
246 | if kind in ('literal', 'symbol'):
247 | if len(saved_literal) == 0:
248 | literal_first_match = m
249 | # literals and symbols get batched together
250 | saved_literal.append(text)
251 | pos = None
252 | else:
253 | # found a non-literal. First yield any literal we've accumulated
254 | if saved_literal != []:
255 | yield 'literal', ''.join(saved_literal), literal_first_match
256 | saved_literal = []
257 |
258 | # Then yield the thing we found. If we get a reply, it's
259 | # the place to resume tokenizing
260 | pos = yield kind, text, m
261 |
262 | # If we were not sent a new position by our client, resume
263 | # tokenizing at the end of this match.
264 | if pos is None:
265 | pos = m.end(0)
266 | else:
267 | # Client is not yet ready to process next token
268 | yield
269 |
270 | if saved_literal != []:
271 | yield 'literal', ''.join(saved_literal), literal_first_match
272 |
273 |
274 | def split_gyb_lines(source_lines):
275 | r"""Return a list of lines at which to split the incoming source
276 |
277 | These positions represent the beginnings of python line groups that
278 | will require a matching %end construct if they are to be closed.
279 |
280 | >>> src = split_lines('''\
281 | ... if x:
282 | ... print x
283 | ... if y: # trailing comment
284 | ... print z
285 | ... if z: # another comment\
286 | ... ''')
287 | >>> s = split_gyb_lines(src)
288 | >>> len(s)
289 | 2
290 | >>> src[s[0]]
291 | ' print z\n'
292 | >>> s[1] - len(src)
293 | 0
294 |
295 | >>> src = split_lines('''\
296 | ... if x:
297 | ... if y: print 1
298 | ... if z:
299 | ... print 2
300 | ... pass\
301 | ... ''')
302 | >>> s = split_gyb_lines(src)
303 | >>> len(s)
304 | 1
305 | >>> src[s[0]]
306 | ' if y: print 1\n'
307 |
308 | >>> src = split_lines('''\
309 | ... if x:
310 | ... if y:
311 | ... print 1
312 | ... print 2
313 | ... ''')
314 | >>> s = split_gyb_lines(src)
315 | >>> len(s)
316 | 2
317 | >>> src[s[0]]
318 | ' if y:\n'
319 | >>> src[s[1]]
320 | ' print 1\n'
321 | """
322 | last_token_text, last_token_kind = None, None
323 | unmatched_indents = []
324 |
325 | dedents = 0
326 | try:
327 | for token_kind, token_text, token_start, \
328 | (token_end_line, token_end_col), line_text \
329 | in tokenize.generate_tokens(lambda i=iter(source_lines):
330 | next(i)):
331 |
332 | if token_kind in (tokenize.COMMENT, tokenize.ENDMARKER):
333 | continue
334 |
335 | if token_text == '\n' and last_token_text == ':':
336 | unmatched_indents.append(token_end_line)
337 |
338 | # The tokenizer appends dedents at EOF; don't consider
339 | # those as matching indentations. Instead just save them
340 | # up...
341 | if last_token_kind == tokenize.DEDENT:
342 | dedents += 1
343 | # And count them later, when we see something real.
344 | if token_kind != tokenize.DEDENT and dedents > 0:
345 | unmatched_indents = unmatched_indents[:-dedents]
346 | dedents = 0
347 |
348 | last_token_text, last_token_kind = token_text, token_kind
349 |
350 | except tokenize.TokenError:
351 | # Let the later compile() call report the error
352 | return []
353 |
354 | if last_token_text == ':':
355 | unmatched_indents.append(len(source_lines))
356 |
357 | return unmatched_indents
358 |
359 |
360 | def code_starts_with_dedent_keyword(source_lines):
361 | r"""Return True iff the incoming Python source_lines begin with "else",
362 | "elif", "except", or "finally".
363 |
364 | Initial comments and whitespace are ignored.
365 |
366 | >>> code_starts_with_dedent_keyword(split_lines('if x in y: pass'))
367 | False
368 | >>> code_starts_with_dedent_keyword(split_lines('except ifSomethingElse:'))
369 | True
370 | >>> code_starts_with_dedent_keyword(
371 | ... split_lines('\n# comment\nelse: # yes'))
372 | True
373 | """
374 | token_text = None
375 | for token_kind, token_text, _, _, _ \
376 | in tokenize.generate_tokens(lambda i=iter(source_lines): next(i)):
377 |
378 | if token_kind != tokenize.COMMENT and token_text.strip() != '':
379 | break
380 |
381 | return token_text in ('else', 'elif', 'except', 'finally')
382 |
383 |
384 | class ParseContext(object):
385 |
386 | """State carried through a parse of a template"""
387 |
388 | filename = ''
389 | template = ''
390 | line_starts = []
391 | code_start_line = -1
392 | code_text = None
393 | tokens = None # The rest of the tokens
394 | close_lines = False
395 |
396 | def __init__(self, filename, template=None):
397 | self.filename = os.path.abspath(filename)
398 | if template is None:
399 | with open(filename) as f:
400 | self.template = f.read()
401 | else:
402 | self.template = template
403 | self.line_starts = get_line_starts(self.template)
404 | self.tokens = self.token_generator(tokenize_template(self.template))
405 | self.next_token()
406 |
407 | def pos_to_line(self, pos):
408 | return bisect(self.line_starts, pos) - 1
409 |
410 | def token_generator(self, base_tokens):
411 | r"""Given an iterator over (kind, text, match) triples (see
412 | tokenize_template above), return a refined iterator over
413 | token_kinds.
414 |
415 | Among other adjustments to the elements found by base_tokens,
416 | this refined iterator tokenizes python code embedded in
417 | template text to help determine its true extent. The
418 | expression "base_tokens.send(pos)" is used to reset the index at
419 | which base_tokens resumes scanning the underlying text.
420 |
421 | >>> ctx = ParseContext('dummy', '''
422 | ... %for x in y:
423 | ... % print x
424 | ... % end
425 | ... literally
426 | ... ''')
427 | >>> while ctx.token_kind:
428 | ... print((ctx.token_kind, ctx.code_text or ctx.token_text))
429 | ... ignored = ctx.next_token()
430 | ('literal', '\n')
431 | ('gybLinesOpen', 'for x in y:\n')
432 | ('gybLines', ' print x\n')
433 | ('gybLinesClose', '% end')
434 | ('literal', 'literally\n')
435 |
436 | >>> ctx = ParseContext('dummy',
437 | ... '''Nothing
438 | ... % if x:
439 | ... % for i in range(3):
440 | ... ${i}
441 | ... % end
442 | ... % else:
443 | ... THIS SHOULD NOT APPEAR IN THE OUTPUT
444 | ... ''')
445 | >>> while ctx.token_kind:
446 | ... print((ctx.token_kind, ctx.code_text or ctx.token_text))
447 | ... ignored = ctx.next_token()
448 | ('literal', 'Nothing\n')
449 | ('gybLinesOpen', 'if x:\n')
450 | ('gybLinesOpen', ' for i in range(3):\n')
451 | ('substitutionOpen', 'i')
452 | ('literal', '\n')
453 | ('gybLinesClose', '% end')
454 | ('gybLinesOpen', 'else:\n')
455 | ('literal', 'THIS SHOULD NOT APPEAR IN THE OUTPUT\n')
456 |
457 | >>> ctx = ParseContext('dummy',
458 | ... '''% for x in [1, 2, 3]:
459 | ... % if x == 1:
460 | ... literal1
461 | ... % elif x > 1: # add output line here to fix bug
462 | ... % if x == 2:
463 | ... literal2
464 | ... % end
465 | ... % end
466 | ... % end
467 | ... ''')
468 | >>> while ctx.token_kind:
469 | ... print((ctx.token_kind, ctx.code_text or ctx.token_text))
470 | ... ignored = ctx.next_token()
471 | ('gybLinesOpen', 'for x in [1, 2, 3]:\n')
472 | ('gybLinesOpen', ' if x == 1:\n')
473 | ('literal', 'literal1\n')
474 | ('gybLinesOpen', 'elif x > 1: # add output line here to fix bug\n')
475 | ('gybLinesOpen', ' if x == 2:\n')
476 | ('literal', 'literal2\n')
477 | ('gybLinesClose', '% end')
478 | ('gybLinesClose', '% end')
479 | ('gybLinesClose', '% end')
480 | """
481 | for self.token_kind, self.token_text, self.token_match in base_tokens:
482 | kind = self.token_kind
483 | self.code_text = None
484 |
485 | # Do we need to close the current lines?
486 | self.close_lines = kind == 'gybLinesClose'
487 |
488 | # %{...}% and ${...} constructs
489 | if kind.endswith('Open'):
490 |
491 | # Tokenize text that follows as Python up to an unmatched '}'
492 | code_start = self.token_match.end(kind)
493 | self.code_start_line = self.pos_to_line(code_start)
494 |
495 | close_pos = tokenize_python_to_unmatched_close_curly(
496 | self.template, code_start, self.line_starts)
497 | self.code_text = self.template[code_start:close_pos]
498 | yield kind
499 |
500 | if (kind == 'gybBlockOpen'):
501 | # Absorb any '}% \n'
502 | m2 = gyb_block_close.match(self.template, close_pos)
503 | if not m2:
504 | raise ValueError("Invalid block closure")
505 | next_pos = m2.end(0)
506 | else:
507 | assert kind == 'substitutionOpen'
508 | # skip past the closing '}'
509 | next_pos = close_pos + 1
510 |
511 | # Resume tokenizing after the end of the code.
512 | base_tokens.send(next_pos)
513 |
514 | elif kind == 'gybLines':
515 |
516 | self.code_start_line = self.pos_to_line(
517 | self.token_match.start('gybLines'))
518 | indentation = self.token_match.group('_indent')
519 |
520 | # Strip off the leading indentation and %-sign
521 | source_lines = re.split(
522 | '^' + re.escape(indentation),
523 | self.token_match.group('gybLines') + '\n',
524 | flags=re.MULTILINE)[1:]
525 |
526 | if code_starts_with_dedent_keyword(source_lines):
527 | self.close_lines = True
528 |
529 | last_split = 0
530 | for line in split_gyb_lines(source_lines):
531 | self.token_kind = 'gybLinesOpen'
532 | self.code_text = ''.join(source_lines[last_split:line])
533 | yield self.token_kind
534 | last_split = line
535 | self.code_start_line += line - last_split
536 | self.close_lines = False
537 |
538 | self.code_text = ''.join(source_lines[last_split:])
539 | if self.code_text:
540 | self.token_kind = 'gybLines'
541 | yield self.token_kind
542 | else:
543 | yield self.token_kind
544 |
545 | def next_token(self):
546 | """Move to the next token"""
547 | for kind in self.tokens:
548 | return self.token_kind
549 |
550 | self.token_kind = None
551 |
552 |
553 | _default_line_directive = '// ###sourceLocation'
554 |
555 |
556 | class ExecutionContext(object):
557 |
558 | """State we pass around during execution of a template"""
559 |
560 | def __init__(self, line_directive=_default_line_directive,
561 | **local_bindings):
562 | self.local_bindings = local_bindings
563 | self.line_directive = line_directive
564 | self.local_bindings['__context__'] = self
565 | self.result_text = []
566 | self.last_file_line = None
567 |
568 | def append_text(self, text, file, line):
569 | # see if we need to inject a line marker
570 | if self.line_directive:
571 | if (file, line) != self.last_file_line:
572 | # We can only insert the line directive at a line break
573 | if len(self.result_text) == 0 \
574 | or self.result_text[-1].endswith('\n'):
575 | self.result_text.append('%s(file: "%s", line: %d)\n' % (
576 | self.line_directive, file, line + 1))
577 | # But if the new text contains any line breaks, we can create
578 | # one
579 | elif '\n' in text:
580 | i = text.find('\n')
581 | self.result_text.append(text[:i + 1])
582 | # and try again
583 | self.append_text(text[i + 1:], file, line + 1)
584 | return
585 |
586 | self.result_text.append(text)
587 | self.last_file_line = (file, line + text.count('\n'))
588 |
589 |
590 | class ASTNode(object):
591 |
592 | """Abstract base class for template AST nodes"""
593 |
594 | def __init__(self):
595 | raise NotImplementedError("ASTNode.__init__ is not implemented.")
596 |
597 | def execute(self, context):
598 | raise NotImplementedError("ASTNode.execute is not implemented.")
599 |
600 | def __str__(self, indent=''):
601 | raise NotImplementedError("ASTNode.__str__ is not implemented.")
602 |
603 | def format_children(self, indent):
604 | if not self.children:
605 | return ' []'
606 |
607 | return '\n'.join(
608 | ['', indent + '['] +
609 | [x.__str__(indent + 4 * ' ') for x in self.children] +
610 | [indent + ']'])
611 |
612 |
613 | class Block(ASTNode):
614 |
615 | """A sequence of other AST nodes, to be executed in order"""
616 |
617 | children = []
618 |
619 | def __init__(self, context):
620 | self.children = []
621 |
622 | while context.token_kind and not context.close_lines:
623 | if context.token_kind == 'literal':
624 | node = Literal
625 | else:
626 | node = Code
627 | self.children.append(node(context))
628 |
629 | def execute(self, context):
630 | for x in self.children:
631 | x.execute(context)
632 |
633 | def __str__(self, indent=''):
634 | return indent + 'Block:' + self.format_children(indent)
635 |
636 |
637 | class Literal(ASTNode):
638 |
639 | """An AST node that generates literal text"""
640 |
641 | def __init__(self, context):
642 | self.text = context.token_text
643 | start_position = context.token_match.start(context.token_kind)
644 | self.start_line_number = context.pos_to_line(start_position)
645 | self.filename = context.filename
646 | context.next_token()
647 |
648 | def execute(self, context):
649 | context.append_text(self.text, self.filename, self.start_line_number)
650 |
651 | def __str__(self, indent=''):
652 | return '\n'.join(
653 | [indent + x for x in ['Literal:'] +
654 | strip_trailing_nl(self.text).split('\n')])
655 |
656 |
657 | class Code(ASTNode):
658 |
659 | """An AST node that is evaluated as Python"""
660 |
661 | code = None
662 | children = ()
663 | kind = None
664 |
665 | def __init__(self, context):
666 |
667 | source = ''
668 | source_line_count = 0
669 |
670 | def accumulate_code():
671 | s = source + (context.code_start_line - source_line_count) * '\n' \
672 | + textwrap.dedent(context.code_text)
673 | line_count = context.code_start_line + \
674 | context.code_text.count('\n')
675 | context.next_token()
676 | return s, line_count
677 |
678 | eval_exec = 'exec'
679 | if context.token_kind.startswith('substitution'):
680 | eval_exec = 'eval'
681 | source, source_line_count = accumulate_code()
682 | source = '(' + source.strip() + ')'
683 |
684 | else:
685 | while context.token_kind == 'gybLinesOpen':
686 | source, source_line_count = accumulate_code()
687 | source += ' __children__[%d].execute(__context__)\n' % len(
688 | self.children)
689 | source_line_count += 1
690 |
691 | self.children += (Block(context),)
692 |
693 | if context.token_kind == 'gybLinesClose':
694 | context.next_token()
695 |
696 | if context.token_kind == 'gybLines':
697 | source, source_line_count = accumulate_code()
698 |
699 | # Only handle a substitution as part of this code block if
700 | # we don't already have some %-lines.
701 | elif context.token_kind == 'gybBlockOpen':
702 |
703 | # Opening ${...} and %{...}% constructs
704 | source, source_line_count = accumulate_code()
705 |
706 | self.filename = context.filename
707 | self.start_line_number = context.code_start_line
708 | self.code = compile(source, context.filename, eval_exec)
709 | self.source = source
710 |
711 | def execute(self, context):
712 | # Save __children__ from the local bindings
713 | save_children = context.local_bindings.get('__children__')
714 | # Execute the code with our __children__ in scope
715 | context.local_bindings['__children__'] = self.children
716 | context.local_bindings['__file__'] = self.filename
717 | result = eval(self.code, context.local_bindings)
718 |
719 | if context.local_bindings['__children__'] is not self.children:
720 | raise ValueError("The code is not allowed to mutate __children__")
721 | # Restore the bindings
722 | context.local_bindings['__children__'] = save_children
723 |
724 | # If we got a result, the code was an expression, so append
725 | # its value
726 | if result is not None \
727 | or (isinstance(result, basestring) and result != ''):
728 | from numbers import Number, Integral
729 | result_string = None
730 | if isinstance(result, Number) and not isinstance(result, Integral):
731 | result_string = repr(result)
732 | else:
733 | result_string = str(result)
734 | context.append_text(
735 | result_string, self.filename, self.start_line_number)
736 |
737 | def __str__(self, indent=''):
738 | source_lines = re.sub(r'^\n', '', strip_trailing_nl(
739 | self.source), flags=re.MULTILINE).split('\n')
740 | if len(source_lines) == 1:
741 | s = indent + 'Code: {' + source_lines[0] + '}'
742 | else:
743 | s = indent + 'Code:\n' + indent + '{\n' + '\n'.join(
744 | indent + 4 * ' ' + l for l in source_lines
745 | ) + '\n' + indent + '}'
746 | return s + self.format_children(indent)
747 |
748 |
749 | def expand(filename, line_directive=_default_line_directive, **local_bindings):
750 | r"""Return the contents of the givepn template file, executed with the given
751 | local bindings.
752 |
753 | >>> from tempfile import NamedTemporaryFile
754 | >>> # On Windows, the name of a NamedTemporaryFile cannot be used to open
755 | >>> # the file for a second time if delete=True. Therefore, we have to
756 | >>> # manually handle closing and deleting this file to allow us to open
757 | >>> # the file by its name across all platforms.
758 | >>> f = NamedTemporaryFile(delete=False)
759 | >>> f.write(
760 | ... r'''---
761 | ... % for i in range(int(x)):
762 | ... a pox on ${i} for epoxy
763 | ... % end
764 | ... ${120 +
765 | ...
766 | ... 3}
767 | ... abc
768 | ... ${"w\nx\nX\ny"}
769 | ... z
770 | ... ''')
771 | >>> f.flush()
772 | >>> result = expand(
773 | ... f.name, line_directive='//#sourceLocation', x=2
774 | ... ).replace(
775 | ... '"%s"' % f.name, '"dummy.file"')
776 | >>> print(result, end='')
777 | //#sourceLocation(file: "dummy.file", line: 1)
778 | ---
779 | //#sourceLocation(file: "dummy.file", line: 3)
780 | a pox on 0 for epoxy
781 | //#sourceLocation(file: "dummy.file", line: 3)
782 | a pox on 1 for epoxy
783 | //#sourceLocation(file: "dummy.file", line: 5)
784 | 123
785 | //#sourceLocation(file: "dummy.file", line: 8)
786 | abc
787 | w
788 | x
789 | X
790 | y
791 | //#sourceLocation(file: "dummy.file", line: 10)
792 | z
793 | >>> f.close()
794 | >>> os.remove(f.name)
795 | """
796 | with open(filename) as f:
797 | t = parse_template(filename, f.read())
798 | d = os.getcwd()
799 | os.chdir(os.path.dirname(os.path.abspath(filename)))
800 | try:
801 | return execute_template(
802 | t, line_directive=line_directive, **local_bindings)
803 | finally:
804 | os.chdir(d)
805 |
806 |
807 | def parse_template(filename, text=None):
808 | r"""Return an AST corresponding to the given template file.
809 |
810 | If text is supplied, it is assumed to be the contents of the file,
811 | as a string.
812 |
813 | >>> print(parse_template('dummy.file', text=
814 | ... '''% for x in [1, 2, 3]:
815 | ... % if x == 1:
816 | ... literal1
817 | ... % elif x > 1: # add output line after this line to fix bug
818 | ... % if x == 2:
819 | ... literal2
820 | ... % end
821 | ... % end
822 | ... % end
823 | ... '''))
824 | Block:
825 | [
826 | Code:
827 | {
828 | for x in [1, 2, 3]:
829 | __children__[0].execute(__context__)
830 | }
831 | [
832 | Block:
833 | [
834 | Code:
835 | {
836 | if x == 1:
837 | __children__[0].execute(__context__)
838 | elif x > 1: # add output line after this line to fix bug
839 | __children__[1].execute(__context__)
840 | }
841 | [
842 | Block:
843 | [
844 | Literal:
845 | literal1
846 | ]
847 | Block:
848 | [
849 | Code:
850 | {
851 | if x == 2:
852 | __children__[0].execute(__context__)
853 | }
854 | [
855 | Block:
856 | [
857 | Literal:
858 | literal2
859 | ]
860 | ]
861 | ]
862 | ]
863 | ]
864 | ]
865 | ]
866 |
867 | >>> print(parse_template(
868 | ... 'dummy.file',
869 | ... text='%for x in range(10):\n% print(x)\n%end\njuicebox'))
870 | Block:
871 | [
872 | Code:
873 | {
874 | for x in range(10):
875 | __children__[0].execute(__context__)
876 | }
877 | [
878 | Block:
879 | [
880 | Code: {print(x)} []
881 | ]
882 | ]
883 | Literal:
884 | juicebox
885 | ]
886 |
887 | >>> print(parse_template('/dummy.file', text=
888 | ... '''Nothing
889 | ... % if x:
890 | ... % for i in range(3):
891 | ... ${i}
892 | ... % end
893 | ... % else:
894 | ... THIS SHOULD NOT APPEAR IN THE OUTPUT
895 | ... '''))
896 | Block:
897 | [
898 | Literal:
899 | Nothing
900 | Code:
901 | {
902 | if x:
903 | __children__[0].execute(__context__)
904 | else:
905 | __children__[1].execute(__context__)
906 | }
907 | [
908 | Block:
909 | [
910 | Code:
911 | {
912 | for i in range(3):
913 | __children__[0].execute(__context__)
914 | }
915 | [
916 | Block:
917 | [
918 | Code: {(i)} []
919 | Literal:
920 |
921 | ]
922 | ]
923 | ]
924 | Block:
925 | [
926 | Literal:
927 | THIS SHOULD NOT APPEAR IN THE OUTPUT
928 | ]
929 | ]
930 | ]
931 |
932 | >>> print(parse_template('dummy.file', text='''%
933 | ... %for x in y:
934 | ... % print(y)
935 | ... '''))
936 | Block:
937 | [
938 | Code:
939 | {
940 | for x in y:
941 | __children__[0].execute(__context__)
942 | }
943 | [
944 | Block:
945 | [
946 | Code: {print(y)} []
947 | ]
948 | ]
949 | ]
950 |
951 | >>> print(parse_template('dummy.file', text='''%
952 | ... %if x:
953 | ... % print(y)
954 | ... AAAA
955 | ... %else:
956 | ... BBBB
957 | ... '''))
958 | Block:
959 | [
960 | Code:
961 | {
962 | if x:
963 | __children__[0].execute(__context__)
964 | else:
965 | __children__[1].execute(__context__)
966 | }
967 | [
968 | Block:
969 | [
970 | Code: {print(y)} []
971 | Literal:
972 | AAAA
973 | ]
974 | Block:
975 | [
976 | Literal:
977 | BBBB
978 | ]
979 | ]
980 | ]
981 |
982 | >>> print(parse_template('dummy.file', text='''%
983 | ... %if x:
984 | ... % print(y)
985 | ... AAAA
986 | ... %# This is a comment
987 | ... %else:
988 | ... BBBB
989 | ... '''))
990 | Block:
991 | [
992 | Code:
993 | {
994 | if x:
995 | __children__[0].execute(__context__)
996 | # This is a comment
997 | else:
998 | __children__[1].execute(__context__)
999 | }
1000 | [
1001 | Block:
1002 | [
1003 | Code: {print(y)} []
1004 | Literal:
1005 | AAAA
1006 | ]
1007 | Block:
1008 | [
1009 | Literal:
1010 | BBBB
1011 | ]
1012 | ]
1013 | ]
1014 |
1015 | >>> print(parse_template('dummy.file', text='''\
1016 | ... %for x in y:
1017 | ... AAAA
1018 | ... %if x:
1019 | ... BBBB
1020 | ... %end
1021 | ... CCCC
1022 | ... '''))
1023 | Block:
1024 | [
1025 | Code:
1026 | {
1027 | for x in y:
1028 | __children__[0].execute(__context__)
1029 | }
1030 | [
1031 | Block:
1032 | [
1033 | Literal:
1034 | AAAA
1035 | Code:
1036 | {
1037 | if x:
1038 | __children__[0].execute(__context__)
1039 | }
1040 | [
1041 | Block:
1042 | [
1043 | Literal:
1044 | BBBB
1045 | ]
1046 | ]
1047 | Literal:
1048 | CCCC
1049 | ]
1050 | ]
1051 | ]
1052 | """
1053 | return Block(ParseContext(filename, text))
1054 |
1055 |
1056 | def execute_template(
1057 | ast, line_directive=_default_line_directive, **local_bindings):
1058 | r"""Return the text generated by executing the given template AST.
1059 |
1060 | Keyword arguments become local variable bindings in the execution context
1061 |
1062 | >>> root_directory = os.path.abspath('/')
1063 | >>> file_name = root_directory + 'dummy.file'
1064 | >>> ast = parse_template(file_name, text=
1065 | ... '''Nothing
1066 | ... % if x:
1067 | ... % for i in range(3):
1068 | ... ${i}
1069 | ... % end
1070 | ... % else:
1071 | ... THIS SHOULD NOT APPEAR IN THE OUTPUT
1072 | ... ''')
1073 | >>> out = execute_template(ast, line_directive='//#sourceLocation', x=1)
1074 | >>> out = out.replace(file_name, "DUMMY-FILE")
1075 | >>> print(out, end="")
1076 | //#sourceLocation(file: "DUMMY-FILE", line: 1)
1077 | Nothing
1078 | //#sourceLocation(file: "DUMMY-FILE", line: 4)
1079 | 0
1080 | //#sourceLocation(file: "DUMMY-FILE", line: 4)
1081 | 1
1082 | //#sourceLocation(file: "DUMMY-FILE", line: 4)
1083 | 2
1084 |
1085 | >>> ast = parse_template(file_name, text=
1086 | ... '''Nothing
1087 | ... % a = []
1088 | ... % for x in range(3):
1089 | ... % a.append(x)
1090 | ... % end
1091 | ... ${a}
1092 | ... ''')
1093 | >>> out = execute_template(ast, line_directive='//#sourceLocation', x=1)
1094 | >>> out = out.replace(file_name, "DUMMY-FILE")
1095 | >>> print(out, end="")
1096 | //#sourceLocation(file: "DUMMY-FILE", line: 1)
1097 | Nothing
1098 | //#sourceLocation(file: "DUMMY-FILE", line: 6)
1099 | [0, 1, 2]
1100 | """
1101 | execution_context = ExecutionContext(
1102 | line_directive=line_directive, **local_bindings)
1103 | ast.execute(execution_context)
1104 | return ''.join(execution_context.result_text)
1105 |
1106 |
1107 | def main():
1108 | """
1109 | Lint this file.
1110 | >>> import sys
1111 | >>> gyb_path = os.path.realpath(__file__).replace('.pyc', '.py')
1112 | >>> sys.path.append(os.path.dirname(gyb_path))
1113 | >>> import python_lint
1114 | >>> python_lint.lint([gyb_path], verbose=False)
1115 | 0
1116 | """
1117 |
1118 | import argparse
1119 | import sys
1120 |
1121 | parser = argparse.ArgumentParser(
1122 | formatter_class=argparse.RawDescriptionHelpFormatter,
1123 | description='Generate Your Boilerplate!', epilog='''
1124 | A GYB template consists of the following elements:
1125 |
1126 | - Literal text which is inserted directly into the output
1127 |
1128 | - %% or $$ in literal text, which insert literal '%' and '$'
1129 | symbols respectively.
1130 |
1131 | - Substitutions of the form ${}. The Python
1132 | expression is converted to a string and the result is inserted
1133 | into the output.
1134 |
1135 | - Python code delimited by %{...}%. Typically used to inject
1136 | definitions (functions, classes, variable bindings) into the
1137 | evaluation context of the template. Common indentation is
1138 | stripped, so you can add as much indentation to the beginning
1139 | of this code as you like
1140 |
1141 | - Lines beginning with optional whitespace followed by a single
1142 | '%' and Python code. %-lines allow you to nest other
1143 | constructs inside them. To close a level of nesting, use the
1144 | "%end" construct.
1145 |
1146 | - Lines beginning with optional whitespace and followed by a
1147 | single '%' and the token "end", which close open constructs in
1148 | %-lines.
1149 |
1150 | Example template:
1151 |
1152 | - Hello -
1153 | %{
1154 | x = 42
1155 | def succ(a):
1156 | return a+1
1157 | }%
1158 |
1159 | I can assure you that ${x} < ${succ(x)}
1160 |
1161 | % if int(y) > 7:
1162 | % for i in range(3):
1163 | y is greater than seven!
1164 | % end
1165 | % else:
1166 | y is less than or equal to seven
1167 | % end
1168 |
1169 | - The End. -
1170 |
1171 | When run with "gyb -Dy=9", the output is
1172 |
1173 | - Hello -
1174 |
1175 | I can assure you that 42 < 43
1176 |
1177 | y is greater than seven!
1178 | y is greater than seven!
1179 | y is greater than seven!
1180 |
1181 | - The End. -
1182 | '''
1183 | )
1184 | parser.add_argument(
1185 | '-D', action='append', dest='defines', metavar='NAME=VALUE',
1186 | default=[],
1187 | help='''Bindings to be set in the template's execution context''')
1188 |
1189 | parser.add_argument(
1190 | 'file', type=argparse.FileType(),
1191 | help='Path to GYB template file (defaults to stdin)', nargs='?',
1192 | default=sys.stdin)
1193 | parser.add_argument(
1194 | '-o', dest='target', type=argparse.FileType('w'),
1195 | help='Output file (defaults to stdout)', default=sys.stdout)
1196 | parser.add_argument(
1197 | '--test', action='store_true',
1198 | default=False, help='Run a self-test')
1199 | parser.add_argument(
1200 | '--verbose-test', action='store_true',
1201 | default=False, help='Run a verbose self-test')
1202 | parser.add_argument(
1203 | '--dump', action='store_true',
1204 | default=False, help='Dump the parsed template to stdout')
1205 | parser.add_argument(
1206 | '--line-directive', default='// ###sourceLocation',
1207 | help='Line directive prefix; empty => no line markers')
1208 |
1209 | args = parser.parse_args(sys.argv[1:])
1210 |
1211 | if args.test or args.verbose_test:
1212 | import doctest
1213 | selfmod = sys.modules[__name__]
1214 | if doctest.testmod(selfmod, verbose=args.verbose_test or None).failed:
1215 | sys.exit(1)
1216 |
1217 | bindings = dict(x.split('=', 1) for x in args.defines)
1218 | ast = parse_template(args.file.name, args.file.read())
1219 | if args.dump:
1220 | print(ast)
1221 | # Allow the template to open files and import .py files relative to its own
1222 | # directory
1223 | os.chdir(os.path.dirname(os.path.abspath(args.file.name)))
1224 | sys.path = ['.'] + sys.path
1225 |
1226 | args.target.write(execute_template(ast, args.line_directive, **bindings))
1227 |
1228 |
1229 | if __name__ == '__main__':
1230 | main()
1231 |
--------------------------------------------------------------------------------