├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── CameraManager.podspec
├── CameraManager.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── xcshareddata
│ └── xcschemes
│ ├── CameraManager.xcscheme
│ └── camera.xcscheme
├── Example App
├── AppDelegate.swift
├── Base.lproj
│ ├── LaunchScreen.xib
│ └── Main.storyboard
├── ImageViewController.swift
├── Images.xcassets
│ ├── AppIcon.appiconset
│ │ ├── Contents.json
│ │ ├── Icon-App-20x20@1x.png
│ │ ├── Icon-App-20x20@2x.png
│ │ ├── Icon-App-20x20@3x.png
│ │ ├── Icon-App-29x29@1x.png
│ │ ├── Icon-App-29x29@2x.png
│ │ ├── Icon-App-29x29@3x.png
│ │ ├── Icon-App-40x40@1x.png
│ │ ├── Icon-App-40x40@2x.png
│ │ ├── Icon-App-40x40@3x.png
│ │ ├── Icon-App-57x57@1x.png
│ │ ├── Icon-App-57x57@2x.png
│ │ ├── Icon-App-60x60@2x.png
│ │ ├── Icon-App-60x60@3x.png
│ │ ├── Icon-App-72x72@1x.png
│ │ ├── Icon-App-72x72@2x.png
│ │ ├── Icon-App-76x76@1x.png
│ │ ├── Icon-App-76x76@2x.png
│ │ ├── Icon-App-83.5x83.5@2x.png
│ │ ├── Icon-Small-50x50@1x.png
│ │ ├── Icon-Small-50x50@2x.png
│ │ ├── copy.png
│ │ ├── imaginary copy 2-1.png
│ │ ├── imaginary copy 2-2.png
│ │ ├── imaginary copy 2-3.png
│ │ ├── imaginary copy 2-4.png
│ │ ├── imaginary copy 2.png
│ │ ├── imaginary copy-1.png
│ │ ├── imaginary copy.png
│ │ └── logo.jpeg
│ ├── Contents.json
│ ├── flash_auto.imageset
│ │ ├── Contents.json
│ │ └── flash-auto.png
│ ├── flash_off.imageset
│ │ ├── Contents.json
│ │ └── flash-off.png
│ ├── flash_on.imageset
│ │ ├── Contents.json
│ │ └── flash-on.png
│ ├── logo.imageset
│ │ ├── Contents.json
│ │ ├── imaginary-1.png
│ │ ├── imaginary-2.png
│ │ └── imaginary.png
│ ├── output_image.imageset
│ │ ├── Contents.json
│ │ └── output-image.png
│ ├── output_video.imageset
│ │ ├── Contents.json
│ │ └── icons8-video-call.png
│ └── switch_camera.imageset
│ │ ├── Contents.json
│ │ └── switch-camera.png
├── Info.plist
└── ViewController.swift
├── LICENSE
├── Package.swift
├── README.md
└── Sources
├── CameraManager.h
├── CameraManager.swift
└── Info.plist
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata
19 |
20 | ## Other
21 | *.xccheckout
22 | *.moved-aside
23 | *.xcuserstate
24 | *.xcscmblueprint
25 |
26 | ## Obj-C/Swift specific
27 | *.hmap
28 | *.ipa
29 |
30 | # CocoaPods
31 | #
32 | # We recommend against adding the Pods directory to your .gitignore. However
33 | # you should judge for yourself, the pros and cons are mentioned at:
34 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
35 | #
36 | # Pods/
37 |
38 | # Carthage
39 | #
40 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
41 | # Carthage/Checkouts
42 |
43 | Carthage/Build
44 | .DS_Store
45 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to this project will be documented in this file.
4 | This project adheres to [Semantic Versioning](http://semver.org/).
5 |
6 | ## [5.1.3](https://github.com/imaginary-cloud/CameraManager/tree/5.1.3) - 2020-04-20
7 |
8 | ### Fixed
9 |
10 | - Remove unnecessary calls to `_orientationChanged`
11 | - Change videoStabilisationMode in between recordings
12 | - [Change the main class to open to permit inheritance](https://github.com/imaginary-cloud/CameraManager/pull/225)
13 |
14 | ### Added
15 |
16 | - [Add property to get the stabilisation mode currently active](https://github.com/imaginary-cloud/CameraManager/issues/177)
17 |
18 | ## [5.1.2](https://github.com/imaginary-cloud/CameraManager/tree/5.1.2) - 2020-04-16
19 |
20 | ### Fixed
21 |
22 | - Fix location metadata and front image flipping when capturing a video
23 |
24 | ## [5.1.1](https://github.com/imaginary-cloud/CameraManager/tree/5.1.1) - 2020-04-15
25 |
26 | ### Fixed
27 |
28 | - Minor fixes when changing between outputs
29 | - Fix bug when changing exposure duration not calling `unlockForConfiguration`
30 |
31 | ## [5.1.0](https://github.com/imaginary-cloud/CameraManager/tree/5.1.0) - 2020-04-06
32 |
33 | ### Fixed
34 |
35 | - [`capturePictureWithCompletion` called twice when `writeFilesToPhoneLibrary = true`](https://github.com/imaginary-cloud/CameraManager/issues/187)
36 | - [Camera freeze caused by orientation changes](https://github.com/imaginary-cloud/CameraManager/pull/204)
37 | - [Location data not stored in EXIF data](https://github.com/imaginary-cloud/CameraManager/issues/213)
38 | - [Support flash mode selection on front camera](https://github.com/imaginary-cloud/CameraManager/pull/214)
39 |
40 | ### Added
41 |
42 | - [Set camera output quality using AVCaptureSession.Preset](https://github.com/imaginary-cloud/CameraManager/pull/195)
43 | - [Add support for scanning QR codes](https://github.com/imaginary-cloud/CameraManager/pull/199)
44 |
45 | ## [5.0.0](https://github.com/imaginary-cloud/CameraManager/tree/5.0.0) - 2020-04-01
46 |
47 | ### Changed
48 |
49 | - [Syntax update for Swift 5.2](https://github.com/imaginary-cloud/CameraManager/issues/189)
50 |
51 | ### Fixed
52 |
53 | - [Fix Swift Package Manager support](https://github.com/imaginary-cloud/CameraManager/issues/215)
54 |
55 | ## [4.4.0](https://github.com/imaginary-cloud/CameraManager/tree/4.4.0) - 2019-03-12
56 |
57 | ### Changed
58 |
59 | - Change callbacks to the swifty way of reporting success and errors (pull request #185)
60 |
61 | ## [4.3.1](https://github.com/imaginary-cloud/CameraManager/tree/4.3.1) - 2019-03-08
62 |
63 | ### Fixed
64 |
65 | - Swift Support (pull request #173 and #168)
66 | - Example App layout supports iPhone X (pull request #173)
67 |
68 | ## [4.3.0](https://github.com/imaginary-cloud/CameraManager/tree/4.3.0) - 2018-08-17
69 |
70 | ### Added
71 |
72 | - Add video stabilization mode (pull request #160)
73 |
74 | ## [4.2.3](https://github.com/imaginary-cloud/CameraManager/tree/4.2.3) - 2018-07-27
75 |
76 | ### Fixed
77 |
78 | - Deinit cause crash when `cameraDevice = .front` (#157)
79 |
80 | ### Changed
81 |
82 | - Add note to documentation when a property has a default value
83 |
84 | ## [4.2.2](https://github.com/imaginary-cloud/CameraManager/tree/4.2.2) - 2018-07-26
85 |
86 | ### Added
87 |
88 | - Add `CONTRIBUTING.md`
89 |
90 | ### Fixed
91 |
92 | - When recording is finished `videoURL` is always nil (#158)
93 |
94 | ## [4.2.1](https://github.com/imaginary-cloud/CameraManager/tree/4.2.1) - 2018-06-20
95 |
96 | ### Added
97 |
98 | - Add `CODE_OF_CONDUCT.md`
99 |
100 | ### Fixed
101 |
102 | - Fix flash for video (#152)
103 |
104 | ## [4.2.0](https://github.com/imaginary-cloud/CameraManager/tree/4.2.0) - 2018-06-19
105 |
106 | ### Added
107 |
108 | - Add properties for setting custom album names for image and video recordings (pull request #147)
109 | - Add Exposure slider (pull request #148)
110 |
111 | ### Fixed
112 |
113 | - Orientation bug (pull request #150)
114 | - Video orientation not set after camera switch (#64)
115 | - Memory leak in Example App
116 |
117 | ## [4.1.1](https://github.com/imaginary-cloud/CameraManager/tree/4.1.1) - 2018-05-16
118 |
119 | ### Fixed
120 |
121 | - License
122 |
123 | ## [4.1.0](https://github.com/imaginary-cloud/CameraManager/tree/4.1.0) - 2018-05-15
124 |
125 | ### Added
126 |
127 | - Add EXIF metadata for images and video - including location (#131)
128 | - New layout for Example App
129 |
130 | ### Fixed
131 |
132 | - Fix Crash (pull request #141)
133 | - Fix image orientation when taking pictures with device rotation lock active
134 |
135 | ## [4.0.2](https://github.com/imaginary-cloud/CameraManager/tree/4.0.2) - 2018-05-07
136 |
137 | ### Fixed
138 |
139 | - Using CoreMotion to get real device orientation, fixing no sound when switching camera (pull request #134)
140 | - Fixed take picture on iPad with front camera(landscape, UpsideDown… (pull request #139)
141 |
142 | - Bugs (issue #115)
143 | - Not able to to get sound while recording from front camera (#116)
144 | - Urgent: toggle camera back to front no sound coming in recorded video (issue #118)
145 | - Orientation issues (landscape) (issue #135)
146 | - Resuming capture session improvement (issue #136)
147 | - Issue with focus area (issue #137)
148 | - Repeat crash CameraManager.swift line 430 in release (issue #138)
149 |
150 | ## [4.0.1](https://github.com/imaginary-cloud/CameraManager/tree/4.0.1) - 2017-11-18
151 |
152 | ### Added
153 |
154 | - Add @discardableResult modifiers to addPreviewLayerToView (pull request #132)
155 |
156 | ### Fixed
157 |
158 | - Fix shouldEnableTapToFocus function (pull request #133)
159 |
160 | ## [4.0.0](https://github.com/imaginary-cloud/CameraManager/tree/4.0.0) - 2017-10-22
161 |
162 | ### Changed
163 |
164 | - Syntax update for Swift 4.0 (pull request #125)
165 |
166 | ### Fixed
167 |
168 | - Add gesture recognizers on the main thread (pull request #123)
169 |
170 | ## [3.2.0](https://github.com/imaginary-cloud/CameraManager/tree/3.2.0) - 2017-07-03
171 |
172 | ### Added
173 |
174 | - Add location data to videos (pull request #110)
175 | - Optional location permissions (pull request #110)
176 |
177 | ## [3.1.4](https://github.com/imaginary-cloud/CameraManager/tree/3.1.4) - 2017-06-14
178 |
179 | ### Added
180 |
181 | - Add properties `focusMode` and `exposureMode` (pull request #106)
182 | - Add property `animateShutter` to disable shutter animation
183 |
184 | ### Fixed
185 |
186 | - FlashMode on front camera (issue #82)
187 | - Zoom of front camera not working (issue #84)
188 | - Getting same video URL, when simultaneously recording video (issue #108)
189 |
190 | ## [3.1.3](https://github.com/imaginary-cloud/CameraManager/tree/3.1.3) - 2017-05-15
191 |
192 | ### Added
193 |
194 | - Add two new properties `shouldEnableTapToFocus` and `shouldEnablePinchToZoom` (pull request #106)
195 |
196 | ## [3.1.2](https://github.com/imaginary-cloud/CameraManager/tree/3.1.2) - 2017-05-02
197 |
198 | ### Changed
199 |
200 | - New option to flip image took by front camera (pull request #104)
201 | - Fixes possible hang after requesting permission (pull request #98)
202 |
203 | ## [3.1.1](https://github.com/imaginary-cloud/CameraManager/tree/3.1.1) - 2017-03-15
204 |
205 | ### Changed
206 |
207 | - Refactor to avoid implicit unwrapped optionals (pull request #94)
208 |
209 | ## [3.1.0](https://github.com/imaginary-cloud/CameraManager/tree/3.1.0) - 2017-02-11
210 |
211 | ### Added
212 |
213 | - Flip animation and tap to focus (pull request #72)
214 | - Icons and splash image to example
215 |
216 | ## [3.0.0](https://github.com/imaginary-cloud/CameraManager/tree/3.0.0) - 2016-09-16
217 |
218 | ### Changed
219 |
220 | - Syntax update for Swift 3.0.
221 |
222 | ## [2.2.4](https://github.com/imaginary-cloud/CameraManager/tree/2.2.4) - 2016-07-06
223 |
224 | ### Added
225 |
226 | - Add error checking.
227 |
228 | ### Changed
229 |
230 | - Fixes completion typos and suggests renamed functions.
231 |
232 | ## [2.2.3](https://github.com/imaginary-cloud/CameraManager/tree/2.2.3) - 2016-05-12
233 |
234 | ### Changed
235 |
236 | - Fixed zoom in StillImage Mode.
237 |
238 | - Minor refactoring
239 |
240 | ## [2.2.2](https://github.com/imaginary-cloud/CameraManager/tree/2.2.2) - 2016-03-07
241 |
242 | ### Added
243 |
244 | - `CHANGELOG.md` file.
245 |
246 | ## [2.2.1](https://github.com/imaginary-cloud/CameraManager/tree/2.2.1) - 2016-03-02
247 |
248 | ### Added
249 |
250 | - Initial support for the Swift Package Manager.
251 |
252 | ## [2.2.0](https://github.com/imaginary-cloud/CameraManager/tree/2.2.0) - 2016-02-19
253 |
254 | ### Added
255 |
256 | - Zoom support.
257 |
258 | ### Changed
259 |
260 | - Fixed spelling of `embeddingView`.
261 |
262 | ## [2.1.3](https://github.com/imaginary-cloud/CameraManager/tree/2.1.3) - 2016-01-08
263 |
264 | ### Changed
265 |
266 | - No sound in video with more than 10 seconds fixed.
267 |
268 | - Fixed `NewCameraOutputMode` not passed during init.
269 |
270 | ## [2.1.2](https://github.com/imaginary-cloud/CameraManager/tree/2.1.2) - 2015-12-24
271 |
272 | ### Added
273 |
274 | - Property `cameraIsReady`.
275 |
276 | - Completion block `addPreviewLayerToView`.
277 |
278 | ## [2.1.1](https://github.com/imaginary-cloud/CameraManager/tree/2.1.1) - 2015-12-11
279 |
280 | ### Added
281 |
282 | - Ability to disable responding to device orientation changes.
283 |
284 | ## [2.1.0](https://github.com/imaginary-cloud/CameraManager/tree/2.1) - 2015-11-20
285 |
286 | ### Added
287 |
288 | - Properties `recordedDuration` and `recordedFileSize`.
289 |
290 | ## [2.0.2](https://github.com/imaginary-cloud/CameraManager/tree/2.0.2) - 2015-11-17
291 |
292 | ### Fixed
293 |
294 | - iOS 9.0.1 bug.
295 |
296 | ## [2.0.1](https://github.com/imaginary-cloud/CameraManager/tree/2.0.1) - 2015-09-17
297 |
298 | ### Changed
299 |
300 | - Syntax updates.
301 |
302 | ## [2.0.0](https://github.com/imaginary-cloud/CameraManager/tree/2.0.0) - 2015-07-30
303 |
304 | ### Changed
305 |
306 | - Syntax update for Swift 2.0.
307 |
308 | ## [1.0.14](https://github.com/imaginary-cloud/CameraManager/tree/1.0.14) - 2015-07-17
309 |
310 | ### Changed
311 |
312 | - Small fixes.
313 |
314 | ## [1.0.13](https://github.com/imaginary-cloud/CameraManager/tree/1.0.13) - 2015-05-12
315 |
316 | ### Added
317 |
318 | - Support for installing via Carthage.
319 |
320 | - Property `hasFlash`.
321 |
322 | ### Changed
323 |
324 | - Syntax update for Swift 1.2.
325 |
326 | ## [1.0.12](https://github.com/imaginary-cloud/CameraManager/tree/1.0.12) - 2015-03-23
327 |
328 | ### Added
329 |
330 | - Incremental flash mode.
331 |
332 | - Content localization.
333 |
334 | ### Changed
335 |
336 | - Torch is set to correct state according to the current flash mode.
337 |
338 | - `README.md` update.
339 |
340 | ## [1.0.11](https://github.com/imaginary-cloud/CameraManager/tree/1.0.11) - 2015-03-20
341 |
342 | ### Added
343 |
344 | - Property `showAccessPermissionPopupAutomatically`, to determine if you want the user to be asked about camera permissions automatically or manually.
345 |
346 | - Error handling in capture completion blocks.
347 |
348 | ## [1.0.10](https://github.com/imaginary-cloud/CameraManager/tree/1.0.10) - 2015-03-19
349 |
350 | ### Added
351 |
352 | - Camera state returned when adding the preview layer.
353 |
354 | ### Changed
355 |
356 | - `README.md` update.
357 |
358 | ## [1.0.9](https://github.com/imaginary-cloud/CameraManager/tree/1.0.9) - 2015-03-10
359 |
360 | ### Changed
361 |
362 | - CameraManager class made public.
363 |
364 | ## [1.0.8](https://github.com/imaginary-cloud/CameraManager/tree/1.0.8) - 2015-02-24
365 |
366 | ### Fixed
367 |
368 | - Wrong orientation when camera preview starts in landscape mode.
369 |
370 | - Crash when trying to capture a still image.
371 |
372 | - Orientation detection failure after stop and resume of a capture session.
373 |
374 | - Bug which prevented from recording audio.
375 |
376 | ## [1.0.7](https://github.com/imaginary-cloud/CameraManager/tree/1.0.7) - 2014-10-30
377 |
378 | ### Added
379 |
380 | - Version compatible with XCode 6.1.
381 |
382 | ### Changed
383 |
384 | - `README.md` update.
385 |
386 | - Swift syntax updates to resolve compile errors.
387 |
388 | ## [1.0.6](https://github.com/imaginary-cloud/CameraManager/tree/1.0.6) - 2014-10-28
389 |
390 | ### Added
391 |
392 | - Check for valid capture session.
393 |
394 | ### Changed
395 |
396 | - Fixed video orientation change.
397 |
398 | ## [1.0.5](https://github.com/imaginary-cloud/CameraManager/tree/1.0.5) - 2014-10-22
399 |
400 | ### Changed
401 |
402 | - Enhanced Camera lifecyle.
403 |
404 | - Orientation observers only added if needed.
405 |
406 | ## [1.0.4](https://github.com/imaginary-cloud/CameraManager/tree/1.0.4) - 2014-10-16
407 |
408 | ### Added
409 |
410 | - Restart session.
411 |
412 | ### Changed
413 |
414 | - `README.md` update.
415 |
416 | ## [1.0.3](https://github.com/imaginary-cloud/CameraManager/tree/1.0.3) - 2014-10-15
417 |
418 | ### Added
419 |
420 | - Property `writeFilesToPhoneLibrary` to conditionally write to user library.
421 |
422 | ### Changed
423 |
424 | - Resources only recreated when needed.
425 |
426 | - `README.md` update.
427 |
428 | ## [1.0.2](https://github.com/imaginary-cloud/CameraManager/tree/1.0.2) - 2014-10-15
429 |
430 | ### Added
431 |
432 | - `CameraManager.podspec` file.
433 |
434 | ## [1.0.1](https://github.com/imaginary-cloud/CameraManager/tree/1.0.1) - 2014-10-15
435 |
436 | ### Changed
437 |
438 | - Optional initializer for `addPreviewLayerToView`.
439 |
440 | ## [1.0.0](https://github.com/imaginary-cloud/CameraManager/tree/1.0.0) - 2014-10-15
441 |
442 | ### Added
443 |
444 | - Front and back camera selection.
445 |
446 | - Support for multiple flash modes.
447 |
448 | - Video recording, with or without mic.
449 |
450 | - Support for multiple camera output quality.
451 |
452 | - Preview layer follows interface orientation changes.
453 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # [Imaginary Cloud's](https://www.imaginarycloud.com) Code of Conduct
2 |
3 | To make sure that we provide an open and welcoming environment, we enforce in our projects a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
4 |
5 | ## Behavior
6 |
7 | How to create a positive environment:
8 |
9 | * Using welcoming and inclusive language;
10 | * Being respectful of differing viewpoints and experiences;
11 | * Gracefully accepting constructive criticism;
12 | * Focusing on what is best for the community;
13 | * Showing empathy towards other community members.
14 |
15 | Unacceptable behavior by participants include:
16 |
17 | * The use of sexualized language or imagery and unwelcome sexual attention or advances;
18 | * Trolling, insulting/derogatory comments, and personal or political attacks;
19 | * Public or private harassment;
20 | * Publishing others' private information, such as a physical or electronic address, without explicit permission;
21 | * Other conduct which could reasonably be considered inappropriate in a professional setting.
22 |
23 | ## Responsibilities
24 |
25 | We are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
26 |
27 | We have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that we deem inappropriate, threatening, offensive, or harmful.
28 |
29 | ## Scope
30 |
31 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
32 |
33 | ## Enforcement
34 |
35 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at info@imaginarycloud. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
36 |
37 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
38 |
39 | ## Attribution
40 |
41 | This Code of Conduct is inspired by the [Contributor Covenant](http://contributor-covenant.org/version/1/4), version 1.4.
42 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # [Imaginary Cloud's](https://www.imaginarycloud.com) Contribution Guidelines
2 |
3 | We accept pull requests from everyone and you're very welcome to do it. Just be sure that you're following the Imaginary Clouds's Code of Conduct and everything will run smoothly thereafter.
4 |
5 | ## General Guidelines
6 |
7 | * Fork the repository;
8 | * Provide a thorough description of the feature behavior;
9 | * If you're submiting a bug fix, provide a thorough description of its behavior or expected behavior;
10 | * Push to fork and submit a pull request.
11 |
--------------------------------------------------------------------------------
/CameraManager.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "CameraManager"
3 | s.version = "5.1.3"
4 | s.summary = "This is a simple Swift class to provide all the configurations you need to create custom camera view in your app. Just drag, drop and use."
5 | s.requires_arc = true
6 | s.homepage = "https://github.com/imaginary-cloud/CameraManager"
7 | s.license = 'MIT'
8 | s.author = { "torrao" => "rtorrao@imaginarycloud.com" }
9 | s.source = { :git => "https://github.com/imaginary-cloud/CameraManager.git", :tag => "5.1.3" }
10 | s.social_media_url = 'http://www.imaginarycloud.com/'
11 | s.platform = :ios, '9.0'
12 | s.pod_target_xcconfig = { "SWIFT_VERSION" => "5.2" }
13 | s.swift_version = '5.2'
14 | s.source_files = 'Sources/CameraManager.swift'
15 | end
16 |
--------------------------------------------------------------------------------
/CameraManager.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 02A8524E2433A7BC001F560A /* CameraManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 454C1F6619E8316A00C81915 /* CameraManager.swift */; };
11 | 454C1F4719E82E2500C81915 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 454C1F4619E82E2500C81915 /* AppDelegate.swift */; };
12 | 454C1F4919E82E2500C81915 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 454C1F4819E82E2500C81915 /* ViewController.swift */; };
13 | 454C1F4C19E82E2500C81915 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 454C1F4A19E82E2500C81915 /* Main.storyboard */; };
14 | 454C1F4E19E82E2500C81915 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 454C1F4D19E82E2500C81915 /* Images.xcassets */; };
15 | 454C1F5119E82E2500C81915 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 454C1F4F19E82E2500C81915 /* LaunchScreen.xib */; };
16 | 45A23C181A656BDC00FB48F3 /* ImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45A23C171A656BDC00FB48F3 /* ImageViewController.swift */; };
17 | D71DE8861AD677A7001E62F1 /* CameraManager.h in Headers */ = {isa = PBXBuildFile; fileRef = D71DE8851AD677A7001E62F1 /* CameraManager.h */; settings = {ATTRIBUTES = (Public, ); }; };
18 | D71DE8981AD677A8001E62F1 /* CameraManager.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D71DE8811AD677A7001E62F1 /* CameraManager.framework */; };
19 | D71DE8991AD677A8001E62F1 /* CameraManager.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D71DE8811AD677A7001E62F1 /* CameraManager.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
20 | /* End PBXBuildFile section */
21 |
22 | /* Begin PBXContainerItemProxy section */
23 | D71DE8961AD677A8001E62F1 /* PBXContainerItemProxy */ = {
24 | isa = PBXContainerItemProxy;
25 | containerPortal = 454C1F3919E82E2500C81915 /* Project object */;
26 | proxyType = 1;
27 | remoteGlobalIDString = D71DE8801AD677A7001E62F1;
28 | remoteInfo = CameraManager;
29 | };
30 | /* End PBXContainerItemProxy section */
31 |
32 | /* Begin PBXCopyFilesBuildPhase section */
33 | D71DE89D1AD677A8001E62F1 /* Embed Frameworks */ = {
34 | isa = PBXCopyFilesBuildPhase;
35 | buildActionMask = 2147483647;
36 | dstPath = "";
37 | dstSubfolderSpec = 10;
38 | files = (
39 | D71DE8991AD677A8001E62F1 /* CameraManager.framework in Embed Frameworks */,
40 | );
41 | name = "Embed Frameworks";
42 | runOnlyForDeploymentPostprocessing = 0;
43 | };
44 | /* End PBXCopyFilesBuildPhase section */
45 |
46 | /* Begin PBXFileReference section */
47 | 454C1F4119E82E2500C81915 /* camera.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = camera.app; sourceTree = BUILT_PRODUCTS_DIR; };
48 | 454C1F4519E82E2500C81915 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
49 | 454C1F4619E82E2500C81915 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
50 | 454C1F4819E82E2500C81915 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
51 | 454C1F4B19E82E2500C81915 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
52 | 454C1F4D19E82E2500C81915 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; };
53 | 454C1F5019E82E2500C81915 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; };
54 | 454C1F6619E8316A00C81915 /* CameraManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraManager.swift; sourceTree = ""; };
55 | 45A23C171A656BDC00FB48F3 /* ImageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageViewController.swift; sourceTree = ""; };
56 | D71DE8811AD677A7001E62F1 /* CameraManager.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CameraManager.framework; sourceTree = BUILT_PRODUCTS_DIR; };
57 | D71DE8841AD677A7001E62F1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
58 | D71DE8851AD677A7001E62F1 /* CameraManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CameraManager.h; sourceTree = ""; };
59 | /* End PBXFileReference section */
60 |
61 | /* Begin PBXFrameworksBuildPhase section */
62 | 454C1F3E19E82E2500C81915 /* Frameworks */ = {
63 | isa = PBXFrameworksBuildPhase;
64 | buildActionMask = 2147483647;
65 | files = (
66 | D71DE8981AD677A8001E62F1 /* CameraManager.framework in Frameworks */,
67 | );
68 | runOnlyForDeploymentPostprocessing = 0;
69 | };
70 | D71DE87D1AD677A7001E62F1 /* Frameworks */ = {
71 | isa = PBXFrameworksBuildPhase;
72 | buildActionMask = 2147483647;
73 | files = (
74 | );
75 | runOnlyForDeploymentPostprocessing = 0;
76 | };
77 | /* End PBXFrameworksBuildPhase section */
78 |
79 | /* Begin PBXGroup section */
80 | 454C1F3819E82E2500C81915 = {
81 | isa = PBXGroup;
82 | children = (
83 | 454C1F4319E82E2500C81915 /* Example App */,
84 | D71DE8821AD677A7001E62F1 /* Sources */,
85 | 454C1F4219E82E2500C81915 /* Products */,
86 | );
87 | sourceTree = "";
88 | };
89 | 454C1F4219E82E2500C81915 /* Products */ = {
90 | isa = PBXGroup;
91 | children = (
92 | 454C1F4119E82E2500C81915 /* camera.app */,
93 | D71DE8811AD677A7001E62F1 /* CameraManager.framework */,
94 | );
95 | name = Products;
96 | sourceTree = "";
97 | };
98 | 454C1F4319E82E2500C81915 /* Example App */ = {
99 | isa = PBXGroup;
100 | children = (
101 | 454C1F4619E82E2500C81915 /* AppDelegate.swift */,
102 | 454C1F4819E82E2500C81915 /* ViewController.swift */,
103 | 45A23C171A656BDC00FB48F3 /* ImageViewController.swift */,
104 | 454C1F4D19E82E2500C81915 /* Images.xcassets */,
105 | 454C1F4F19E82E2500C81915 /* LaunchScreen.xib */,
106 | 454C1F4A19E82E2500C81915 /* Main.storyboard */,
107 | 454C1F4419E82E2500C81915 /* Supporting Files */,
108 | );
109 | path = "Example App";
110 | sourceTree = "";
111 | };
112 | 454C1F4419E82E2500C81915 /* Supporting Files */ = {
113 | isa = PBXGroup;
114 | children = (
115 | 454C1F4519E82E2500C81915 /* Info.plist */,
116 | );
117 | name = "Supporting Files";
118 | sourceTree = "";
119 | };
120 | D71DE8821AD677A7001E62F1 /* Sources */ = {
121 | isa = PBXGroup;
122 | children = (
123 | 454C1F6619E8316A00C81915 /* CameraManager.swift */,
124 | D71DE8851AD677A7001E62F1 /* CameraManager.h */,
125 | D71DE8831AD677A7001E62F1 /* Supporting Files */,
126 | );
127 | path = Sources;
128 | sourceTree = "";
129 | };
130 | D71DE8831AD677A7001E62F1 /* Supporting Files */ = {
131 | isa = PBXGroup;
132 | children = (
133 | D71DE8841AD677A7001E62F1 /* Info.plist */,
134 | );
135 | name = "Supporting Files";
136 | sourceTree = "";
137 | };
138 | /* End PBXGroup section */
139 |
140 | /* Begin PBXHeadersBuildPhase section */
141 | D71DE87E1AD677A7001E62F1 /* Headers */ = {
142 | isa = PBXHeadersBuildPhase;
143 | buildActionMask = 2147483647;
144 | files = (
145 | D71DE8861AD677A7001E62F1 /* CameraManager.h in Headers */,
146 | );
147 | runOnlyForDeploymentPostprocessing = 0;
148 | };
149 | /* End PBXHeadersBuildPhase section */
150 |
151 | /* Begin PBXNativeTarget section */
152 | 454C1F4019E82E2500C81915 /* camera */ = {
153 | isa = PBXNativeTarget;
154 | buildConfigurationList = 454C1F6019E82E2500C81915 /* Build configuration list for PBXNativeTarget "camera" */;
155 | buildPhases = (
156 | 454C1F3D19E82E2500C81915 /* Sources */,
157 | 454C1F3E19E82E2500C81915 /* Frameworks */,
158 | 454C1F3F19E82E2500C81915 /* Resources */,
159 | D71DE89D1AD677A8001E62F1 /* Embed Frameworks */,
160 | );
161 | buildRules = (
162 | );
163 | dependencies = (
164 | D71DE8971AD677A8001E62F1 /* PBXTargetDependency */,
165 | );
166 | name = camera;
167 | productName = camera;
168 | productReference = 454C1F4119E82E2500C81915 /* camera.app */;
169 | productType = "com.apple.product-type.application";
170 | };
171 | D71DE8801AD677A7001E62F1 /* CameraManager */ = {
172 | isa = PBXNativeTarget;
173 | buildConfigurationList = D71DE89A1AD677A8001E62F1 /* Build configuration list for PBXNativeTarget "CameraManager" */;
174 | buildPhases = (
175 | D71DE87C1AD677A7001E62F1 /* Sources */,
176 | D71DE87D1AD677A7001E62F1 /* Frameworks */,
177 | D71DE87E1AD677A7001E62F1 /* Headers */,
178 | D71DE87F1AD677A7001E62F1 /* Resources */,
179 | );
180 | buildRules = (
181 | );
182 | dependencies = (
183 | );
184 | name = CameraManager;
185 | productName = CameraManager;
186 | productReference = D71DE8811AD677A7001E62F1 /* CameraManager.framework */;
187 | productType = "com.apple.product-type.framework";
188 | };
189 | /* End PBXNativeTarget section */
190 |
191 | /* Begin PBXProject section */
192 | 454C1F3919E82E2500C81915 /* Project object */ = {
193 | isa = PBXProject;
194 | attributes = {
195 | LastSwiftUpdateCheck = 0700;
196 | LastUpgradeCheck = 1130;
197 | ORGANIZATIONNAME = imaginaryCloud;
198 | TargetAttributes = {
199 | 454C1F4019E82E2500C81915 = {
200 | CreatedOnToolsVersion = 6.0.1;
201 | DevelopmentTeam = QM7HJTY23M;
202 | LastSwiftMigration = 1130;
203 | ProvisioningStyle = Automatic;
204 | };
205 | D71DE8801AD677A7001E62F1 = {
206 | CreatedOnToolsVersion = 6.3;
207 | LastSwiftMigration = 1130;
208 | };
209 | };
210 | };
211 | buildConfigurationList = 454C1F3C19E82E2500C81915 /* Build configuration list for PBXProject "CameraManager" */;
212 | compatibilityVersion = "Xcode 3.2";
213 | developmentRegion = en;
214 | hasScannedForEncodings = 0;
215 | knownRegions = (
216 | en,
217 | Base,
218 | );
219 | mainGroup = 454C1F3819E82E2500C81915;
220 | productRefGroup = 454C1F4219E82E2500C81915 /* Products */;
221 | projectDirPath = "";
222 | projectRoot = "";
223 | targets = (
224 | 454C1F4019E82E2500C81915 /* camera */,
225 | D71DE8801AD677A7001E62F1 /* CameraManager */,
226 | );
227 | };
228 | /* End PBXProject section */
229 |
230 | /* Begin PBXResourcesBuildPhase section */
231 | 454C1F3F19E82E2500C81915 /* Resources */ = {
232 | isa = PBXResourcesBuildPhase;
233 | buildActionMask = 2147483647;
234 | files = (
235 | 454C1F4C19E82E2500C81915 /* Main.storyboard in Resources */,
236 | 454C1F5119E82E2500C81915 /* LaunchScreen.xib in Resources */,
237 | 454C1F4E19E82E2500C81915 /* Images.xcassets in Resources */,
238 | );
239 | runOnlyForDeploymentPostprocessing = 0;
240 | };
241 | D71DE87F1AD677A7001E62F1 /* Resources */ = {
242 | isa = PBXResourcesBuildPhase;
243 | buildActionMask = 2147483647;
244 | files = (
245 | );
246 | runOnlyForDeploymentPostprocessing = 0;
247 | };
248 | /* End PBXResourcesBuildPhase section */
249 |
250 | /* Begin PBXSourcesBuildPhase section */
251 | 454C1F3D19E82E2500C81915 /* Sources */ = {
252 | isa = PBXSourcesBuildPhase;
253 | buildActionMask = 2147483647;
254 | files = (
255 | 454C1F4919E82E2500C81915 /* ViewController.swift in Sources */,
256 | 45A23C181A656BDC00FB48F3 /* ImageViewController.swift in Sources */,
257 | 454C1F4719E82E2500C81915 /* AppDelegate.swift in Sources */,
258 | );
259 | runOnlyForDeploymentPostprocessing = 0;
260 | };
261 | D71DE87C1AD677A7001E62F1 /* Sources */ = {
262 | isa = PBXSourcesBuildPhase;
263 | buildActionMask = 2147483647;
264 | files = (
265 | 02A8524E2433A7BC001F560A /* CameraManager.swift in Sources */,
266 | );
267 | runOnlyForDeploymentPostprocessing = 0;
268 | };
269 | /* End PBXSourcesBuildPhase section */
270 |
271 | /* Begin PBXTargetDependency section */
272 | D71DE8971AD677A8001E62F1 /* PBXTargetDependency */ = {
273 | isa = PBXTargetDependency;
274 | target = D71DE8801AD677A7001E62F1 /* CameraManager */;
275 | targetProxy = D71DE8961AD677A8001E62F1 /* PBXContainerItemProxy */;
276 | };
277 | /* End PBXTargetDependency section */
278 |
279 | /* Begin PBXVariantGroup section */
280 | 454C1F4A19E82E2500C81915 /* Main.storyboard */ = {
281 | isa = PBXVariantGroup;
282 | children = (
283 | 454C1F4B19E82E2500C81915 /* Base */,
284 | );
285 | name = Main.storyboard;
286 | sourceTree = "";
287 | };
288 | 454C1F4F19E82E2500C81915 /* LaunchScreen.xib */ = {
289 | isa = PBXVariantGroup;
290 | children = (
291 | 454C1F5019E82E2500C81915 /* Base */,
292 | );
293 | name = LaunchScreen.xib;
294 | sourceTree = "";
295 | };
296 | /* End PBXVariantGroup section */
297 |
298 | /* Begin XCBuildConfiguration section */
299 | 454C1F5E19E82E2500C81915 /* Debug */ = {
300 | isa = XCBuildConfiguration;
301 | buildSettings = {
302 | ALWAYS_SEARCH_USER_PATHS = NO;
303 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
304 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
305 | CLANG_CXX_LIBRARY = "libc++";
306 | CLANG_ENABLE_MODULES = YES;
307 | CLANG_ENABLE_OBJC_ARC = YES;
308 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
309 | CLANG_WARN_BOOL_CONVERSION = YES;
310 | CLANG_WARN_COMMA = YES;
311 | CLANG_WARN_CONSTANT_CONVERSION = YES;
312 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
313 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
314 | CLANG_WARN_EMPTY_BODY = YES;
315 | CLANG_WARN_ENUM_CONVERSION = YES;
316 | CLANG_WARN_INFINITE_RECURSION = YES;
317 | CLANG_WARN_INT_CONVERSION = YES;
318 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
319 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
320 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
321 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
322 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
323 | CLANG_WARN_STRICT_PROTOTYPES = YES;
324 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
325 | CLANG_WARN_UNREACHABLE_CODE = YES;
326 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
327 | "CODE_SIGN_ENTITLEMENTS[sdk=iphoneos*]" = "";
328 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
329 | COPY_PHASE_STRIP = NO;
330 | ENABLE_STRICT_OBJC_MSGSEND = YES;
331 | ENABLE_TESTABILITY = YES;
332 | GCC_C_LANGUAGE_STANDARD = gnu99;
333 | GCC_DYNAMIC_NO_PIC = NO;
334 | GCC_NO_COMMON_BLOCKS = YES;
335 | GCC_OPTIMIZATION_LEVEL = 0;
336 | GCC_PREPROCESSOR_DEFINITIONS = (
337 | "DEBUG=1",
338 | "$(inherited)",
339 | );
340 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
341 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
342 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
343 | GCC_WARN_UNDECLARED_SELECTOR = YES;
344 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
345 | GCC_WARN_UNUSED_FUNCTION = YES;
346 | GCC_WARN_UNUSED_VARIABLE = YES;
347 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
348 | MTL_ENABLE_DEBUG_INFO = YES;
349 | ONLY_ACTIVE_ARCH = YES;
350 | SDKROOT = iphoneos;
351 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
352 | SWIFT_VERSION = 5.0;
353 | TARGETED_DEVICE_FAMILY = "1,2";
354 | };
355 | name = Debug;
356 | };
357 | 454C1F5F19E82E2500C81915 /* Release */ = {
358 | isa = XCBuildConfiguration;
359 | buildSettings = {
360 | ALWAYS_SEARCH_USER_PATHS = NO;
361 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
362 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
363 | CLANG_CXX_LIBRARY = "libc++";
364 | CLANG_ENABLE_MODULES = YES;
365 | CLANG_ENABLE_OBJC_ARC = YES;
366 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
367 | CLANG_WARN_BOOL_CONVERSION = YES;
368 | CLANG_WARN_COMMA = YES;
369 | CLANG_WARN_CONSTANT_CONVERSION = YES;
370 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
371 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
372 | CLANG_WARN_EMPTY_BODY = YES;
373 | CLANG_WARN_ENUM_CONVERSION = YES;
374 | CLANG_WARN_INFINITE_RECURSION = YES;
375 | CLANG_WARN_INT_CONVERSION = YES;
376 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
377 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
378 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
379 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
380 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
381 | CLANG_WARN_STRICT_PROTOTYPES = YES;
382 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
383 | CLANG_WARN_UNREACHABLE_CODE = YES;
384 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
385 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
386 | COPY_PHASE_STRIP = YES;
387 | ENABLE_NS_ASSERTIONS = NO;
388 | ENABLE_STRICT_OBJC_MSGSEND = YES;
389 | GCC_C_LANGUAGE_STANDARD = gnu99;
390 | GCC_NO_COMMON_BLOCKS = YES;
391 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
392 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
393 | GCC_WARN_UNDECLARED_SELECTOR = YES;
394 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
395 | GCC_WARN_UNUSED_FUNCTION = YES;
396 | GCC_WARN_UNUSED_VARIABLE = YES;
397 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
398 | MTL_ENABLE_DEBUG_INFO = NO;
399 | SDKROOT = iphoneos;
400 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
401 | SWIFT_VERSION = 5.0;
402 | TARGETED_DEVICE_FAMILY = "1,2";
403 | VALIDATE_PRODUCT = YES;
404 | };
405 | name = Release;
406 | };
407 | 454C1F6119E82E2500C81915 /* Debug */ = {
408 | isa = XCBuildConfiguration;
409 | buildSettings = {
410 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
411 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
412 | BUILD_LIBRARY_FOR_DISTRIBUTION = NO;
413 | CODE_SIGN_IDENTITY = "iPhone Developer";
414 | CODE_SIGN_STYLE = Automatic;
415 | DEVELOPMENT_TEAM = QM7HJTY23M;
416 | INFOPLIST_FILE = "Example App/Info.plist";
417 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
418 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
419 | MARKETING_VERSION = 5.1.3;
420 | PRODUCT_BUNDLE_IDENTIFIER = cameraDemo;
421 | PRODUCT_NAME = "$(TARGET_NAME)";
422 | PROVISIONING_PROFILE_SPECIFIER = "";
423 | SWIFT_SWIFT3_OBJC_INFERENCE = Default;
424 | SWIFT_VERSION = 5.0;
425 | };
426 | name = Debug;
427 | };
428 | 454C1F6219E82E2500C81915 /* Release */ = {
429 | isa = XCBuildConfiguration;
430 | buildSettings = {
431 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
432 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
433 | BUILD_LIBRARY_FOR_DISTRIBUTION = NO;
434 | CODE_SIGN_IDENTITY = "iPhone Developer";
435 | CODE_SIGN_STYLE = Automatic;
436 | DEVELOPMENT_TEAM = QM7HJTY23M;
437 | INFOPLIST_FILE = camera/Info.plist;
438 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
439 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
440 | MARKETING_VERSION = 5.1.3;
441 | PRODUCT_BUNDLE_IDENTIFIER = cameraDemo;
442 | PRODUCT_NAME = "$(TARGET_NAME)";
443 | PROVISIONING_PROFILE_SPECIFIER = "";
444 | SWIFT_SWIFT3_OBJC_INFERENCE = Default;
445 | SWIFT_VERSION = 5.0;
446 | };
447 | name = Release;
448 | };
449 | D71DE89B1AD677A8001E62F1 /* Debug */ = {
450 | isa = XCBuildConfiguration;
451 | buildSettings = {
452 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
453 | CURRENT_PROJECT_VERSION = 1;
454 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
455 | DEFINES_MODULE = YES;
456 | DYLIB_COMPATIBILITY_VERSION = 1;
457 | DYLIB_CURRENT_VERSION = 1;
458 | DYLIB_INSTALL_NAME_BASE = "@rpath";
459 | GCC_NO_COMMON_BLOCKS = YES;
460 | GCC_PREPROCESSOR_DEFINITIONS = (
461 | "DEBUG=1",
462 | "$(inherited)",
463 | );
464 | INFOPLIST_FILE = Sources/Info.plist;
465 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
466 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
467 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
468 | PRODUCT_BUNDLE_IDENTIFIER = "com.imaginarycloud.$(PRODUCT_NAME:rfc1034identifier)";
469 | PRODUCT_NAME = "$(TARGET_NAME)";
470 | SKIP_INSTALL = YES;
471 | SWIFT_SWIFT3_OBJC_INFERENCE = Default;
472 | SWIFT_VERSION = 5.0;
473 | VERSIONING_SYSTEM = "apple-generic";
474 | VERSION_INFO_PREFIX = "";
475 | };
476 | name = Debug;
477 | };
478 | D71DE89C1AD677A8001E62F1 /* Release */ = {
479 | isa = XCBuildConfiguration;
480 | buildSettings = {
481 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
482 | COPY_PHASE_STRIP = NO;
483 | CURRENT_PROJECT_VERSION = 1;
484 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
485 | DEFINES_MODULE = YES;
486 | DYLIB_COMPATIBILITY_VERSION = 1;
487 | DYLIB_CURRENT_VERSION = 1;
488 | DYLIB_INSTALL_NAME_BASE = "@rpath";
489 | GCC_NO_COMMON_BLOCKS = YES;
490 | INFOPLIST_FILE = Sources/Info.plist;
491 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
492 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
493 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
494 | PRODUCT_BUNDLE_IDENTIFIER = "com.imaginarycloud.$(PRODUCT_NAME:rfc1034identifier)";
495 | PRODUCT_NAME = "$(TARGET_NAME)";
496 | SKIP_INSTALL = YES;
497 | SWIFT_SWIFT3_OBJC_INFERENCE = Default;
498 | SWIFT_VERSION = 5.0;
499 | VERSIONING_SYSTEM = "apple-generic";
500 | VERSION_INFO_PREFIX = "";
501 | };
502 | name = Release;
503 | };
504 | /* End XCBuildConfiguration section */
505 |
506 | /* Begin XCConfigurationList section */
507 | 454C1F3C19E82E2500C81915 /* Build configuration list for PBXProject "CameraManager" */ = {
508 | isa = XCConfigurationList;
509 | buildConfigurations = (
510 | 454C1F5E19E82E2500C81915 /* Debug */,
511 | 454C1F5F19E82E2500C81915 /* Release */,
512 | );
513 | defaultConfigurationIsVisible = 0;
514 | defaultConfigurationName = Release;
515 | };
516 | 454C1F6019E82E2500C81915 /* Build configuration list for PBXNativeTarget "camera" */ = {
517 | isa = XCConfigurationList;
518 | buildConfigurations = (
519 | 454C1F6119E82E2500C81915 /* Debug */,
520 | 454C1F6219E82E2500C81915 /* Release */,
521 | );
522 | defaultConfigurationIsVisible = 0;
523 | defaultConfigurationName = Release;
524 | };
525 | D71DE89A1AD677A8001E62F1 /* Build configuration list for PBXNativeTarget "CameraManager" */ = {
526 | isa = XCConfigurationList;
527 | buildConfigurations = (
528 | D71DE89B1AD677A8001E62F1 /* Debug */,
529 | D71DE89C1AD677A8001E62F1 /* Release */,
530 | );
531 | defaultConfigurationIsVisible = 0;
532 | defaultConfigurationName = Release;
533 | };
534 | /* End XCConfigurationList section */
535 | };
536 | rootObject = 454C1F3919E82E2500C81915 /* Project object */;
537 | }
538 |
--------------------------------------------------------------------------------
/CameraManager.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/CameraManager.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/CameraManager.xcodeproj/xcshareddata/xcschemes/CameraManager.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
44 |
50 |
51 |
52 |
53 |
59 |
60 |
66 |
67 |
68 |
69 |
71 |
72 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/CameraManager.xcodeproj/xcshareddata/xcschemes/camera.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
45 |
47 |
53 |
54 |
55 |
56 |
62 |
64 |
70 |
71 |
72 |
73 |
75 |
76 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/Example App/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // camera
4 | //
5 | // Created by Natalia Terlecka on 10/10/14.
6 | // Copyright (c) 2014 Imaginary Cloud. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 | var window: UIWindow?
14 |
15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
16 | // Override point for customization after application launch.
17 | return true
18 | }
19 |
20 | func applicationWillResignActive(_ application: UIApplication) {
21 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
22 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
23 | }
24 |
25 | func applicationDidEnterBackground(_ application: UIApplication) {
26 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
27 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
28 | }
29 |
30 | func applicationWillEnterForeground(_ application: UIApplication) {
31 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
32 | }
33 |
34 | func applicationDidBecomeActive(_ application: UIApplication) {
35 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
36 | }
37 |
38 | func applicationWillTerminate(_ application: UIApplication) {
39 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Example App/Base.lproj/LaunchScreen.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/Example App/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
81 |
82 |
83 |
84 |
105 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
--------------------------------------------------------------------------------
/Example App/ImageViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImageViewController.swift
3 | // camera
4 | //
5 | // Created by Natalia Terlecka on 13/01/15.
6 | // Copyright (c) 2015 Imaginary Cloud. All rights reserved.
7 | //
8 |
9 | import CameraManager
10 | import UIKit
11 |
12 | class ImageViewController: UIViewController {
13 | var image: UIImage?
14 | var cameraManager: CameraManager?
15 | @IBOutlet var imageView: UIImageView!
16 |
17 | override func viewDidLoad() {
18 | super.viewDidLoad()
19 | navigationController?.navigationBar.isHidden = true
20 |
21 | guard let validImage = image else {
22 | return
23 | }
24 |
25 | imageView.image = validImage
26 |
27 | if cameraManager?.cameraDevice == .front {
28 | switch validImage.imageOrientation {
29 | case .up, .down:
30 | imageView.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi))
31 | default:
32 | break
33 | }
34 | }
35 | }
36 |
37 | override func didReceiveMemoryWarning() {
38 | super.didReceiveMemoryWarning()
39 | // Dispose of any resources that can be recreated.
40 | }
41 |
42 | @IBAction func closeButtonTapped(_: Any) {
43 | navigationController?.popViewController(animated: true)
44 | }
45 |
46 | override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
47 | return .portrait
48 | }
49 |
50 | override var shouldAutorotate: Bool {
51 | return false
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Example App/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Icon-App-20x20@2x.png",
5 | "idiom" : "iphone",
6 | "scale" : "2x",
7 | "size" : "20x20"
8 | },
9 | {
10 | "filename" : "Icon-App-20x20@3x.png",
11 | "idiom" : "iphone",
12 | "scale" : "3x",
13 | "size" : "20x20"
14 | },
15 | {
16 | "filename" : "Icon-App-29x29@1x.png",
17 | "idiom" : "iphone",
18 | "scale" : "1x",
19 | "size" : "29x29"
20 | },
21 | {
22 | "filename" : "imaginary copy 2-4.png",
23 | "idiom" : "iphone",
24 | "scale" : "2x",
25 | "size" : "29x29"
26 | },
27 | {
28 | "filename" : "imaginary copy 2-3.png",
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "29x29"
32 | },
33 | {
34 | "filename" : "imaginary copy 2-2.png",
35 | "idiom" : "iphone",
36 | "scale" : "2x",
37 | "size" : "40x40"
38 | },
39 | {
40 | "filename" : "Icon-App-40x40@3x.png",
41 | "idiom" : "iphone",
42 | "scale" : "3x",
43 | "size" : "40x40"
44 | },
45 | {
46 | "filename" : "Icon-App-57x57@1x.png",
47 | "idiom" : "iphone",
48 | "scale" : "1x",
49 | "size" : "57x57"
50 | },
51 | {
52 | "filename" : "Icon-App-57x57@2x.png",
53 | "idiom" : "iphone",
54 | "scale" : "2x",
55 | "size" : "57x57"
56 | },
57 | {
58 | "filename" : "imaginary copy 2-1.png",
59 | "idiom" : "iphone",
60 | "scale" : "2x",
61 | "size" : "60x60"
62 | },
63 | {
64 | "filename" : "imaginary copy 2.png",
65 | "idiom" : "iphone",
66 | "scale" : "3x",
67 | "size" : "60x60"
68 | },
69 | {
70 | "filename" : "Icon-App-20x20@1x.png",
71 | "idiom" : "ipad",
72 | "scale" : "1x",
73 | "size" : "20x20"
74 | },
75 | {
76 | "idiom" : "ipad",
77 | "scale" : "2x",
78 | "size" : "20x20"
79 | },
80 | {
81 | "filename" : "imaginary copy-1.png",
82 | "idiom" : "ipad",
83 | "scale" : "1x",
84 | "size" : "29x29"
85 | },
86 | {
87 | "filename" : "imaginary copy.png",
88 | "idiom" : "ipad",
89 | "scale" : "2x",
90 | "size" : "29x29"
91 | },
92 | {
93 | "filename" : "Icon-App-40x40@1x.png",
94 | "idiom" : "ipad",
95 | "scale" : "1x",
96 | "size" : "40x40"
97 | },
98 | {
99 | "filename" : "copy.png",
100 | "idiom" : "ipad",
101 | "scale" : "2x",
102 | "size" : "40x40"
103 | },
104 | {
105 | "filename" : "Icon-Small-50x50@1x.png",
106 | "idiom" : "ipad",
107 | "scale" : "1x",
108 | "size" : "50x50"
109 | },
110 | {
111 | "filename" : "Icon-Small-50x50@2x.png",
112 | "idiom" : "ipad",
113 | "scale" : "2x",
114 | "size" : "50x50"
115 | },
116 | {
117 | "filename" : "Icon-App-72x72@1x.png",
118 | "idiom" : "ipad",
119 | "scale" : "1x",
120 | "size" : "72x72"
121 | },
122 | {
123 | "filename" : "Icon-App-72x72@2x.png",
124 | "idiom" : "ipad",
125 | "scale" : "2x",
126 | "size" : "72x72"
127 | },
128 | {
129 | "filename" : "Icon-App-76x76@1x.png",
130 | "idiom" : "ipad",
131 | "scale" : "1x",
132 | "size" : "76x76"
133 | },
134 | {
135 | "filename" : "Icon-App-76x76@2x.png",
136 | "idiom" : "ipad",
137 | "scale" : "2x",
138 | "size" : "76x76"
139 | },
140 | {
141 | "filename" : "Icon-App-83.5x83.5@2x.png",
142 | "idiom" : "ipad",
143 | "scale" : "2x",
144 | "size" : "83.5x83.5"
145 | },
146 | {
147 | "filename" : "logo.jpeg",
148 | "idiom" : "ios-marketing",
149 | "scale" : "1x",
150 | "size" : "1024x1024"
151 | },
152 | {
153 | "filename" : "Icon-App-60x60@2x.png",
154 | "idiom" : "car",
155 | "scale" : "2x",
156 | "size" : "60x60"
157 | },
158 | {
159 | "filename" : "Icon-App-60x60@3x.png",
160 | "idiom" : "car",
161 | "scale" : "3x",
162 | "size" : "60x60"
163 | },
164 | {
165 | "idiom" : "watch",
166 | "role" : "notificationCenter",
167 | "scale" : "2x",
168 | "size" : "24x24",
169 | "subtype" : "38mm"
170 | },
171 | {
172 | "idiom" : "watch",
173 | "role" : "notificationCenter",
174 | "scale" : "2x",
175 | "size" : "27.5x27.5",
176 | "subtype" : "42mm"
177 | },
178 | {
179 | "filename" : "Icon-App-29x29@2x.png",
180 | "idiom" : "watch",
181 | "role" : "companionSettings",
182 | "scale" : "2x",
183 | "size" : "29x29"
184 | },
185 | {
186 | "filename" : "Icon-App-29x29@3x.png",
187 | "idiom" : "watch",
188 | "role" : "companionSettings",
189 | "scale" : "3x",
190 | "size" : "29x29"
191 | },
192 | {
193 | "filename" : "Icon-App-40x40@2x.png",
194 | "idiom" : "watch",
195 | "role" : "appLauncher",
196 | "scale" : "2x",
197 | "size" : "40x40",
198 | "subtype" : "38mm"
199 | },
200 | {
201 | "idiom" : "watch",
202 | "role" : "appLauncher",
203 | "scale" : "2x",
204 | "size" : "44x44",
205 | "subtype" : "40mm"
206 | },
207 | {
208 | "idiom" : "watch",
209 | "role" : "appLauncher",
210 | "scale" : "2x",
211 | "size" : "50x50",
212 | "subtype" : "44mm"
213 | },
214 | {
215 | "idiom" : "watch",
216 | "role" : "quickLook",
217 | "scale" : "2x",
218 | "size" : "86x86",
219 | "subtype" : "38mm"
220 | },
221 | {
222 | "idiom" : "watch",
223 | "role" : "quickLook",
224 | "scale" : "2x",
225 | "size" : "98x98",
226 | "subtype" : "42mm"
227 | },
228 | {
229 | "idiom" : "watch",
230 | "role" : "quickLook",
231 | "scale" : "2x",
232 | "size" : "108x108",
233 | "subtype" : "44mm"
234 | },
235 | {
236 | "idiom" : "watch-marketing",
237 | "scale" : "1x",
238 | "size" : "1024x1024"
239 | }
240 | ],
241 | "info" : {
242 | "author" : "xcode",
243 | "version" : 1
244 | }
245 | }
246 |
--------------------------------------------------------------------------------
/Example App/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/Example App/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/Example App/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/Example App/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/Example App/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/Example App/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/Example App/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/Example App/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/Example App/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/Example App/Images.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png
--------------------------------------------------------------------------------
/Example App/Images.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png
--------------------------------------------------------------------------------
/Example App/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/Example App/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/Example App/Images.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png
--------------------------------------------------------------------------------
/Example App/Images.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png
--------------------------------------------------------------------------------
/Example App/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/Example App/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/Example App/Images.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/Example App/Images.xcassets/AppIcon.appiconset/Icon-Small-50x50@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/AppIcon.appiconset/Icon-Small-50x50@1x.png
--------------------------------------------------------------------------------
/Example App/Images.xcassets/AppIcon.appiconset/Icon-Small-50x50@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/AppIcon.appiconset/Icon-Small-50x50@2x.png
--------------------------------------------------------------------------------
/Example App/Images.xcassets/AppIcon.appiconset/copy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/AppIcon.appiconset/copy.png
--------------------------------------------------------------------------------
/Example App/Images.xcassets/AppIcon.appiconset/imaginary copy 2-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/AppIcon.appiconset/imaginary copy 2-1.png
--------------------------------------------------------------------------------
/Example App/Images.xcassets/AppIcon.appiconset/imaginary copy 2-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/AppIcon.appiconset/imaginary copy 2-2.png
--------------------------------------------------------------------------------
/Example App/Images.xcassets/AppIcon.appiconset/imaginary copy 2-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/AppIcon.appiconset/imaginary copy 2-3.png
--------------------------------------------------------------------------------
/Example App/Images.xcassets/AppIcon.appiconset/imaginary copy 2-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/AppIcon.appiconset/imaginary copy 2-4.png
--------------------------------------------------------------------------------
/Example App/Images.xcassets/AppIcon.appiconset/imaginary copy 2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/AppIcon.appiconset/imaginary copy 2.png
--------------------------------------------------------------------------------
/Example App/Images.xcassets/AppIcon.appiconset/imaginary copy-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/AppIcon.appiconset/imaginary copy-1.png
--------------------------------------------------------------------------------
/Example App/Images.xcassets/AppIcon.appiconset/imaginary copy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/AppIcon.appiconset/imaginary copy.png
--------------------------------------------------------------------------------
/Example App/Images.xcassets/AppIcon.appiconset/logo.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/AppIcon.appiconset/logo.jpeg
--------------------------------------------------------------------------------
/Example App/Images.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Example App/Images.xcassets/flash_auto.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "universal",
13 | "filename" : "flash-auto.png",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Example App/Images.xcassets/flash_auto.imageset/flash-auto.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/flash_auto.imageset/flash-auto.png
--------------------------------------------------------------------------------
/Example App/Images.xcassets/flash_off.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "universal",
13 | "filename" : "flash-off.png",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Example App/Images.xcassets/flash_off.imageset/flash-off.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/flash_off.imageset/flash-off.png
--------------------------------------------------------------------------------
/Example App/Images.xcassets/flash_on.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "universal",
13 | "filename" : "flash-on.png",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Example App/Images.xcassets/flash_on.imageset/flash-on.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/flash_on.imageset/flash-on.png
--------------------------------------------------------------------------------
/Example App/Images.xcassets/logo.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "imaginary-2.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "imaginary-1.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "imaginary.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/Example App/Images.xcassets/logo.imageset/imaginary-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/logo.imageset/imaginary-1.png
--------------------------------------------------------------------------------
/Example App/Images.xcassets/logo.imageset/imaginary-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/logo.imageset/imaginary-2.png
--------------------------------------------------------------------------------
/Example App/Images.xcassets/logo.imageset/imaginary.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/logo.imageset/imaginary.png
--------------------------------------------------------------------------------
/Example App/Images.xcassets/output_image.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "universal",
13 | "filename" : "output-image.png",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Example App/Images.xcassets/output_image.imageset/output-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/output_image.imageset/output-image.png
--------------------------------------------------------------------------------
/Example App/Images.xcassets/output_video.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "universal",
13 | "filename" : "icons8-video-call.png",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Example App/Images.xcassets/output_video.imageset/icons8-video-call.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/output_video.imageset/icons8-video-call.png
--------------------------------------------------------------------------------
/Example App/Images.xcassets/switch_camera.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "universal",
13 | "filename" : "switch-camera.png",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Example App/Images.xcassets/switch_camera.imageset/switch-camera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imaginary-cloud/CameraManager/660d7b8326f43ab1721620cd4729b3928c8a767b/Example App/Images.xcassets/switch_camera.imageset/switch-camera.png
--------------------------------------------------------------------------------
/Example App/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 | APPL
17 | CFBundleShortVersionString
18 | $(MARKETING_VERSION)
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | NSCameraUsageDescription
26 | CameraManager will use your camera to take pictures/video.
27 | NSLocationWhenInUseUsageDescription
28 | CameraManager will use your location to save EXIF data.
29 | NSMicrophoneUsageDescription
30 | CameraManager will use your microphone to record audio for videos.
31 | NSPhotoLibraryUsageDescription
32 | CameraManager will access your photo library to save pictures/video.
33 | UILaunchStoryboardName
34 | LaunchScreen
35 | UIMainStoryboardFile
36 | Main
37 | UIRequiredDeviceCapabilities
38 |
39 | armv7
40 |
41 | UIRequiresFullScreen
42 |
43 | UIStatusBarHidden
44 |
45 | UIStatusBarStyle
46 | UIStatusBarStyleDefault
47 | UISupportedInterfaceOrientations
48 |
49 | UIInterfaceOrientationPortrait
50 | UIInterfaceOrientationPortraitUpsideDown
51 | UIInterfaceOrientationLandscapeLeft
52 | UIInterfaceOrientationLandscapeRight
53 |
54 | UISupportedInterfaceOrientations~ipad
55 |
56 | UIInterfaceOrientationPortrait
57 | UIInterfaceOrientationPortraitUpsideDown
58 | UIInterfaceOrientationLandscapeLeft
59 | UIInterfaceOrientationLandscapeRight
60 |
61 | UIViewControllerBasedStatusBarAppearance
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/Example App/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // camera
4 | //
5 | // Created by Natalia Terlecka on 10/10/14.
6 | // Copyright (c) 2014 Imaginary Cloud. All rights reserved.
7 | //
8 |
9 | import CameraManager
10 | import CoreLocation
11 | import UIKit
12 |
13 | class ViewController: UIViewController {
14 | // MARK: - Constants
15 |
16 | let cameraManager = CameraManager()
17 |
18 | // MARK: - @IBOutlets
19 |
20 | @IBOutlet var headerView: UIView!
21 | @IBOutlet var flashModeImageView: UIImageView!
22 | @IBOutlet var outputImageView: UIImageView!
23 | @IBOutlet var cameraTypeImageView: UIImageView!
24 | @IBOutlet var qualityLabel: UILabel!
25 |
26 | @IBOutlet var cameraView: UIView!
27 | @IBOutlet var askForPermissionsLabel: UILabel!
28 |
29 | @IBOutlet var footerView: UIView!
30 | @IBOutlet var cameraButton: UIButton!
31 | @IBOutlet var locationButton: UIButton!
32 |
33 | let darkBlue = UIColor(red: 4 / 255, green: 14 / 255, blue: 26 / 255, alpha: 1)
34 | let lightBlue = UIColor(red: 24 / 255, green: 125 / 255, blue: 251 / 255, alpha: 1)
35 | let redColor = UIColor(red: 229 / 255, green: 77 / 255, blue: 67 / 255, alpha: 1)
36 |
37 | // MARK: - UIViewController
38 |
39 | override func viewDidLoad() {
40 | super.viewDidLoad()
41 |
42 | setupCameraManager()
43 |
44 | navigationController?.navigationBar.isHidden = true
45 |
46 | askForPermissionsLabel.isHidden = true
47 | askForPermissionsLabel.backgroundColor = lightBlue
48 | askForPermissionsLabel.textColor = .white
49 | askForPermissionsLabel.isUserInteractionEnabled = true
50 | let tapGesture = UITapGestureRecognizer(target: self, action: #selector(askForCameraPermissions))
51 | askForPermissionsLabel.addGestureRecognizer(tapGesture)
52 |
53 | footerView.backgroundColor = darkBlue
54 | headerView.backgroundColor = darkBlue
55 |
56 | if CLLocationManager.locationServicesEnabled() {
57 | switch CLLocationManager.authorizationStatus() {
58 | case .authorizedAlways, .authorizedWhenInUse:
59 | cameraManager.shouldUseLocationServices = true
60 | locationButton.isHidden = true
61 | default:
62 | cameraManager.shouldUseLocationServices = false
63 | }
64 | }
65 |
66 | let currentCameraState = cameraManager.currentCameraStatus()
67 |
68 | if currentCameraState == .notDetermined {
69 | askForPermissionsLabel.isHidden = false
70 | } else if currentCameraState == .ready {
71 | addCameraToView()
72 | } else {
73 | askForPermissionsLabel.isHidden = false
74 | }
75 |
76 | flashModeImageView.image = UIImage(named: "flash_off")
77 | if cameraManager.hasFlash {
78 | let tapGesture = UITapGestureRecognizer(target: self, action: #selector(changeFlashMode))
79 | flashModeImageView.addGestureRecognizer(tapGesture)
80 | }
81 |
82 | outputImageView.image = UIImage(named: "output_video")
83 | let outputGesture = UITapGestureRecognizer(target: self, action: #selector(outputModeButtonTapped))
84 | outputImageView.addGestureRecognizer(outputGesture)
85 |
86 | cameraTypeImageView.image = UIImage(named: "switch_camera")
87 | let cameraTypeGesture = UITapGestureRecognizer(target: self, action: #selector(changeCameraDevice))
88 | cameraTypeImageView.addGestureRecognizer(cameraTypeGesture)
89 |
90 | qualityLabel.isUserInteractionEnabled = true
91 | let qualityGesture = UITapGestureRecognizer(target: self, action: #selector(changeCameraQuality))
92 | qualityLabel.addGestureRecognizer(qualityGesture)
93 | }
94 |
95 | override func viewWillAppear(_ animated: Bool) {
96 | super.viewWillAppear(animated)
97 |
98 | navigationController?.navigationBar.isHidden = true
99 | cameraManager.resumeCaptureSession()
100 | cameraManager.startQRCodeDetection { result in
101 | switch result {
102 | case .success(let value):
103 | print(value)
104 | case .failure(let error):
105 | print(error.localizedDescription)
106 | }
107 | }
108 | }
109 |
110 | override func viewWillDisappear(_ animated: Bool) {
111 | super.viewWillDisappear(animated)
112 | cameraManager.stopQRCodeDetection()
113 | cameraManager.stopCaptureSession()
114 | }
115 |
116 | // MARK: - ViewController
117 | fileprivate func setupCameraManager() {
118 | cameraManager.shouldEnableExposure = true
119 |
120 | cameraManager.writeFilesToPhoneLibrary = false
121 |
122 | cameraManager.shouldFlipFrontCameraImage = false
123 | cameraManager.showAccessPermissionPopupAutomatically = false
124 | }
125 |
126 |
127 | fileprivate func addCameraToView() {
128 | cameraManager.addPreviewLayerToView(cameraView, newCameraOutputMode: CameraOutputMode.videoWithMic)
129 | cameraManager.showErrorBlock = { [weak self] (erTitle: String, erMessage: String) -> Void in
130 |
131 | let alertController = UIAlertController(title: erTitle, message: erMessage, preferredStyle: .alert)
132 | alertController.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: { (_) -> Void in }))
133 |
134 | self?.present(alertController, animated: true, completion: nil)
135 | }
136 | }
137 |
138 | // MARK: - @IBActions
139 |
140 | @IBAction func changeFlashMode(_ sender: UIButton) {
141 | switch cameraManager.changeFlashMode() {
142 | case .off:
143 | flashModeImageView.image = UIImage(named: "flash_off")
144 | case .on:
145 | flashModeImageView.image = UIImage(named: "flash_on")
146 | case .auto:
147 | flashModeImageView.image = UIImage(named: "flash_auto")
148 | }
149 | }
150 |
151 | @IBAction func recordButtonTapped(_ sender: UIButton) {
152 | switch cameraManager.cameraOutputMode {
153 | case .stillImage:
154 | cameraManager.capturePictureWithCompletion { result in
155 | switch result {
156 | case .failure:
157 | self.cameraManager.showErrorBlock("Error occurred", "Cannot save picture.")
158 | case .success(let content):
159 |
160 | let vc: ImageViewController? = self.storyboard?.instantiateViewController(withIdentifier: "ImageVC") as? ImageViewController
161 | if let validVC: ImageViewController = vc,
162 | case let capturedData = content.asData {
163 | print(capturedData!.printExifData())
164 | let capturedImage = UIImage(data: capturedData!)!
165 | validVC.image = capturedImage
166 | validVC.cameraManager = self.cameraManager
167 | self.navigationController?.pushViewController(validVC, animated: true)
168 | }
169 | }
170 | }
171 | case .videoWithMic, .videoOnly:
172 | cameraButton.isSelected = !cameraButton.isSelected
173 | cameraButton.setTitle("", for: UIControl.State.selected)
174 |
175 | cameraButton.backgroundColor = cameraButton.isSelected ? redColor : lightBlue
176 | if sender.isSelected {
177 | cameraManager.startRecordingVideo()
178 | } else {
179 | cameraManager.stopVideoRecording { (_, error) -> Void in
180 | if error != nil {
181 | self.cameraManager.showErrorBlock("Error occurred", "Cannot save video.")
182 | }
183 | }
184 | }
185 | }
186 | }
187 |
188 | @IBAction func locateMeButtonTapped(_ sender: Any) {
189 | cameraManager.shouldUseLocationServices = true
190 | locationButton.isHidden = true
191 | }
192 |
193 | @IBAction func outputModeButtonTapped(_ sender: UIButton) {
194 | cameraManager.cameraOutputMode = cameraManager.cameraOutputMode == CameraOutputMode.videoWithMic ? CameraOutputMode.stillImage : CameraOutputMode.videoWithMic
195 | switch cameraManager.cameraOutputMode {
196 | case .stillImage:
197 | cameraButton.isSelected = false
198 | cameraButton.backgroundColor = lightBlue
199 | outputImageView.image = UIImage(named: "output_image")
200 | case .videoWithMic, .videoOnly:
201 | outputImageView.image = UIImage(named: "output_video")
202 | }
203 | }
204 |
205 | @IBAction func changeCameraDevice() {
206 | cameraManager.cameraDevice = cameraManager.cameraDevice == CameraDevice.front ? CameraDevice.back : CameraDevice.front
207 | }
208 |
209 | @IBAction func askForCameraPermissions() {
210 | cameraManager.askUserForCameraPermission { permissionGranted in
211 |
212 | if permissionGranted {
213 | self.askForPermissionsLabel.isHidden = true
214 | self.askForPermissionsLabel.alpha = 0
215 | self.addCameraToView()
216 | } else {
217 | if #available(iOS 10.0, *) {
218 | UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!)
219 | } else {
220 | // Fallback on earlier versions
221 | }
222 | }
223 | }
224 | }
225 |
226 | @IBAction func changeCameraQuality() {
227 | switch cameraManager.cameraOutputQuality {
228 | case .high:
229 | qualityLabel.text = "Medium"
230 | cameraManager.cameraOutputQuality = .medium
231 | case .medium:
232 | qualityLabel.text = "Low"
233 | cameraManager.cameraOutputQuality = .low
234 | case .low:
235 | qualityLabel.text = "High"
236 | cameraManager.cameraOutputQuality = .high
237 | default:
238 | qualityLabel.text = "High"
239 | cameraManager.cameraOutputQuality = .high
240 | }
241 | }
242 | }
243 |
244 | public extension Data {
245 | func printExifData() {
246 | let cfdata: CFData = self as CFData
247 | let imageSourceRef = CGImageSourceCreateWithData(cfdata, nil)
248 | let imageProperties = CGImageSourceCopyMetadataAtIndex(imageSourceRef!, 0, nil)!
249 |
250 | let mutableMetadata = CGImageMetadataCreateMutableCopy(imageProperties)!
251 |
252 | CGImageMetadataEnumerateTagsUsingBlock(mutableMetadata, nil, nil) { _, tag in
253 | print(CGImageMetadataTagCopyName(tag)!, ":", CGImageMetadataTagCopyValue(tag)!)
254 | return true
255 | }
256 | }
257 | }
258 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Imaginary Cloud
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.2
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "CameraManager",
8 | platforms: [
9 | .iOS(.v10)
10 | ],
11 | products: [
12 | .library(
13 | name: "CameraManager",
14 | targets: ["CameraManager"]),
15 | ],
16 | dependencies: [],
17 | targets: [
18 | .target(
19 | name: "CameraManager",
20 | dependencies: [],
21 | path: "Sources",
22 | sources: ["CameraManager.swift"]
23 | )
24 | ],
25 | swiftLanguageVersions: [.v5]
26 | )
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Camera Manager
2 |
3 | [](https://github.com/imaginary-cloud/CameraManager) [](https://github.com/Carthage/Carthage)
4 |
5 | This is a simple Swift class to provide all the configurations you need to create custom camera view in your app.
6 | It follows orientation change and updates UI accordingly, supports front and rear camera selection, pinch to zoom, tap to focus, exposure slider, different flash modes, inputs and outputs and QRCode detection.
7 | Just drag, drop and use.
8 |
9 | We've also written a blog post about it. You can read it [here](https://www.imaginarycloud.com/blog/camera-manager/).
10 |
11 | ## Installation with CocoaPods
12 |
13 | The easiest way to install the CameraManager is with [CocoaPods](http://cocoapods.org)
14 |
15 | ### Podfile
16 |
17 | ```ruby
18 | use_frameworks!
19 |
20 | pod 'CameraManager', '~> 5.1'
21 | ```
22 |
23 | ## Installation with Swift Package Manager
24 |
25 | The [Swift Package Manager](https://swift.org/package-manager/) is a tool for managing the distribution of Swift code.
26 |
27 | Add `CameraManager` as a dependency in your `Package.swift` file:
28 |
29 | ```
30 | import PackageDescription
31 |
32 | let package = Package(
33 | dependencies: [
34 | .Package(url: "https://github.com/imaginary-cloud/CameraManager", from: "5.1.3")
35 | ]
36 | )
37 | ```
38 |
39 | ## Installation with Carthage
40 |
41 | [Carthage](https://github.com/Carthage/Carthage) is another dependency management tool written in Swift.
42 |
43 | Add the following line to your Cartfile:
44 |
45 | ```
46 | github "imaginary-cloud/CameraManager" >= 5.1
47 | ```
48 |
49 | And run `carthage update` to build the dynamic framework.
50 |
51 | ## How to use
52 |
53 | To use it you just add the preview layer to your desired view, you'll get back the state of the camera if it's unavailable, ready or the user denied access to it. Have in mind that in order to retain the AVCaptureSession you will need to retain cameraManager instance somewhere, ex. as an instance constant.
54 |
55 | ```swift
56 | let cameraManager = CameraManager()
57 | cameraManager.addPreviewLayerToView(self.cameraView)
58 |
59 | ```
60 |
61 | To shoot image all you need to do is call:
62 |
63 | ```swift
64 | cameraManager.capturePictureWithCompletion({ result in
65 | switch result {
66 | case .failure:
67 | // error handling
68 | case .success(let content):
69 | self.myImage = content.asImage;
70 | }
71 | })
72 | ```
73 |
74 | To record video you call:
75 |
76 | ```swift
77 | cameraManager.startRecordingVideo()
78 | cameraManager.stopVideoRecording({ (videoURL, recordError) -> Void in
79 | guard let videoURL = videoURL else {
80 | //Handle error of no recorded video URL
81 | }
82 | do {
83 | try FileManager.default.copyItem(at: videoURL, to: self.myVideoURL)
84 | }
85 | catch {
86 | //Handle error occured during copy
87 | }
88 | })
89 | ```
90 |
91 | To zoom in manually:
92 |
93 | ```swift
94 | let zoomScale = CGFloat(2.0)
95 | cameraManager.zoom(zoomScale)
96 | ```
97 |
98 | ### Properties
99 |
100 | You can set input device to front or back camera. `(Default: .Back)`
101 |
102 | ```swift
103 | cameraManager.cameraDevice = .front || .back
104 | ```
105 |
106 | You can specify if the front camera image should be horizontally fliped. `(Default: false)`
107 |
108 | ```swift
109 | cameraManager.shouldFlipFrontCameraImage = true || false
110 | ```
111 |
112 | You can enable or disable gestures on camera preview. `(Default: true)`
113 |
114 | ```swift
115 | cameraManager.shouldEnableTapToFocus = true || false
116 | cameraManager.shouldEnablePinchToZoom = true || false
117 | cameraManager.shouldEnableExposure = true || false
118 | ```
119 |
120 | You can set output format to Image, video or video with audio. `(Default: .stillImage)`
121 |
122 | ```swift
123 | cameraManager.cameraOutputMode = .stillImage || .videoWithMic || .videoOnly
124 | ```
125 |
126 | You can set the quality based on the [AVCaptureSession.Preset values](https://developer.apple.com/documentation/avfoundation/avcapturesession/preset) `(Default: .high)`
127 |
128 | ```swift
129 | cameraManager.cameraOutputQuality = .low || .medium || .high || *
130 | ```
131 |
132 | `*` check all the possible values [here](https://developer.apple.com/documentation/avfoundation/avcapturesession/preset)
133 |
134 | You can also check if you can set a specific preset value:
135 |
136 | ```swift
137 | if .cameraManager.canSetPreset(preset: .hd1280x720) {
138 | cameraManager.cameraOutputQuality = .hd1280x720
139 | } else {
140 | cameraManager.cameraOutputQuality = .high
141 | }
142 | ```
143 |
144 | You can specify the focus mode. `(Default: .continuousAutoFocus)`
145 |
146 | ```swift
147 | cameraManager.focusMode = .autoFocus || .continuousAutoFocus || .locked
148 | ```
149 |
150 | You can specifiy the exposure mode. `(Default: .continuousAutoExposure)`
151 |
152 | ```swift
153 | cameraManager.exposureMode = .autoExpose || .continuousAutoExposure || .locked || .custom
154 | ```
155 |
156 | You can change the flash mode (it will also set corresponding flash mode). `(Default: .off)`
157 |
158 | ```swift
159 | cameraManager.flashMode = .off || .on || .auto
160 | ```
161 |
162 | You can specify the stabilisation mode to be used during a video record session. `(Default: .auto)`
163 |
164 | ```swift
165 | cameraManager.videoStabilisationMode = .auto || .cinematic
166 | ```
167 |
168 | You can get the video stabilization mode currently active. If video stabilization is neither supported or active it will return `.off`.
169 |
170 | ```swift
171 | cameraManager.activeVideoStabilisationMode
172 | ```
173 |
174 | You can enable location services for storing GPS location when saving to Camera Roll. `(Default: false)`
175 |
176 | ```swift
177 | cameraManager.shouldUseLocationServices = true || false
178 | ```
179 |
180 | In case you use location it's mandatory to add `NSLocationWhenInUseUsageDescription` key to the `Info.plist` in your app. [More Info](https://developer.apple.com/documentation/uikit/protecting_the_user_s_privacy)
181 |
182 | For getting the gps location when calling `capturePictureWithCompletion` you should use the `CaptureResult` as `data` (see [Example App](https://github.com/imaginary-cloud/CameraManager/blob/master/Example%20App/ViewController.swift)).
183 |
184 | You can specify if you want to save the files to phone library. `(Default: true)`
185 |
186 | ```swift
187 | cameraManager.writeFilesToPhoneLibrary = true || false
188 | ```
189 |
190 | You can specify the album names for image and video recordings.
191 |
192 | ```swift
193 | cameraManager.imageAlbumName = "Image Album Name"
194 | cameraManager.videoAlbumName = "Video Album Name"
195 | ```
196 |
197 | You can specify if you want to disable animations. `(Default: true)`
198 |
199 | ```swift
200 | cameraManager.animateShutter = true || false
201 | cameraManager.animateCameraDeviceChange = true || false
202 | ```
203 |
204 | You can specify if you want the user to be asked about camera permissions automatically when you first try to use the camera or manually. `(Default: true)`
205 |
206 | ```swift
207 | cameraManager.showAccessPermissionPopupAutomatically = true || false
208 | ```
209 |
210 | To check if the device supports flash call:
211 |
212 | ```swift
213 | cameraManager.hasFlash
214 | ```
215 |
216 | To change flash mode to the next available one you can use this handy function which will also return current value for you to update the UI accordingly:
217 |
218 | ```swift
219 | cameraManager.changeFlashMode()
220 | ```
221 |
222 | You can even setUp your custom block to handle error messages:
223 | It can be customized to be presented on the Window root view controller, for example.
224 |
225 | ```swift
226 | cameraManager.showErrorBlock = { (erTitle: String, erMessage: String) -> Void in
227 | var alertController = UIAlertController(title: erTitle, message: erMessage, preferredStyle: .alert)
228 | alertController.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: { (alertAction) -> Void in
229 | }))
230 |
231 | let topController = UIApplication.shared.keyWindow?.rootViewController
232 |
233 | if (topController != nil) {
234 | topController?.present(alertController, animated: true, completion: { () -> Void in
235 | //
236 | })
237 | }
238 |
239 | }
240 | ```
241 |
242 | You can set if you want to detect QR codes:
243 |
244 | ```swift
245 | cameraManager.startQRCodeDetection { (result) in
246 | switch result {
247 | case .success(let value):
248 | print(value)
249 | case .failure(let error):
250 | print(error.localizedDescription)
251 | }
252 | }
253 | ```
254 |
255 | and don't forget to call `cameraManager.stopQRCodeDetection()` whenever you done detecting.
256 |
257 | ## Support
258 |
259 | Supports iOS 9 and above. Xcode 11.4 is required to build the latest code written in Swift 5.2.
260 |
261 | Now it's compatible with latest Swift syntax, so if you're using any Swift version prior to 5 make sure to use one of the previously tagged releases:
262 |
263 | - for Swift 4 see: [v4.4.0](https://github.com/imaginary-cloud/CameraManager/tree/4.4.0).
264 | - for Swift 3 see: [v3.2.0](https://github.com/imaginary-cloud/CameraManager/tree/3.2.0).
265 |
266 | ## License
267 |
268 | Copyright © 2010-2020 [Imaginary Cloud](https://www.imaginarycloud.com/?utm_source=github). This library is licensed under the MIT license.
269 |
270 | ## About Imaginary Cloud
271 |
272 | [](https://www.imaginarycloud.com/?utm_source=github)
273 |
274 | At Imaginary Cloud, we build world-class web & mobile apps. Our Front-end developers and UI/UX designers are ready to create or scale your digital product. Take a look at our [website](https://www.imaginarycloud.com/?utm_source=github) and [get in touch!](https://www.imaginarycloud.com/contacts/?utm_source=github) We'll take it from there.
275 |
--------------------------------------------------------------------------------
/Sources/CameraManager.h:
--------------------------------------------------------------------------------
1 | //
2 | // CameraManager.h
3 | // CameraManager
4 | //
5 | // Created by Lex Tang on 4/9/15.
6 | // Copyright (c) 2015 Imaginary Cloud. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for CameraManager.
12 | FOUNDATION_EXPORT double CameraManagerVersionNumber;
13 |
14 | //! Project version string for CameraManager.
15 | FOUNDATION_EXPORT const unsigned char CameraManagerVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Sources/CameraManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CameraManager.swift
3 | // camera
4 | //
5 | // Created by Natalia Terlecka on 10/10/14.
6 | // Copyright (c) 2014 Imaginary Cloud. All rights reserved.
7 | //
8 |
9 | import AVFoundation
10 | import CoreImage
11 | import CoreLocation
12 | import CoreMotion
13 | import ImageIO
14 | import MobileCoreServices
15 | import Photos
16 | import PhotosUI
17 | import UIKit
18 |
19 | public enum CameraState {
20 | case ready, accessDenied, noDeviceFound, notDetermined
21 | }
22 |
23 | public enum CameraDevice {
24 | case front, back
25 | }
26 |
27 | public enum CameraFlashMode: Int {
28 | case off, on, auto
29 | }
30 |
31 | public enum CameraOutputMode {
32 | case stillImage, videoWithMic, videoOnly
33 | }
34 |
35 | public enum CaptureResult {
36 | case success(content: CaptureContent)
37 | case failure(Error)
38 |
39 | init(_ image: UIImage) {
40 | self = .success(content: .image(image))
41 | }
42 |
43 | init(_ data: Data) {
44 | self = .success(content: .imageData(data))
45 | }
46 |
47 | init(_ asset: PHAsset) {
48 | self = .success(content: .asset(asset))
49 | }
50 |
51 | var imageData: Data? {
52 | if case let .success(content) = self {
53 | return content.asData
54 | } else {
55 | return nil
56 | }
57 | }
58 | }
59 |
60 | public enum CaptureContent {
61 | case imageData(Data)
62 | case image(UIImage)
63 | case asset(PHAsset)
64 | }
65 |
66 | extension CaptureContent {
67 | public var asImage: UIImage? {
68 | switch self {
69 | case let .image(image): return image
70 | case let .imageData(data): return UIImage(data: data)
71 | case let .asset(asset):
72 | if let data = getImageData(fromAsset: asset) {
73 | return UIImage(data: data)
74 | } else {
75 | return nil
76 | }
77 | }
78 | }
79 |
80 | public var asData: Data? {
81 | switch self {
82 | case let .image(image): return image.jpegData(compressionQuality: 1.0)
83 | case let .imageData(data): return data
84 | case let .asset(asset): return getImageData(fromAsset: asset)
85 | }
86 | }
87 |
88 | private func getImageData(fromAsset asset: PHAsset) -> Data? {
89 | var imageData: Data?
90 | let manager = PHImageManager.default()
91 | let options = PHImageRequestOptions()
92 | options.version = .original
93 | options.isSynchronous = true
94 | manager.requestImageData(for: asset, options: options) { data, _, _, _ in
95 |
96 | imageData = data
97 | }
98 | return imageData
99 | }
100 | }
101 |
102 | public enum CaptureError: Error {
103 | case noImageData
104 | case invalidImageData
105 | case noVideoConnection
106 | case noSampleBuffer
107 | case assetNotSaved
108 | }
109 |
110 | /// Class for handling iDevices custom camera usage
111 | open class CameraManager: NSObject, AVCaptureFileOutputRecordingDelegate, UIGestureRecognizerDelegate {
112 | // MARK: - Public properties
113 |
114 | // Property for custom image album name.
115 | open var imageAlbumName: String?
116 |
117 | // Property for custom image album name.
118 | open var videoAlbumName: String?
119 |
120 | /// Property for capture session to customize camera settings.
121 | open var captureSession: AVCaptureSession?
122 |
123 | /**
124 | Property to determine if the manager should show the error for the user. If you want to show the errors yourself set this to false. If you want to add custom error UI set showErrorBlock property.
125 | - note: Default value is **false**
126 | */
127 | open var showErrorsToUsers = false
128 |
129 | /// Property to determine if the manager should show the camera permission popup immediatly when it's needed or you want to show it manually. Default value is true. Be carful cause using the camera requires permission, if you set this value to false and don't ask manually you won't be able to use the camera.
130 | open var showAccessPermissionPopupAutomatically = true
131 |
132 | /// A block creating UI to present error message to the user. This can be customised to be presented on the Window root view controller, or to pass in the viewController which will present the UIAlertController, for example.
133 | open var showErrorBlock: (_ erTitle: String, _ erMessage: String) -> Void = { (erTitle: String, erMessage: String) -> Void in
134 |
135 | var alertController = UIAlertController(title: erTitle, message: erMessage, preferredStyle: .alert)
136 | alertController.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: { (_) -> Void in }))
137 |
138 | if let topController = UIApplication.shared.keyWindow?.rootViewController {
139 | topController.present(alertController, animated: true, completion: nil)
140 | }
141 | }
142 |
143 | open func canSetPreset(preset: AVCaptureSession.Preset) -> Bool? {
144 | if let validCaptureSession = captureSession {
145 | return validCaptureSession.canSetSessionPreset(preset)
146 | }
147 | return nil
148 | }
149 |
150 | /**
151 | Property to determine if manager should write the resources to the phone library.
152 | - note: Default value is **true**
153 | */
154 | open var writeFilesToPhoneLibrary = true
155 |
156 | /**
157 | Property to determine if manager should follow device orientation.
158 | - note: Default value is **true**
159 | */
160 | open var shouldRespondToOrientationChanges = true {
161 | didSet {
162 | if shouldRespondToOrientationChanges {
163 | _startFollowingDeviceOrientation()
164 | } else {
165 | _stopFollowingDeviceOrientation()
166 | }
167 | }
168 | }
169 |
170 | /**
171 | Property to determine if manager should horizontally flip image took by front camera.
172 | - note: Default value is **false**
173 | */
174 | open var shouldFlipFrontCameraImage = false
175 |
176 | /**
177 | Property to determine if manager should keep view with the same bounds when the orientation changes.
178 | - note: Default value is **false**
179 | */
180 | open var shouldKeepViewAtOrientationChanges = false
181 |
182 | /**
183 | Property to determine if manager should enable tap to focus on camera preview.
184 | - note: Default value is **true**
185 | */
186 | open var shouldEnableTapToFocus = true {
187 | didSet {
188 | focusGesture.isEnabled = shouldEnableTapToFocus
189 | }
190 | }
191 |
192 | /**
193 | Property to determine if manager should enable pinch to zoom on camera preview.
194 | - note: Default value is **true**
195 | */
196 | open var shouldEnablePinchToZoom = true {
197 | didSet {
198 | zoomGesture.isEnabled = shouldEnablePinchToZoom
199 | }
200 | }
201 |
202 | /**
203 | Property to determine if manager should enable pan to change exposure/brightness.
204 | - note: Default value is **true**
205 | */
206 | open var shouldEnableExposure = true {
207 | didSet {
208 | exposureGesture.isEnabled = shouldEnableExposure
209 | }
210 | }
211 |
212 | /// Property to determine if the camera is ready to use.
213 | open var cameraIsReady: Bool {
214 | return cameraIsSetup
215 | }
216 |
217 | /// Property to determine if current device has front camera.
218 | open var hasFrontCamera: Bool = {
219 | let frontDevices = AVCaptureDevice.videoDevices.filter { $0.position == .front }
220 | return !frontDevices.isEmpty
221 | }()
222 |
223 | /// Property to determine if current device has flash.
224 | open var hasFlash: Bool = {
225 | let hasFlashDevices = AVCaptureDevice.videoDevices.filter { $0.hasFlash }
226 | return !hasFlashDevices.isEmpty
227 | }()
228 |
229 | /**
230 | Property to enable or disable flip animation when switch between back and front camera.
231 | - note: Default value is **true**
232 | */
233 | open var animateCameraDeviceChange: Bool = true
234 |
235 | /**
236 | Property to enable or disable shutter animation when taking a picture.
237 | - note: Default value is **true**
238 | */
239 | open var animateShutter: Bool = true
240 |
241 | /**
242 | Property to enable or disable location services. Location services in camera is used for EXIF data.
243 | - note: Default value is **false**
244 | */
245 | open var shouldUseLocationServices: Bool = false {
246 | didSet {
247 | if shouldUseLocationServices {
248 | self.locationManager = CameraLocationManager()
249 | }
250 | }
251 | }
252 |
253 | /// Property to change camera device between front and back.
254 | open var cameraDevice: CameraDevice = .back {
255 | didSet {
256 | if cameraIsSetup, cameraDevice != oldValue {
257 | if animateCameraDeviceChange {
258 | _doFlipAnimation()
259 | }
260 | _updateCameraDevice(cameraDevice)
261 | _updateIlluminationMode(flashMode)
262 | _setupMaxZoomScale()
263 | _zoom(0)
264 | _orientationChanged()
265 | }
266 | }
267 | }
268 |
269 | /// Property to change camera flash mode.
270 | open var flashMode: CameraFlashMode = .off {
271 | didSet {
272 | if cameraIsSetup && flashMode != oldValue {
273 | _updateIlluminationMode(flashMode)
274 | }
275 | }
276 | }
277 |
278 | /// Property to change camera output quality.
279 | open var cameraOutputQuality: AVCaptureSession.Preset = .high {
280 | didSet {
281 | if cameraIsSetup && cameraOutputQuality != oldValue {
282 | _updateCameraQualityMode(cameraOutputQuality)
283 | }
284 | }
285 | }
286 |
287 | /// Property to change camera output.
288 | open var cameraOutputMode: CameraOutputMode = .stillImage {
289 | didSet {
290 | if cameraIsSetup {
291 | if cameraOutputMode != oldValue {
292 | _setupOutputMode(cameraOutputMode, oldCameraOutputMode: oldValue)
293 | }
294 | _setupMaxZoomScale()
295 | _zoom(0)
296 | }
297 | }
298 | }
299 |
300 | /// Property to check video recording duration when in progress.
301 | open var recordedDuration: CMTime { return movieOutput?.recordedDuration ?? CMTime.zero }
302 |
303 | /// Property to check video recording file size when in progress.
304 | open var recordedFileSize: Int64 { return movieOutput?.recordedFileSize ?? 0 }
305 |
306 | /// Property to set focus mode when tap to focus is used (_focusStart).
307 | open var focusMode: AVCaptureDevice.FocusMode = .continuousAutoFocus
308 |
309 | /// Property to set exposure mode when tap to focus is used (_focusStart).
310 | open var exposureMode: AVCaptureDevice.ExposureMode = .continuousAutoExposure
311 |
312 | /// Property to set video stabilisation mode during a video record session
313 | open var videoStabilisationMode: AVCaptureVideoStabilizationMode = .auto {
314 | didSet {
315 | if oldValue != videoStabilisationMode {
316 | _setupVideoConnection()
317 | }
318 | }
319 | }
320 |
321 | // Property to get the stabilization mode currently active
322 | open var activeVideoStabilisationMode: AVCaptureVideoStabilizationMode {
323 | if let movieOutput = movieOutput {
324 | for connection in movieOutput.connections {
325 | for port in connection.inputPorts {
326 | if port.mediaType == AVMediaType.video {
327 | let videoConnection = connection as AVCaptureConnection
328 | return videoConnection.activeVideoStabilizationMode
329 | }
330 | }
331 | }
332 | }
333 |
334 | return .off
335 | }
336 |
337 | // MARK: - Private properties
338 |
339 | fileprivate var locationManager: CameraLocationManager?
340 |
341 | fileprivate weak var embeddingView: UIView?
342 | fileprivate var videoCompletion: ((_ videoURL: URL?, _ error: NSError?) -> Void)?
343 |
344 | fileprivate var sessionQueue: DispatchQueue = DispatchQueue(label: "CameraSessionQueue", attributes: [])
345 |
346 | fileprivate lazy var frontCameraDevice: AVCaptureDevice? = {
347 | AVCaptureDevice.videoDevices.filter { $0.position == .front }.first
348 | }()
349 |
350 | fileprivate lazy var backCameraDevice: AVCaptureDevice? = {
351 | AVCaptureDevice.videoDevices.filter { $0.position == .back }.first
352 | }()
353 |
354 | fileprivate lazy var mic: AVCaptureDevice? = {
355 | AVCaptureDevice.default(for: AVMediaType.audio)
356 | }()
357 |
358 | fileprivate var stillImageOutput: AVCaptureStillImageOutput?
359 | fileprivate var movieOutput: AVCaptureMovieFileOutput?
360 | fileprivate var previewLayer: AVCaptureVideoPreviewLayer?
361 | fileprivate var library: PHPhotoLibrary?
362 |
363 | fileprivate var cameraIsSetup = false
364 | fileprivate var cameraIsObservingDeviceOrientation = false
365 |
366 | fileprivate var zoomScale = CGFloat(1.0)
367 | fileprivate var beginZoomScale = CGFloat(1.0)
368 | fileprivate var maxZoomScale = CGFloat(1.0)
369 |
370 | fileprivate func _tempFilePath() -> URL {
371 | let tempURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("tempMovie\(Date().timeIntervalSince1970)").appendingPathExtension("mp4")
372 | return tempURL
373 | }
374 |
375 | fileprivate var coreMotionManager: CMMotionManager!
376 |
377 | /// Real device orientation from DeviceMotion
378 | fileprivate var deviceOrientation: UIDeviceOrientation = .portrait
379 |
380 | // MARK: - CameraManager
381 |
382 | /**
383 | Inits a capture session and adds a preview layer to the given view. Preview layer bounds will automaticaly be set to match given view. Default session is initialized with still image output.
384 |
385 | :param: view The view you want to add the preview layer to
386 | :param: cameraOutputMode The mode you want capturesession to run image / video / video and microphone
387 | :param: completion Optional completion block
388 |
389 | :returns: Current state of the camera: Ready / AccessDenied / NoDeviceFound / NotDetermined.
390 | */
391 | @discardableResult open func addPreviewLayerToView(_ view: UIView) -> CameraState {
392 | return addPreviewLayerToView(view, newCameraOutputMode: cameraOutputMode)
393 | }
394 |
395 | @discardableResult open func addPreviewLayerToView(_ view: UIView, newCameraOutputMode: CameraOutputMode) -> CameraState {
396 | return addLayerPreviewToView(view, newCameraOutputMode: newCameraOutputMode, completion: nil)
397 | }
398 |
399 | @discardableResult open func addLayerPreviewToView(_ view: UIView, newCameraOutputMode: CameraOutputMode, completion: (() -> Void)?) -> CameraState {
400 | if _canLoadCamera() {
401 | if let _ = embeddingView {
402 | if let validPreviewLayer = previewLayer {
403 | validPreviewLayer.removeFromSuperlayer()
404 | }
405 | }
406 | if cameraIsSetup {
407 | _addPreviewLayerToView(view)
408 | cameraOutputMode = newCameraOutputMode
409 | if let validCompletion = completion {
410 | validCompletion()
411 | }
412 | } else {
413 | _setupCamera {
414 | self._addPreviewLayerToView(view)
415 | self.cameraOutputMode = newCameraOutputMode
416 | if let validCompletion = completion {
417 | validCompletion()
418 | }
419 | }
420 | }
421 | }
422 | return _checkIfCameraIsAvailable()
423 | }
424 |
425 | /**
426 | Zoom in to the requested scale.
427 | */
428 | open func zoom(_ scale: CGFloat) {
429 | _zoom(scale)
430 | }
431 |
432 | /**
433 | Asks the user for camera permissions. Only works if the permissions are not yet determined. Note that it'll also automaticaly ask about the microphone permissions if you selected VideoWithMic output.
434 |
435 | :param: completion Completion block with the result of permission request
436 | */
437 | open func askUserForCameraPermission(_ completion: @escaping (Bool) -> Void) {
438 | AVCaptureDevice.requestAccess(for: AVMediaType.video, completionHandler: { (allowedAccess) -> Void in
439 | if self.cameraOutputMode == .videoWithMic {
440 | AVCaptureDevice.requestAccess(for: AVMediaType.audio, completionHandler: { (allowedAccess) -> Void in
441 | DispatchQueue.main.async { () -> Void in
442 | completion(allowedAccess)
443 | }
444 | })
445 | } else {
446 | DispatchQueue.main.async { () -> Void in
447 | completion(allowedAccess)
448 | }
449 | }
450 | })
451 | }
452 |
453 | /**
454 | Stops running capture session but all setup devices, inputs and outputs stay for further reuse.
455 | */
456 | open func stopCaptureSession() {
457 | captureSession?.stopRunning()
458 | _stopFollowingDeviceOrientation()
459 | }
460 |
461 | /**
462 | Resumes capture session.
463 | */
464 | open func resumeCaptureSession() {
465 | if let validCaptureSession = captureSession {
466 | if !validCaptureSession.isRunning, cameraIsSetup {
467 | sessionQueue.async {
468 | validCaptureSession.startRunning()
469 | self._startFollowingDeviceOrientation()
470 | }
471 | }
472 | } else {
473 | if _canLoadCamera() {
474 | if cameraIsSetup {
475 | stopAndRemoveCaptureSession()
476 | }
477 | _setupCamera {
478 | if let validEmbeddingView = self.embeddingView {
479 | self._addPreviewLayerToView(validEmbeddingView)
480 | }
481 | self._startFollowingDeviceOrientation()
482 | }
483 | }
484 | }
485 | }
486 |
487 | /**
488 | Stops running capture session and removes all setup devices, inputs and outputs.
489 | */
490 | open func stopAndRemoveCaptureSession() {
491 | stopCaptureSession()
492 | let oldAnimationValue = animateCameraDeviceChange
493 | animateCameraDeviceChange = false
494 | cameraDevice = .back
495 | cameraIsSetup = false
496 | previewLayer = nil
497 | captureSession = nil
498 | frontCameraDevice = nil
499 | backCameraDevice = nil
500 | mic = nil
501 | stillImageOutput = nil
502 | movieOutput = nil
503 | animateCameraDeviceChange = oldAnimationValue
504 | }
505 |
506 | /**
507 | Captures still image from currently running capture session.
508 |
509 | :param: imageCompletion Completion block containing the captured UIImage
510 | */
511 | @available(*, deprecated)
512 | open func capturePictureWithCompletion(_ imageCompletion: @escaping (UIImage?, NSError?) -> Void) {
513 | func completion(_ result: CaptureResult) {
514 | switch result {
515 | case let .success(content):
516 | imageCompletion(content.asImage, nil)
517 | case .failure:
518 | imageCompletion(nil, NSError())
519 | }
520 | }
521 |
522 | capturePictureWithCompletion(completion)
523 | }
524 |
525 | /**
526 | Captures still image from currently running capture session.
527 |
528 | :param: imageCompletion Completion block containing the captured UIImage
529 | */
530 | open func capturePictureWithCompletion(_ imageCompletion: @escaping (CaptureResult) -> Void) {
531 | capturePictureDataWithCompletion { result in
532 |
533 | guard let imageData = result.imageData else {
534 | if case let .failure(error) = result {
535 | imageCompletion(.failure(error))
536 | } else {
537 | imageCompletion(.failure(CaptureError.noImageData))
538 | }
539 |
540 | return
541 | }
542 |
543 | if self.animateShutter {
544 | self._performShutterAnimation {
545 | self._capturePicture(imageData, imageCompletion)
546 | }
547 | } else {
548 | self._capturePicture(imageData, imageCompletion)
549 | }
550 | }
551 | }
552 |
553 | fileprivate func _capturePicture(_ imageData: Data, _ imageCompletion: @escaping (CaptureResult) -> Void) {
554 | guard let img = UIImage(data: imageData) else {
555 | imageCompletion(.failure(NSError()))
556 | return
557 | }
558 |
559 | let image = fixOrientation(withImage: img)
560 | let newImageData = _imageDataWithEXIF(forImage: image, imageData)! as Data
561 |
562 | if writeFilesToPhoneLibrary {
563 | let filePath = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("tempImg\(Int(Date().timeIntervalSince1970)).jpg")
564 |
565 | do {
566 | try newImageData.write(to: filePath)
567 |
568 | // make sure that doesn't fail the first time
569 | if PHPhotoLibrary.authorizationStatus() != .authorized {
570 | PHPhotoLibrary.requestAuthorization { status in
571 | if status == PHAuthorizationStatus.authorized {
572 | self._saveImageToLibrary(atFileURL: filePath, imageCompletion)
573 | }
574 | }
575 | } else {
576 | _saveImageToLibrary(atFileURL: filePath, imageCompletion)
577 | }
578 |
579 | } catch {
580 | imageCompletion(.failure(error))
581 | return
582 | }
583 | }
584 |
585 | imageCompletion(CaptureResult(newImageData))
586 | }
587 |
588 | fileprivate func _setVideoWithGPS(forLocation location: CLLocation) {
589 | let metadata = AVMutableMetadataItem()
590 | metadata.keySpace = AVMetadataKeySpace.quickTimeMetadata
591 | metadata.key = AVMetadataKey.quickTimeMetadataKeyLocationISO6709 as NSString
592 | metadata.identifier = AVMetadataIdentifier.quickTimeMetadataLocationISO6709
593 | metadata.value = String(format: "%+09.5f%+010.5f%+.0fCRSWGS_84", location.coordinate.latitude, location.coordinate.longitude, location.altitude) as NSString
594 | _getMovieOutput().metadata = [metadata]
595 | }
596 |
597 | fileprivate func _imageDataWithEXIF(forImage _: UIImage, _ data: Data) -> NSData? {
598 | let cfdata: CFData = data as CFData
599 | let source = CGImageSourceCreateWithData(cfdata, nil)!
600 | let UTI: CFString = CGImageSourceGetType(source)!
601 | let mutableData: CFMutableData = NSMutableData(data: data) as CFMutableData
602 | let destination = CGImageDestinationCreateWithData(mutableData, UTI, 1, nil)!
603 |
604 | let imageSourceRef = CGImageSourceCreateWithData(cfdata, nil)
605 | let imageProperties = CGImageSourceCopyMetadataAtIndex(imageSourceRef!, 0, nil)!
606 |
607 | var mutableMetadata = CGImageMetadataCreateMutableCopy(imageProperties)!
608 |
609 | if let location = locationManager?.latestLocation {
610 | mutableMetadata = _gpsMetadata(mutableMetadata, withLocation: location)
611 | }
612 |
613 | let finalMetadata: CGImageMetadata = mutableMetadata
614 | CGImageDestinationAddImageAndMetadata(destination, UIImage(data: data)!.cgImage!, finalMetadata, nil)
615 | CGImageDestinationFinalize(destination)
616 | return mutableData
617 | }
618 |
619 | fileprivate func _gpsMetadata(_ imageMetadata: CGMutableImageMetadata, withLocation location: CLLocation) -> CGMutableImageMetadata {
620 | let altitudeRef = Int(location.altitude < 0.0 ? 1 : 0)
621 | let latitudeRef = location.coordinate.latitude < 0.0 ? "S" : "N"
622 | let longitudeRef = location.coordinate.longitude < 0.0 ? "W" : "E"
623 |
624 | let f = DateFormatter()
625 | f.timeZone = TimeZone(abbreviation: "UTC")
626 |
627 | f.dateFormat = "yyyy:MM:dd"
628 | let isoDate = f.string(from: location.timestamp)
629 |
630 | f.dateFormat = "HH:mm:ss.SSSSSS"
631 | let isoTime = f.string(from: location.timestamp)
632 |
633 | CGImageMetadataSetValueMatchingImageProperty(imageMetadata, kCGImagePropertyGPSDictionary, kCGImagePropertyGPSLatitudeRef, latitudeRef as CFTypeRef)
634 | CGImageMetadataSetValueMatchingImageProperty(imageMetadata, kCGImagePropertyGPSDictionary, kCGImagePropertyGPSLatitude, abs(location.coordinate.latitude) as CFTypeRef)
635 | CGImageMetadataSetValueMatchingImageProperty(imageMetadata, kCGImagePropertyGPSDictionary, kCGImagePropertyGPSLongitudeRef, longitudeRef as CFTypeRef)
636 | CGImageMetadataSetValueMatchingImageProperty(imageMetadata, kCGImagePropertyGPSDictionary, kCGImagePropertyGPSLongitude, abs(location.coordinate.longitude) as CFTypeRef)
637 | CGImageMetadataSetValueMatchingImageProperty(imageMetadata, kCGImagePropertyGPSDictionary, kCGImagePropertyGPSAltitude, Int(abs(location.altitude)) as CFTypeRef)
638 | CGImageMetadataSetValueMatchingImageProperty(imageMetadata, kCGImagePropertyGPSDictionary, kCGImagePropertyGPSAltitudeRef, altitudeRef as CFTypeRef)
639 | CGImageMetadataSetValueMatchingImageProperty(imageMetadata, kCGImagePropertyGPSDictionary, kCGImagePropertyGPSTimeStamp, isoTime as CFTypeRef)
640 | CGImageMetadataSetValueMatchingImageProperty(imageMetadata, kCGImagePropertyGPSDictionary, kCGImagePropertyGPSDateStamp, isoDate as CFTypeRef)
641 |
642 | return imageMetadata
643 | }
644 |
645 | fileprivate func _saveImageToLibrary(atFileURL filePath: URL, _ imageCompletion: @escaping (CaptureResult) -> Void) {
646 | let location = locationManager?.latestLocation
647 | let date = Date()
648 |
649 | library?.save(imageAtURL: filePath, albumName: imageAlbumName, date: date, location: location) { asset in
650 |
651 | guard let _ = asset else {
652 | return imageCompletion(.failure(CaptureError.assetNotSaved))
653 | }
654 | }
655 | }
656 |
657 | /**
658 | Captures still image from currently running capture session.
659 |
660 | :param: imageCompletion Completion block containing the captured imageData
661 | */
662 | @available(*, deprecated)
663 | open func capturePictureDataWithCompletion(_ imageCompletion: @escaping (Data?, NSError?) -> Void) {
664 | func completion(_ result: CaptureResult) {
665 | switch result {
666 | case let .success(content):
667 | imageCompletion(content.asData, nil)
668 | case .failure:
669 | imageCompletion(nil, NSError())
670 | }
671 | }
672 | capturePictureDataWithCompletion(completion)
673 | }
674 |
675 | /**
676 | Captures still image from currently running capture session.
677 |
678 | :param: imageCompletion Completion block containing the captured imageData
679 | */
680 | open func capturePictureDataWithCompletion(_ imageCompletion: @escaping (CaptureResult) -> Void) {
681 | guard cameraIsSetup else {
682 | _show(NSLocalizedString("No capture session setup", comment: ""), message: NSLocalizedString("I can't take any picture", comment: ""))
683 | return
684 | }
685 |
686 | guard cameraOutputMode == .stillImage else {
687 | _show(NSLocalizedString("Capture session output mode video", comment: ""), message: NSLocalizedString("I can't take any picture", comment: ""))
688 | return
689 | }
690 |
691 | _updateIlluminationMode(flashMode)
692 |
693 | sessionQueue.async {
694 | let stillImageOutput = self._getStillImageOutput()
695 | if let connection = stillImageOutput.connection(with: AVMediaType.video),
696 | connection.isEnabled {
697 | if self.cameraDevice == CameraDevice.front, connection.isVideoMirroringSupported,
698 | self.shouldFlipFrontCameraImage {
699 | connection.isVideoMirrored = true
700 | }
701 | if connection.isVideoOrientationSupported {
702 | connection.videoOrientation = self._currentCaptureVideoOrientation()
703 | }
704 |
705 | stillImageOutput.captureStillImageAsynchronously(from: connection, completionHandler: { [weak self] sample, error in
706 |
707 | if let error = error {
708 | self?._show(NSLocalizedString("Error", comment: ""), message: error.localizedDescription)
709 | imageCompletion(.failure(error))
710 | return
711 | }
712 |
713 | guard let sample = sample else { imageCompletion(.failure(CaptureError.noSampleBuffer)); return }
714 | if let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(sample) {
715 | imageCompletion(CaptureResult(imageData))
716 | } else {
717 | imageCompletion(.failure(CaptureError.noImageData))
718 | }
719 |
720 | })
721 | } else {
722 | imageCompletion(.failure(CaptureError.noVideoConnection))
723 | }
724 | }
725 | }
726 |
727 | fileprivate func _imageOrientation(forDeviceOrientation deviceOrientation: UIDeviceOrientation, isMirrored: Bool) -> UIImage.Orientation {
728 | switch deviceOrientation {
729 | case .landscapeLeft:
730 | return isMirrored ? .upMirrored : .up
731 | case .landscapeRight:
732 | return isMirrored ? .downMirrored : .down
733 | default:
734 | break
735 | }
736 |
737 | return isMirrored ? .leftMirrored : .right
738 | }
739 |
740 | /**
741 | Starts recording a video with or without voice as in the session preset.
742 | */
743 | open func startRecordingVideo() {
744 | guard cameraOutputMode != .stillImage else {
745 | _show(NSLocalizedString("Capture session output still image", comment: ""), message: NSLocalizedString("I can only take pictures", comment: ""))
746 | return
747 | }
748 |
749 | let videoOutput = _getMovieOutput()
750 |
751 | if shouldUseLocationServices {
752 |
753 | let specs = [kCMMetadataFormatDescriptionMetadataSpecificationKey_Identifier as String: AVMetadataIdentifier.quickTimeMetadataLocationISO6709,
754 | kCMMetadataFormatDescriptionMetadataSpecificationKey_DataType as String: kCMMetadataDataType_QuickTimeMetadataLocation_ISO6709 as String] as [String: Any]
755 |
756 | var locationMetadataDesc: CMFormatDescription?
757 | CMMetadataFormatDescriptionCreateWithMetadataSpecifications(allocator: kCFAllocatorDefault, metadataType: kCMMetadataFormatType_Boxed, metadataSpecifications: [specs] as CFArray, formatDescriptionOut: &locationMetadataDesc)
758 |
759 | // Create the metadata input and add it to the session.
760 | guard let captureSession = captureSession, let locationMetadata = locationMetadataDesc else {
761 | return
762 | }
763 |
764 | let newLocationMetadataInput = AVCaptureMetadataInput(formatDescription: locationMetadata, clock: CMClockGetHostTimeClock())
765 | captureSession.addInputWithNoConnections(newLocationMetadataInput)
766 |
767 | // Connect the location metadata input to the movie file output.
768 | let inputPort = newLocationMetadataInput.ports[0]
769 | captureSession.addConnection(AVCaptureConnection(inputPorts: [inputPort], output: videoOutput))
770 |
771 | }
772 |
773 | _updateIlluminationMode(flashMode)
774 |
775 | videoOutput.startRecording(to: _tempFilePath(), recordingDelegate: self)
776 | }
777 |
778 | /**
779 | Stop recording a video. Save it to the cameraRoll and give back the url.
780 | */
781 | open func stopVideoRecording(_ completion: ((_ videoURL: URL?, _ error: NSError?) -> Void)?) {
782 | if let runningMovieOutput = movieOutput,
783 | runningMovieOutput.isRecording {
784 | videoCompletion = completion
785 | runningMovieOutput.stopRecording()
786 | }
787 | }
788 |
789 | /**
790 | The signature for a handler.
791 | The success value is the string representation of a scanned QR code, if any.
792 | */
793 | public typealias QRCodeDetectionHandler = (Result) -> Void
794 |
795 | /**
796 | Start detecting QR codes.
797 | */
798 | open func startQRCodeDetection(_ handler: @escaping QRCodeDetectionHandler) {
799 | guard let captureSession = self.captureSession
800 | else { return }
801 |
802 | let output = AVCaptureMetadataOutput()
803 |
804 | guard captureSession.canAddOutput(output)
805 | else { return }
806 |
807 | qrCodeDetectionHandler = handler
808 | captureSession.addOutput(output)
809 |
810 | // Note: The object types must be set after the output was added to the capture session.
811 | output.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
812 | output.metadataObjectTypes = [.qr, .ean8, .ean13, .pdf417].filter { output.availableMetadataObjectTypes.contains($0) }
813 | }
814 |
815 | /**
816 | Stop detecting QR codes.
817 | */
818 | open func stopQRCodeDetection() {
819 | qrCodeDetectionHandler = nil
820 |
821 | if let output = qrOutput {
822 | captureSession?.removeOutput(output)
823 | }
824 | qrOutput = nil
825 | }
826 |
827 | /**
828 | The stored handler for QR codes.
829 | */
830 | private var qrCodeDetectionHandler: QRCodeDetectionHandler?
831 |
832 | /**
833 | The stored meta data output; used to detect QR codes.
834 | */
835 | private var qrOutput: AVCaptureOutput?
836 |
837 | /**
838 | Check if the device rotation is locked
839 | */
840 | open func deviceOrientationMatchesInterfaceOrientation() -> Bool {
841 | return deviceOrientation == UIDevice.current.orientation
842 | }
843 |
844 | /**
845 | Current camera status.
846 |
847 | :returns: Current state of the camera: Ready / AccessDenied / NoDeviceFound / NotDetermined
848 | */
849 | open func currentCameraStatus() -> CameraState {
850 | return _checkIfCameraIsAvailable()
851 | }
852 |
853 | /**
854 | Change current flash mode to next value from available ones.
855 |
856 | :returns: Current flash mode: Off / On / Auto
857 | */
858 | open func changeFlashMode() -> CameraFlashMode {
859 | guard let newFlashMode = CameraFlashMode(rawValue: (flashMode.rawValue + 1) % 3) else { return flashMode }
860 | flashMode = newFlashMode
861 | return flashMode
862 | }
863 |
864 | /**
865 | Check the camera device has flash
866 | */
867 | open func hasFlash(for cameraDevice: CameraDevice) -> Bool {
868 | let devices = AVCaptureDevice.videoDevices
869 | for device in devices {
870 | if device.position == .back, cameraDevice == .back {
871 | return device.hasFlash
872 | } else if device.position == .front, cameraDevice == .front {
873 | return device.hasFlash
874 | }
875 | }
876 | return false
877 | }
878 |
879 | // MARK: - AVCaptureFileOutputRecordingDelegate
880 |
881 | public func fileOutput(_: AVCaptureFileOutput, didStartRecordingTo _: URL, from _: [AVCaptureConnection]) {
882 | captureSession?.beginConfiguration()
883 | if flashMode != .off {
884 | _updateIlluminationMode(flashMode)
885 | }
886 |
887 | captureSession?.commitConfiguration()
888 | }
889 |
890 | open func fileOutput(_: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from _: [AVCaptureConnection], error: Error?) {
891 | if let error = error {
892 | _show(NSLocalizedString("Unable to save video to the device", comment: ""), message: error.localizedDescription)
893 | } else {
894 | if writeFilesToPhoneLibrary {
895 | if PHPhotoLibrary.authorizationStatus() == .authorized {
896 | _saveVideoToLibrary(outputFileURL)
897 | } else {
898 | PHPhotoLibrary.requestAuthorization { autorizationStatus in
899 | if autorizationStatus == .authorized {
900 | self._saveVideoToLibrary(outputFileURL)
901 | }
902 | }
903 | }
904 | } else {
905 | _executeVideoCompletionWithURL(outputFileURL, error: error as NSError?)
906 | }
907 | }
908 | }
909 |
910 | fileprivate func _saveVideoToLibrary(_ fileURL: URL) {
911 | let location = locationManager?.latestLocation
912 | let date = Date()
913 |
914 | library?.save(videoAtURL: fileURL, albumName: videoAlbumName, date: date, location: location, completion: { _ in
915 | self._executeVideoCompletionWithURL(fileURL, error: nil)
916 | })
917 | }
918 |
919 | // MARK: - UIGestureRecognizerDelegate
920 |
921 | fileprivate lazy var zoomGesture = UIPinchGestureRecognizer()
922 |
923 | fileprivate func attachZoom(_ view: UIView) {
924 | DispatchQueue.main.async {
925 | self.zoomGesture.addTarget(self, action: #selector(CameraManager._zoomStart(_:)))
926 | view.addGestureRecognizer(self.zoomGesture)
927 | self.zoomGesture.delegate = self
928 | }
929 | }
930 |
931 | open func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
932 | if gestureRecognizer.isKind(of: UIPinchGestureRecognizer.self) {
933 | beginZoomScale = zoomScale
934 | }
935 |
936 | return true
937 | }
938 |
939 | @objc fileprivate func _zoomStart(_ recognizer: UIPinchGestureRecognizer) {
940 | guard let view = embeddingView,
941 | let previewLayer = previewLayer
942 | else { return }
943 |
944 | var allTouchesOnPreviewLayer = true
945 | let numTouch = recognizer.numberOfTouches
946 |
947 | for i in 0 ..< numTouch {
948 | let location = recognizer.location(ofTouch: i, in: view)
949 | let convertedTouch = previewLayer.convert(location, from: previewLayer.superlayer)
950 | if !previewLayer.contains(convertedTouch) {
951 | allTouchesOnPreviewLayer = false
952 | break
953 | }
954 | }
955 | if allTouchesOnPreviewLayer {
956 | _zoom(recognizer.scale)
957 | }
958 | }
959 |
960 | fileprivate func _zoom(_ scale: CGFloat) {
961 | let device: AVCaptureDevice?
962 |
963 | switch cameraDevice {
964 | case .back:
965 | device = backCameraDevice
966 | case .front:
967 | device = frontCameraDevice
968 | }
969 |
970 | do {
971 | let captureDevice = device
972 | try captureDevice?.lockForConfiguration()
973 |
974 | zoomScale = max(1.0, min(beginZoomScale * scale, maxZoomScale))
975 |
976 | captureDevice?.videoZoomFactor = zoomScale
977 |
978 | captureDevice?.unlockForConfiguration()
979 |
980 | } catch {
981 | print("Error locking configuration")
982 | }
983 | }
984 |
985 | // MARK: - UIGestureRecognizerDelegate
986 |
987 | fileprivate lazy var focusGesture = UITapGestureRecognizer()
988 |
989 | fileprivate func attachFocus(_ view: UIView) {
990 | DispatchQueue.main.async {
991 | self.focusGesture.addTarget(self, action: #selector(CameraManager._focusStart(_:)))
992 | view.addGestureRecognizer(self.focusGesture)
993 | self.focusGesture.delegate = self
994 | }
995 | }
996 |
997 | fileprivate lazy var exposureGesture = UIPanGestureRecognizer()
998 |
999 | fileprivate func attachExposure(_ view: UIView) {
1000 | DispatchQueue.main.async {
1001 | self.exposureGesture.addTarget(self, action: #selector(CameraManager._exposureStart(_:)))
1002 | view.addGestureRecognizer(self.exposureGesture)
1003 | self.exposureGesture.delegate = self
1004 | }
1005 | }
1006 |
1007 | @objc fileprivate func _focusStart(_ recognizer: UITapGestureRecognizer) {
1008 | let device: AVCaptureDevice?
1009 |
1010 | switch cameraDevice {
1011 | case .back:
1012 | device = backCameraDevice
1013 | case .front:
1014 | device = frontCameraDevice
1015 | }
1016 |
1017 | _changeExposureMode(mode: .continuousAutoExposure)
1018 | translationY = 0
1019 | exposureValue = 0.5
1020 |
1021 | if let validDevice = device,
1022 | let validPreviewLayer = previewLayer,
1023 | let view = recognizer.view {
1024 | let pointInPreviewLayer = view.layer.convert(recognizer.location(in: view), to: validPreviewLayer)
1025 | let pointOfInterest = validPreviewLayer.captureDevicePointConverted(fromLayerPoint: pointInPreviewLayer)
1026 |
1027 | do {
1028 | try validDevice.lockForConfiguration()
1029 |
1030 | _showFocusRectangleAtPoint(pointInPreviewLayer, inLayer: validPreviewLayer)
1031 |
1032 | if validDevice.isFocusPointOfInterestSupported {
1033 | validDevice.focusPointOfInterest = pointOfInterest
1034 | }
1035 |
1036 | if validDevice.isExposurePointOfInterestSupported {
1037 | validDevice.exposurePointOfInterest = pointOfInterest
1038 | }
1039 |
1040 | if validDevice.isFocusModeSupported(focusMode) {
1041 | validDevice.focusMode = focusMode
1042 | }
1043 |
1044 | if validDevice.isExposureModeSupported(exposureMode) {
1045 | validDevice.exposureMode = exposureMode
1046 | }
1047 |
1048 | validDevice.unlockForConfiguration()
1049 | } catch {
1050 | print(error)
1051 | }
1052 | }
1053 | }
1054 |
1055 | fileprivate var lastFocusRectangle: CAShapeLayer?
1056 | fileprivate var lastFocusPoint: CGPoint?
1057 | fileprivate func _showFocusRectangleAtPoint(_ focusPoint: CGPoint, inLayer layer: CALayer, withBrightness brightness: Float? = nil) {
1058 | if let lastFocusRectangle = lastFocusRectangle {
1059 | lastFocusRectangle.removeFromSuperlayer()
1060 | self.lastFocusRectangle = nil
1061 | }
1062 |
1063 | let size = CGSize(width: 75, height: 75)
1064 | let rect = CGRect(origin: CGPoint(x: focusPoint.x - size.width / 2.0, y: focusPoint.y - size.height / 2.0), size: size)
1065 |
1066 | let endPath = UIBezierPath(rect: rect)
1067 | endPath.move(to: CGPoint(x: rect.minX + size.width / 2.0, y: rect.minY))
1068 | endPath.addLine(to: CGPoint(x: rect.minX + size.width / 2.0, y: rect.minY + 5.0))
1069 | endPath.move(to: CGPoint(x: rect.maxX, y: rect.minY + size.height / 2.0))
1070 | endPath.addLine(to: CGPoint(x: rect.maxX - 5.0, y: rect.minY + size.height / 2.0))
1071 | endPath.move(to: CGPoint(x: rect.minX + size.width / 2.0, y: rect.maxY))
1072 | endPath.addLine(to: CGPoint(x: rect.minX + size.width / 2.0, y: rect.maxY - 5.0))
1073 | endPath.move(to: CGPoint(x: rect.minX, y: rect.minY + size.height / 2.0))
1074 | endPath.addLine(to: CGPoint(x: rect.minX + 5.0, y: rect.minY + size.height / 2.0))
1075 | if brightness != nil {
1076 | endPath.move(to: CGPoint(x: rect.minX + size.width + size.width / 4, y: rect.minY))
1077 | endPath.addLine(to: CGPoint(x: rect.minX + size.width + size.width / 4, y: rect.minY + size.height))
1078 |
1079 | endPath.move(to: CGPoint(x: rect.minX + size.width + size.width / 4 - size.width / 16, y: rect.minY + size.height - CGFloat(brightness!) * size.height))
1080 | endPath.addLine(to: CGPoint(x: rect.minX + size.width + size.width / 4 + size.width / 16, y: rect.minY + size.height - CGFloat(brightness!) * size.height))
1081 | }
1082 |
1083 | let startPath = UIBezierPath(cgPath: endPath.cgPath)
1084 | let scaleAroundCenterTransform = CGAffineTransform(translationX: -focusPoint.x, y: -focusPoint.y).concatenating(CGAffineTransform(scaleX: 2.0, y: 2.0).concatenating(CGAffineTransform(translationX: focusPoint.x, y: focusPoint.y)))
1085 | startPath.apply(scaleAroundCenterTransform)
1086 |
1087 | let shapeLayer = CAShapeLayer()
1088 | shapeLayer.path = endPath.cgPath
1089 | shapeLayer.fillColor = UIColor.clear.cgColor
1090 | shapeLayer.strokeColor = UIColor(red: 1, green: 0.83, blue: 0, alpha: 0.95).cgColor
1091 | shapeLayer.lineWidth = 1.0
1092 |
1093 | layer.addSublayer(shapeLayer)
1094 | lastFocusRectangle = shapeLayer
1095 | lastFocusPoint = focusPoint
1096 |
1097 | CATransaction.begin()
1098 |
1099 | CATransaction.setAnimationDuration(0.2)
1100 | CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut))
1101 |
1102 | CATransaction.setCompletionBlock {
1103 | if shapeLayer.superlayer != nil {
1104 | shapeLayer.removeFromSuperlayer()
1105 | self.lastFocusRectangle = nil
1106 | }
1107 | }
1108 | if brightness == nil {
1109 | let appearPathAnimation = CABasicAnimation(keyPath: "path")
1110 | appearPathAnimation.fromValue = startPath.cgPath
1111 | appearPathAnimation.toValue = endPath.cgPath
1112 | shapeLayer.add(appearPathAnimation, forKey: "path")
1113 |
1114 | let appearOpacityAnimation = CABasicAnimation(keyPath: "opacity")
1115 | appearOpacityAnimation.fromValue = 0.0
1116 | appearOpacityAnimation.toValue = 1.0
1117 | shapeLayer.add(appearOpacityAnimation, forKey: "opacity")
1118 | }
1119 |
1120 | let disappearOpacityAnimation = CABasicAnimation(keyPath: "opacity")
1121 | disappearOpacityAnimation.fromValue = 1.0
1122 | disappearOpacityAnimation.toValue = 0.0
1123 | disappearOpacityAnimation.beginTime = CACurrentMediaTime() + 0.8
1124 | disappearOpacityAnimation.fillMode = CAMediaTimingFillMode.forwards
1125 | disappearOpacityAnimation.isRemovedOnCompletion = false
1126 | shapeLayer.add(disappearOpacityAnimation, forKey: "opacity")
1127 |
1128 | CATransaction.commit()
1129 | }
1130 |
1131 | var exposureValue: Float = 0.1 // EV
1132 | var translationY: Float = 0
1133 | var startPanPointInPreviewLayer: CGPoint?
1134 |
1135 | let exposureDurationPower: Float = 4.0 // the exposure slider gain
1136 | let exposureMininumDuration: Float64 = 1.0 / 2000.0
1137 |
1138 | @objc fileprivate func _exposureStart(_ gestureRecognizer: UIPanGestureRecognizer) {
1139 | guard gestureRecognizer.view != nil else { return }
1140 | let view = gestureRecognizer.view!
1141 |
1142 | _changeExposureMode(mode: .custom)
1143 |
1144 | let translation = gestureRecognizer.translation(in: view)
1145 | let currentTranslation = translationY + Float(translation.y)
1146 | if gestureRecognizer.state == .ended {
1147 | translationY = currentTranslation
1148 | }
1149 | if currentTranslation < 0 {
1150 | // up - brighter
1151 | exposureValue = 0.5 + min(abs(currentTranslation) / 400, 1) / 2
1152 | } else if currentTranslation >= 0 {
1153 | // down - lower
1154 | exposureValue = 0.5 - min(abs(currentTranslation) / 400, 1) / 2
1155 | }
1156 | _changeExposureDuration(value: exposureValue)
1157 |
1158 | // UI Visualization
1159 | if gestureRecognizer.state == .began {
1160 | if let validPreviewLayer = previewLayer {
1161 | startPanPointInPreviewLayer = view.layer.convert(gestureRecognizer.location(in: view), to: validPreviewLayer)
1162 | }
1163 | }
1164 |
1165 | if let validPreviewLayer = previewLayer, let lastFocusPoint = self.lastFocusPoint {
1166 | _showFocusRectangleAtPoint(lastFocusPoint, inLayer: validPreviewLayer, withBrightness: exposureValue)
1167 | }
1168 | }
1169 |
1170 | // Available modes:
1171 | // .Locked .AutoExpose .ContinuousAutoExposure .Custom
1172 | func _changeExposureMode(mode: AVCaptureDevice.ExposureMode) {
1173 | let device: AVCaptureDevice?
1174 |
1175 | switch cameraDevice {
1176 | case .back:
1177 | device = backCameraDevice
1178 | case .front:
1179 | device = frontCameraDevice
1180 | }
1181 | if device?.exposureMode == mode {
1182 | return
1183 | }
1184 |
1185 | do {
1186 | try device?.lockForConfiguration()
1187 |
1188 | if device?.isExposureModeSupported(mode) == true {
1189 | device?.exposureMode = mode
1190 | }
1191 | device?.unlockForConfiguration()
1192 |
1193 | } catch {
1194 | return
1195 | }
1196 | }
1197 |
1198 | func _changeExposureDuration(value: Float) {
1199 | if cameraIsSetup {
1200 | let device: AVCaptureDevice?
1201 |
1202 | switch cameraDevice {
1203 | case .back:
1204 | device = backCameraDevice
1205 | case .front:
1206 | device = frontCameraDevice
1207 | }
1208 |
1209 | guard let videoDevice = device else {
1210 | return
1211 | }
1212 |
1213 | do {
1214 | try videoDevice.lockForConfiguration()
1215 |
1216 | let p = Float64(pow(value, exposureDurationPower)) // Apply power function to expand slider's low-end range
1217 | let minDurationSeconds = Float64(max(CMTimeGetSeconds(videoDevice.activeFormat.minExposureDuration), exposureMininumDuration))
1218 | let maxDurationSeconds = Float64(CMTimeGetSeconds(videoDevice.activeFormat.maxExposureDuration))
1219 | let newDurationSeconds = Float64(p * (maxDurationSeconds - minDurationSeconds)) + minDurationSeconds // Scale from 0-1 slider range to actual duration
1220 |
1221 | if videoDevice.exposureMode == .custom {
1222 | let newExposureTime = CMTimeMakeWithSeconds(Float64(newDurationSeconds), preferredTimescale: 1000 * 1000 * 1000)
1223 | videoDevice.setExposureModeCustom(duration: newExposureTime, iso: AVCaptureDevice.currentISO, completionHandler: nil)
1224 | }
1225 |
1226 | videoDevice.unlockForConfiguration()
1227 | } catch {
1228 | return
1229 | }
1230 | }
1231 | }
1232 |
1233 | // MARK: - CameraManager()
1234 |
1235 | fileprivate func _executeVideoCompletionWithURL(_ url: URL?, error: NSError?) {
1236 | if let validCompletion = videoCompletion {
1237 | validCompletion(url, error)
1238 | videoCompletion = nil
1239 | }
1240 | }
1241 |
1242 | fileprivate func _getMovieOutput() -> AVCaptureMovieFileOutput {
1243 | if movieOutput == nil {
1244 | _createMovieOutput()
1245 | }
1246 |
1247 | return movieOutput!
1248 | }
1249 |
1250 | fileprivate func _createMovieOutput() {
1251 |
1252 | let newMovieOutput = AVCaptureMovieFileOutput()
1253 | newMovieOutput.movieFragmentInterval = CMTime.invalid
1254 |
1255 | movieOutput = newMovieOutput
1256 |
1257 | _setupVideoConnection()
1258 |
1259 | if let captureSession = captureSession, captureSession.canAddOutput(newMovieOutput) {
1260 | captureSession.beginConfiguration()
1261 | captureSession.addOutput(newMovieOutput)
1262 | captureSession.commitConfiguration()
1263 | }
1264 | }
1265 |
1266 | fileprivate func _setupVideoConnection() {
1267 | if let movieOutput = movieOutput {
1268 | for connection in movieOutput.connections {
1269 | for port in connection.inputPorts {
1270 | if port.mediaType == AVMediaType.video {
1271 | let videoConnection = connection as AVCaptureConnection
1272 | // setup video mirroring
1273 | if videoConnection.isVideoMirroringSupported {
1274 | videoConnection.isVideoMirrored = (cameraDevice == CameraDevice.front && shouldFlipFrontCameraImage)
1275 | }
1276 |
1277 | if videoConnection.isVideoStabilizationSupported {
1278 | videoConnection.preferredVideoStabilizationMode = videoStabilisationMode
1279 | }
1280 | }
1281 | }
1282 | }
1283 | }
1284 | }
1285 |
1286 | fileprivate func _getStillImageOutput() -> AVCaptureStillImageOutput {
1287 | if let stillImageOutput = stillImageOutput, let connection = stillImageOutput.connection(with: AVMediaType.video),
1288 | connection.isActive {
1289 | return stillImageOutput
1290 | }
1291 | let newStillImageOutput = AVCaptureStillImageOutput()
1292 | stillImageOutput = newStillImageOutput
1293 | if let captureSession = captureSession,
1294 | captureSession.canAddOutput(newStillImageOutput) {
1295 | captureSession.beginConfiguration()
1296 | captureSession.addOutput(newStillImageOutput)
1297 | captureSession.commitConfiguration()
1298 | }
1299 | return newStillImageOutput
1300 | }
1301 |
1302 | @objc fileprivate func _orientationChanged() {
1303 | var currentConnection: AVCaptureConnection?
1304 |
1305 | switch cameraOutputMode {
1306 | case .stillImage:
1307 | currentConnection = stillImageOutput?.connection(with: AVMediaType.video)
1308 | case .videoOnly, .videoWithMic:
1309 | currentConnection = _getMovieOutput().connection(with: AVMediaType.video)
1310 | if let location = locationManager?.latestLocation {
1311 | _setVideoWithGPS(forLocation: location)
1312 | }
1313 | }
1314 |
1315 | if let validPreviewLayer = previewLayer {
1316 | if !shouldKeepViewAtOrientationChanges {
1317 | if let validPreviewLayerConnection = validPreviewLayer.connection,
1318 | validPreviewLayerConnection.isVideoOrientationSupported {
1319 | validPreviewLayerConnection.videoOrientation = _currentPreviewVideoOrientation()
1320 | }
1321 | }
1322 | if let validOutputLayerConnection = currentConnection,
1323 | validOutputLayerConnection.isVideoOrientationSupported {
1324 | validOutputLayerConnection.videoOrientation = _currentCaptureVideoOrientation()
1325 | }
1326 | if !shouldKeepViewAtOrientationChanges && cameraIsObservingDeviceOrientation {
1327 | DispatchQueue.main.async { () -> Void in
1328 | if let validEmbeddingView = self.embeddingView {
1329 | validPreviewLayer.frame = validEmbeddingView.bounds
1330 | }
1331 | }
1332 | }
1333 | }
1334 | }
1335 |
1336 | fileprivate func _currentCaptureVideoOrientation() -> AVCaptureVideoOrientation {
1337 | if deviceOrientation == .faceDown
1338 | || deviceOrientation == .faceUp
1339 | || deviceOrientation == .unknown {
1340 | return _currentPreviewVideoOrientation()
1341 | }
1342 |
1343 | return _videoOrientation(forDeviceOrientation: deviceOrientation)
1344 | }
1345 |
1346 | fileprivate func _currentPreviewDeviceOrientation() -> UIDeviceOrientation {
1347 | if shouldKeepViewAtOrientationChanges {
1348 | return .portrait
1349 | }
1350 |
1351 | return UIDevice.current.orientation
1352 | }
1353 |
1354 | fileprivate func _currentPreviewVideoOrientation() -> AVCaptureVideoOrientation {
1355 | let orientation = _currentPreviewDeviceOrientation()
1356 | return _videoOrientation(forDeviceOrientation: orientation)
1357 | }
1358 |
1359 | open func resetOrientation() {
1360 | // Main purpose is to reset the preview layer orientation. Problems occur if you are recording landscape, present a modal VC,
1361 | // then turn portriat to dismiss. The preview view is then stuck in a prior orientation and not redrawn. Calling this function
1362 | // will then update the orientation of the preview layer.
1363 | _orientationChanged()
1364 | }
1365 |
1366 | fileprivate func _videoOrientation(forDeviceOrientation deviceOrientation: UIDeviceOrientation) -> AVCaptureVideoOrientation {
1367 | switch deviceOrientation {
1368 | case .landscapeLeft:
1369 | return .landscapeRight
1370 | case .landscapeRight:
1371 | return .landscapeLeft
1372 | case .portraitUpsideDown:
1373 | return .portraitUpsideDown
1374 | case .faceUp:
1375 | /*
1376 | Attempt to keep the existing orientation. If the device was landscape, then face up
1377 | getting the orientation from the stats bar would fail every other time forcing it
1378 | to default to portrait which would introduce flicker into the preview layer. This
1379 | would not happen if it was in portrait then face up
1380 | */
1381 | if let validPreviewLayer = previewLayer, let connection = validPreviewLayer.connection {
1382 | return connection.videoOrientation // Keep the existing orientation
1383 | }
1384 | // Could not get existing orientation, try to get it from stats bar
1385 | return _videoOrientationFromStatusBarOrientation()
1386 | case .faceDown:
1387 | /*
1388 | Attempt to keep the existing orientation. If the device was landscape, then face down
1389 | getting the orientation from the stats bar would fail every other time forcing it
1390 | to default to portrait which would introduce flicker into the preview layer. This
1391 | would not happen if it was in portrait then face down
1392 | */
1393 | if let validPreviewLayer = previewLayer, let connection = validPreviewLayer.connection {
1394 | return connection.videoOrientation // Keep the existing orientation
1395 | }
1396 | // Could not get existing orientation, try to get it from stats bar
1397 | return _videoOrientationFromStatusBarOrientation()
1398 | default:
1399 | return .portrait
1400 | }
1401 | }
1402 |
1403 | fileprivate func _videoOrientationFromStatusBarOrientation() -> AVCaptureVideoOrientation {
1404 | var orientation: UIInterfaceOrientation?
1405 |
1406 | DispatchQueue.main.async {
1407 | orientation = UIApplication.shared.statusBarOrientation
1408 | }
1409 |
1410 | /*
1411 | Note - the following would fall into the guard every other call (it is called repeatedly) if the device was
1412 | landscape then face up/down. Did not seem to fail if in portrait first.
1413 | */
1414 | guard let statusBarOrientation = orientation else {
1415 | return .portrait
1416 | }
1417 |
1418 | switch statusBarOrientation {
1419 | case .landscapeLeft:
1420 | return .landscapeLeft
1421 | case .landscapeRight:
1422 | return .landscapeRight
1423 | case .portrait:
1424 | return .portrait
1425 | case .portraitUpsideDown:
1426 | return .portraitUpsideDown
1427 | default:
1428 | return .portrait
1429 | }
1430 | }
1431 |
1432 | fileprivate func fixOrientation(withImage image: UIImage) -> UIImage {
1433 | guard let cgImage = image.cgImage else { return image }
1434 |
1435 | var isMirrored = false
1436 | let orientation = image.imageOrientation
1437 | if orientation == .rightMirrored
1438 | || orientation == .leftMirrored
1439 | || orientation == .upMirrored
1440 | || orientation == .downMirrored {
1441 | isMirrored = true
1442 | }
1443 |
1444 | let newOrientation = _imageOrientation(forDeviceOrientation: deviceOrientation, isMirrored: isMirrored)
1445 |
1446 | if image.imageOrientation != newOrientation {
1447 | return UIImage(cgImage: cgImage, scale: image.scale, orientation: newOrientation)
1448 | }
1449 |
1450 | return image
1451 | }
1452 |
1453 | fileprivate func _canLoadCamera() -> Bool {
1454 | let currentCameraState = _checkIfCameraIsAvailable()
1455 | return currentCameraState == .ready || (currentCameraState == .notDetermined && showAccessPermissionPopupAutomatically)
1456 | }
1457 |
1458 | fileprivate func _setupCamera(_ completion: @escaping () -> Void) {
1459 | captureSession = AVCaptureSession()
1460 |
1461 | sessionQueue.async {
1462 | if let validCaptureSession = self.captureSession {
1463 | validCaptureSession.beginConfiguration()
1464 | validCaptureSession.sessionPreset = AVCaptureSession.Preset.high
1465 | self._updateCameraDevice(self.cameraDevice)
1466 | self._setupOutputs()
1467 | self._setupOutputMode(self.cameraOutputMode, oldCameraOutputMode: nil)
1468 | self._setupPreviewLayer()
1469 | validCaptureSession.commitConfiguration()
1470 | self._updateIlluminationMode(self.flashMode)
1471 | self._updateCameraQualityMode(self.cameraOutputQuality)
1472 | validCaptureSession.startRunning()
1473 | self._startFollowingDeviceOrientation()
1474 | self.cameraIsSetup = true
1475 | self._orientationChanged()
1476 |
1477 | completion()
1478 | }
1479 | }
1480 | }
1481 |
1482 | fileprivate func _startFollowingDeviceOrientation() {
1483 | if shouldRespondToOrientationChanges, !cameraIsObservingDeviceOrientation {
1484 | coreMotionManager = CMMotionManager()
1485 | coreMotionManager.deviceMotionUpdateInterval = 1 / 30.0
1486 | if coreMotionManager.isDeviceMotionAvailable {
1487 | coreMotionManager.startDeviceMotionUpdates(to: OperationQueue()) { motion, _ in
1488 | guard let motion = motion else { return }
1489 | let x = motion.gravity.x
1490 | let y = motion.gravity.y
1491 | let previousOrientation = self.deviceOrientation
1492 | if fabs(y) >= fabs(x) {
1493 | if y >= 0 {
1494 | self.deviceOrientation = .portraitUpsideDown
1495 | } else {
1496 | self.deviceOrientation = .portrait
1497 | }
1498 | } else {
1499 | if x >= 0 {
1500 | self.deviceOrientation = .landscapeRight
1501 | } else {
1502 | self.deviceOrientation = .landscapeLeft
1503 | }
1504 | }
1505 | if previousOrientation != self.deviceOrientation {
1506 | self._orientationChanged()
1507 | }
1508 | }
1509 |
1510 | cameraIsObservingDeviceOrientation = true
1511 | } else {
1512 | cameraIsObservingDeviceOrientation = false
1513 | }
1514 | }
1515 | }
1516 |
1517 | // fileprivate func updateDeviceOrientation(_ orientation: UIDeviceOrientation) {
1518 | // deviceOrientation = orientation
1519 | // }
1520 |
1521 | fileprivate func _stopFollowingDeviceOrientation() {
1522 | if cameraIsObservingDeviceOrientation {
1523 | coreMotionManager.stopDeviceMotionUpdates()
1524 | cameraIsObservingDeviceOrientation = false
1525 | }
1526 | }
1527 |
1528 | fileprivate func _addPreviewLayerToView(_ view: UIView) {
1529 | embeddingView = view
1530 | attachZoom(view)
1531 | attachFocus(view)
1532 | attachExposure(view)
1533 |
1534 | DispatchQueue.main.async { () -> Void in
1535 | guard let previewLayer = self.previewLayer else { return }
1536 | previewLayer.frame = view.layer.bounds
1537 | view.clipsToBounds = true
1538 | view.layer.addSublayer(previewLayer)
1539 | }
1540 | }
1541 |
1542 | fileprivate func _setupMaxZoomScale() {
1543 | var maxZoom = CGFloat(1.0)
1544 | beginZoomScale = CGFloat(1.0)
1545 |
1546 | if cameraDevice == .back, let backCameraDevice = backCameraDevice {
1547 | maxZoom = backCameraDevice.activeFormat.videoMaxZoomFactor
1548 | } else if cameraDevice == .front, let frontCameraDevice = frontCameraDevice {
1549 | maxZoom = frontCameraDevice.activeFormat.videoMaxZoomFactor
1550 | }
1551 |
1552 | maxZoomScale = maxZoom
1553 | }
1554 |
1555 | fileprivate func _checkIfCameraIsAvailable() -> CameraState {
1556 | let deviceHasCamera = UIImagePickerController.isCameraDeviceAvailable(UIImagePickerController.CameraDevice.rear) || UIImagePickerController.isCameraDeviceAvailable(UIImagePickerController.CameraDevice.front)
1557 | if deviceHasCamera {
1558 | let authorizationStatus = AVCaptureDevice.authorizationStatus(for: AVMediaType.video)
1559 | let userAgreedToUseIt = authorizationStatus == .authorized
1560 | if userAgreedToUseIt {
1561 | return .ready
1562 | } else if authorizationStatus == AVAuthorizationStatus.notDetermined {
1563 | return .notDetermined
1564 | } else {
1565 | _show(NSLocalizedString("Camera access denied", comment: ""), message: NSLocalizedString("You need to go to settings app and grant acces to the camera device to use it.", comment: ""))
1566 | return .accessDenied
1567 | }
1568 | } else {
1569 | _show(NSLocalizedString("Camera unavailable", comment: ""), message: NSLocalizedString("The device does not have a camera.", comment: ""))
1570 | return .noDeviceFound
1571 | }
1572 | }
1573 |
1574 | fileprivate func _setupOutputMode(_ newCameraOutputMode: CameraOutputMode, oldCameraOutputMode: CameraOutputMode?) {
1575 | captureSession?.beginConfiguration()
1576 |
1577 | if let cameraOutputToRemove = oldCameraOutputMode {
1578 | // remove current setting
1579 | switch cameraOutputToRemove {
1580 | case .stillImage:
1581 | if let validStillImageOutput = stillImageOutput {
1582 | captureSession?.removeOutput(validStillImageOutput)
1583 | }
1584 | case .videoOnly, .videoWithMic:
1585 | if let validMovieOutput = movieOutput {
1586 | captureSession?.removeOutput(validMovieOutput)
1587 | }
1588 | if cameraOutputToRemove == .videoWithMic {
1589 | _removeMicInput()
1590 | }
1591 | }
1592 | }
1593 |
1594 | _setupOutputs()
1595 |
1596 | // configure new devices
1597 | switch newCameraOutputMode {
1598 | case .stillImage:
1599 | let validStillImageOutput = _getStillImageOutput()
1600 | if let captureSession = captureSession,
1601 | captureSession.canAddOutput(validStillImageOutput) {
1602 | captureSession.addOutput(validStillImageOutput)
1603 | }
1604 | case .videoOnly, .videoWithMic:
1605 | let videoMovieOutput = _getMovieOutput()
1606 | if let captureSession = captureSession,
1607 | captureSession.canAddOutput(videoMovieOutput) {
1608 | captureSession.addOutput(videoMovieOutput)
1609 | }
1610 |
1611 | if newCameraOutputMode == .videoWithMic,
1612 | let validMic = _deviceInputFromDevice(mic) {
1613 | captureSession?.addInput(validMic)
1614 | }
1615 | }
1616 | captureSession?.commitConfiguration()
1617 | _updateCameraQualityMode(cameraOutputQuality)
1618 | _orientationChanged()
1619 | }
1620 |
1621 | fileprivate func _setupOutputs() {
1622 | if stillImageOutput == nil {
1623 | stillImageOutput = AVCaptureStillImageOutput()
1624 | }
1625 | if movieOutput == nil {
1626 | movieOutput = _getMovieOutput()
1627 | }
1628 | if library == nil {
1629 | library = PHPhotoLibrary.shared()
1630 | }
1631 | }
1632 |
1633 | fileprivate func _setupPreviewLayer() {
1634 | if let validCaptureSession = captureSession {
1635 | previewLayer = AVCaptureVideoPreviewLayer(session: validCaptureSession)
1636 | previewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
1637 | }
1638 | }
1639 |
1640 | /**
1641 | Switches between the current and specified camera using a flip animation similar to the one used in the iOS stock camera app.
1642 | */
1643 |
1644 | fileprivate var cameraTransitionView: UIView?
1645 | fileprivate var transitionAnimating = false
1646 |
1647 | open func _doFlipAnimation() {
1648 | if transitionAnimating {
1649 | return
1650 | }
1651 |
1652 | if let validEmbeddingView = embeddingView,
1653 | let validPreviewLayer = previewLayer {
1654 | var tempView = UIView()
1655 |
1656 | if CameraManager._blurSupported() {
1657 | let blurEffect = UIBlurEffect(style: .light)
1658 | tempView = UIVisualEffectView(effect: blurEffect)
1659 | tempView.frame = validEmbeddingView.bounds
1660 | } else {
1661 | tempView = UIView(frame: validEmbeddingView.bounds)
1662 | tempView.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
1663 | }
1664 |
1665 | validEmbeddingView.insertSubview(tempView, at: Int(validPreviewLayer.zPosition + 1))
1666 |
1667 | cameraTransitionView = validEmbeddingView.snapshotView(afterScreenUpdates: true)
1668 |
1669 | if let cameraTransitionView = cameraTransitionView {
1670 | validEmbeddingView.insertSubview(cameraTransitionView, at: Int(validEmbeddingView.layer.zPosition + 1))
1671 | }
1672 | tempView.removeFromSuperview()
1673 |
1674 | transitionAnimating = true
1675 |
1676 | validPreviewLayer.opacity = 0.0
1677 |
1678 | DispatchQueue.main.async {
1679 | self._flipCameraTransitionView()
1680 | }
1681 | }
1682 | }
1683 |
1684 | // MARK: - CameraLocationManager()
1685 |
1686 | public class CameraLocationManager: NSObject, CLLocationManagerDelegate {
1687 | var locationManager = CLLocationManager()
1688 | var latestLocation: CLLocation?
1689 |
1690 | override init() {
1691 | super.init()
1692 | locationManager.delegate = self
1693 | locationManager.requestWhenInUseAuthorization()
1694 | locationManager.distanceFilter = kCLDistanceFilterNone
1695 | locationManager.headingFilter = 5.0
1696 | locationManager.desiredAccuracy = kCLLocationAccuracyBest
1697 | }
1698 |
1699 | func startUpdatingLocation() {
1700 | locationManager.startUpdatingLocation()
1701 | }
1702 |
1703 | func stopUpdatingLocation() {
1704 | locationManager.stopUpdatingLocation()
1705 | }
1706 |
1707 | // MARK: - CLLocationManagerDelegate
1708 |
1709 | public func locationManager(_: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
1710 | // Pick the location with best (= smallest value) horizontal accuracy
1711 | latestLocation = locations.sorted { $0.horizontalAccuracy < $1.horizontalAccuracy }.first
1712 | }
1713 |
1714 | public func locationManager(_: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
1715 | if status == .authorizedAlways || status == .authorizedWhenInUse {
1716 | locationManager.startUpdatingLocation()
1717 | } else {
1718 | locationManager.stopUpdatingLocation()
1719 | }
1720 | }
1721 | }
1722 |
1723 | // Determining whether the current device actually supports blurring
1724 | // As seen on: http://stackoverflow.com/a/29997626/2269387
1725 | fileprivate class func _blurSupported() -> Bool {
1726 | var supported = Set()
1727 | supported.insert("iPad")
1728 | supported.insert("iPad1,1")
1729 | supported.insert("iPhone1,1")
1730 | supported.insert("iPhone1,2")
1731 | supported.insert("iPhone2,1")
1732 | supported.insert("iPhone3,1")
1733 | supported.insert("iPhone3,2")
1734 | supported.insert("iPhone3,3")
1735 | supported.insert("iPod1,1")
1736 | supported.insert("iPod2,1")
1737 | supported.insert("iPod2,2")
1738 | supported.insert("iPod3,1")
1739 | supported.insert("iPod4,1")
1740 | supported.insert("iPad2,1")
1741 | supported.insert("iPad2,2")
1742 | supported.insert("iPad2,3")
1743 | supported.insert("iPad2,4")
1744 | supported.insert("iPad3,1")
1745 | supported.insert("iPad3,2")
1746 | supported.insert("iPad3,3")
1747 |
1748 | return !supported.contains(_hardwareString())
1749 | }
1750 |
1751 | fileprivate class func _hardwareString() -> String {
1752 | var sysinfo = utsname()
1753 | uname(&sysinfo)
1754 | guard let deviceName = String(bytes: Data(bytes: &sysinfo.machine, count: Int(_SYS_NAMELEN)), encoding: .ascii)?.trimmingCharacters(in: .controlCharacters) else {
1755 | return ""
1756 | }
1757 | return deviceName
1758 | }
1759 |
1760 | fileprivate func _flipCameraTransitionView() {
1761 | if let cameraTransitionView = cameraTransitionView {
1762 | UIView.transition(with: cameraTransitionView,
1763 | duration: 0.5,
1764 | options: UIView.AnimationOptions.transitionFlipFromLeft,
1765 | animations: nil,
1766 | completion: { (_) -> Void in
1767 | self._removeCameraTransistionView()
1768 | })
1769 | }
1770 | }
1771 |
1772 | fileprivate func _removeCameraTransistionView() {
1773 | if let cameraTransitionView = cameraTransitionView {
1774 | if let validPreviewLayer = previewLayer {
1775 | validPreviewLayer.opacity = 1.0
1776 | }
1777 |
1778 | UIView.animate(withDuration: 0.5,
1779 | animations: { () -> Void in
1780 |
1781 | cameraTransitionView.alpha = 0.0
1782 |
1783 | }, completion: { (_) -> Void in
1784 |
1785 | self.transitionAnimating = false
1786 |
1787 | cameraTransitionView.removeFromSuperview()
1788 | self.cameraTransitionView = nil
1789 | })
1790 | }
1791 | }
1792 |
1793 | fileprivate func _updateCameraDevice(_: CameraDevice) {
1794 | if let validCaptureSession = captureSession {
1795 | validCaptureSession.beginConfiguration()
1796 | defer { validCaptureSession.commitConfiguration() }
1797 | let inputs: [AVCaptureInput] = validCaptureSession.inputs
1798 |
1799 | for input in inputs {
1800 | if let deviceInput = input as? AVCaptureDeviceInput, deviceInput.device != mic {
1801 | validCaptureSession.removeInput(deviceInput)
1802 | }
1803 | }
1804 |
1805 | switch cameraDevice {
1806 | case .front:
1807 | if hasFrontCamera {
1808 | if let validFrontDevice = _deviceInputFromDevice(frontCameraDevice),
1809 | !inputs.contains(validFrontDevice) {
1810 | validCaptureSession.addInput(validFrontDevice)
1811 | }
1812 | }
1813 | case .back:
1814 | if let validBackDevice = _deviceInputFromDevice(backCameraDevice),
1815 | !inputs.contains(validBackDevice) {
1816 | validCaptureSession.addInput(validBackDevice)
1817 | }
1818 | }
1819 | }
1820 | }
1821 |
1822 | fileprivate func _updateIlluminationMode(_ mode: CameraFlashMode) {
1823 | if cameraOutputMode != .stillImage {
1824 | _updateTorch(mode)
1825 | } else {
1826 | _updateFlash(mode)
1827 | }
1828 | }
1829 |
1830 | fileprivate func _updateTorch(_: CameraFlashMode) {
1831 | captureSession?.beginConfiguration()
1832 | defer { captureSession?.commitConfiguration() }
1833 | for captureDevice in AVCaptureDevice.videoDevices {
1834 | guard let avTorchMode = AVCaptureDevice.TorchMode(rawValue: flashMode.rawValue) else { continue }
1835 | if captureDevice.isTorchModeSupported(avTorchMode), cameraDevice == .back {
1836 | do {
1837 | try captureDevice.lockForConfiguration()
1838 |
1839 | captureDevice.torchMode = avTorchMode
1840 | captureDevice.unlockForConfiguration()
1841 |
1842 | } catch {
1843 | return
1844 | }
1845 | }
1846 | }
1847 | }
1848 |
1849 | fileprivate func _updateFlash(_ flashMode: CameraFlashMode) {
1850 | captureSession?.beginConfiguration()
1851 | defer { captureSession?.commitConfiguration() }
1852 | for captureDevice in AVCaptureDevice.videoDevices {
1853 | guard let avFlashMode = AVCaptureDevice.FlashMode(rawValue: flashMode.rawValue) else { continue }
1854 | if captureDevice.isFlashModeSupported(avFlashMode) {
1855 | do {
1856 | try captureDevice.lockForConfiguration()
1857 | captureDevice.flashMode = avFlashMode
1858 | captureDevice.unlockForConfiguration()
1859 | } catch {
1860 | return
1861 | }
1862 | }
1863 | }
1864 | }
1865 |
1866 | fileprivate func _performShutterAnimation(_ completion: (() -> Void)?) {
1867 | if let validPreviewLayer = previewLayer {
1868 | DispatchQueue.main.async {
1869 | let duration = 0.1
1870 |
1871 | CATransaction.begin()
1872 |
1873 | if let completion = completion {
1874 | CATransaction.setCompletionBlock(completion)
1875 | }
1876 |
1877 | let fadeOutAnimation = CABasicAnimation(keyPath: "opacity")
1878 | fadeOutAnimation.fromValue = 1.0
1879 | fadeOutAnimation.toValue = 0.0
1880 | validPreviewLayer.add(fadeOutAnimation, forKey: "opacity")
1881 |
1882 | let fadeInAnimation = CABasicAnimation(keyPath: "opacity")
1883 | fadeInAnimation.fromValue = 0.0
1884 | fadeInAnimation.toValue = 1.0
1885 | fadeInAnimation.beginTime = CACurrentMediaTime() + duration * 2.0
1886 | validPreviewLayer.add(fadeInAnimation, forKey: "opacity")
1887 |
1888 | CATransaction.commit()
1889 | }
1890 | }
1891 | }
1892 |
1893 | fileprivate func _updateCameraQualityMode(_ newCameraOutputQuality: AVCaptureSession.Preset) {
1894 | if let validCaptureSession = captureSession {
1895 | var sessionPreset = newCameraOutputQuality
1896 | if newCameraOutputQuality == .high {
1897 | if cameraOutputMode == .stillImage {
1898 | sessionPreset = AVCaptureSession.Preset.photo
1899 | } else {
1900 | sessionPreset = AVCaptureSession.Preset.high
1901 | }
1902 | }
1903 |
1904 | if validCaptureSession.canSetSessionPreset(sessionPreset) {
1905 | validCaptureSession.beginConfiguration()
1906 | validCaptureSession.sessionPreset = sessionPreset
1907 | validCaptureSession.commitConfiguration()
1908 | } else {
1909 | _show(NSLocalizedString("Preset not supported", comment: ""), message: NSLocalizedString("Camera preset not supported. Please try another one.", comment: ""))
1910 | }
1911 | } else {
1912 | _show(NSLocalizedString("Camera error", comment: ""), message: NSLocalizedString("No valid capture session found, I can't take any pictures or videos.", comment: ""))
1913 | }
1914 | }
1915 |
1916 | fileprivate func _removeMicInput() {
1917 | guard let inputs = captureSession?.inputs else { return }
1918 |
1919 | for input in inputs {
1920 | if let deviceInput = input as? AVCaptureDeviceInput,
1921 | deviceInput.device == mic {
1922 | captureSession?.removeInput(deviceInput)
1923 | break
1924 | }
1925 | }
1926 | }
1927 |
1928 | fileprivate func _show(_ title: String, message: String) {
1929 | if showErrorsToUsers {
1930 | DispatchQueue.main.async { () -> Void in
1931 | self.showErrorBlock(title, message)
1932 | }
1933 | }
1934 | }
1935 |
1936 | fileprivate func _deviceInputFromDevice(_ device: AVCaptureDevice?) -> AVCaptureDeviceInput? {
1937 | guard let validDevice = device else { return nil }
1938 | do {
1939 | return try AVCaptureDeviceInput(device: validDevice)
1940 | } catch let outError {
1941 | _show(NSLocalizedString("Device setup error occured", comment: ""), message: "\(outError)")
1942 | return nil
1943 | }
1944 | }
1945 |
1946 | deinit {
1947 | _stopFollowingDeviceOrientation()
1948 | stopAndRemoveCaptureSession()
1949 | }
1950 | }
1951 |
1952 | private extension AVCaptureDevice {
1953 | static var videoDevices: [AVCaptureDevice] {
1954 | return AVCaptureDevice.devices(for: AVMediaType.video)
1955 | }
1956 | }
1957 |
1958 | extension PHPhotoLibrary {
1959 | // MARK: - Public
1960 |
1961 | // finds or creates an album
1962 |
1963 | func getAlbum(name: String, completion: @escaping (PHAssetCollection) -> Void) {
1964 | if let album = findAlbum(name: name) {
1965 | completion(album)
1966 | } else {
1967 | createAlbum(name: name, completion: completion)
1968 | }
1969 | }
1970 |
1971 | func save(imageAtURL: URL, albumName: String?, date: Date = Date(), location: CLLocation? = nil, completion: ((PHAsset?) -> Void)? = nil) {
1972 | func save() {
1973 | if let albumName = albumName {
1974 | getAlbum(name: albumName) { album in
1975 | self.saveImage(imageAtURL: imageAtURL, album: album, date: date, location: location, completion: completion)
1976 | }
1977 | } else {
1978 | saveImage(imageAtURL: imageAtURL, album: nil, date: date, location: location, completion: completion)
1979 | }
1980 | }
1981 |
1982 | if PHPhotoLibrary.authorizationStatus() == .authorized {
1983 | save()
1984 | } else {
1985 | PHPhotoLibrary.requestAuthorization { status in
1986 | if status == .authorized {
1987 | save()
1988 | }
1989 | }
1990 | }
1991 | }
1992 |
1993 | func save(videoAtURL: URL, albumName: String?, date: Date = Date(), location: CLLocation? = nil, completion: ((PHAsset?) -> Void)? = nil) {
1994 | func save() {
1995 | if let albumName = albumName {
1996 | getAlbum(name: albumName) { album in
1997 | self.saveVideo(videoAtURL: videoAtURL, album: album, date: date, location: location, completion: completion)
1998 | }
1999 | } else {
2000 | saveVideo(videoAtURL: videoAtURL, album: nil, date: date, location: location, completion: completion)
2001 | }
2002 | }
2003 |
2004 | if PHPhotoLibrary.authorizationStatus() == .authorized {
2005 | save()
2006 | } else {
2007 | PHPhotoLibrary.requestAuthorization { status in
2008 | if status == .authorized {
2009 | save()
2010 | }
2011 | }
2012 | }
2013 | }
2014 |
2015 | // MARK: - Private
2016 |
2017 | fileprivate func findAlbum(name: String) -> PHAssetCollection? {
2018 | let fetchOptions = PHFetchOptions()
2019 | fetchOptions.predicate = NSPredicate(format: "title = %@", name)
2020 | let fetchResult: PHFetchResult = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: fetchOptions)
2021 | guard let photoAlbum = fetchResult.firstObject else {
2022 | return nil
2023 | }
2024 | return photoAlbum
2025 | }
2026 |
2027 | fileprivate func createAlbum(name: String, completion: @escaping (PHAssetCollection) -> Void) {
2028 | var placeholder: PHObjectPlaceholder?
2029 |
2030 | performChanges({
2031 | let createAlbumRequest = PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: name)
2032 | placeholder = createAlbumRequest.placeholderForCreatedAssetCollection
2033 | }, completionHandler: { _, _ in
2034 | let fetchResult = PHAssetCollection.fetchAssetCollections(withLocalIdentifiers: [placeholder!.localIdentifier], options: nil)
2035 | completion(fetchResult.firstObject!)
2036 | })
2037 | }
2038 |
2039 | fileprivate func saveImage(imageAtURL: URL, album: PHAssetCollection?, date: Date = Date(), location: CLLocation? = nil, completion: ((PHAsset?) -> Void)? = nil) {
2040 | var placeholder: PHObjectPlaceholder?
2041 | performChanges({
2042 | let createAssetRequest = PHAssetChangeRequest.creationRequestForAssetFromImage(atFileURL: imageAtURL)!
2043 | createAssetRequest.creationDate = date
2044 | createAssetRequest.location = location
2045 | if let album = album {
2046 | guard let albumChangeRequest = PHAssetCollectionChangeRequest(for: album),
2047 | let photoPlaceholder = createAssetRequest.placeholderForCreatedAsset else { return }
2048 | placeholder = photoPlaceholder
2049 | let fastEnumeration = NSArray(array: [photoPlaceholder] as [PHObjectPlaceholder])
2050 | albumChangeRequest.addAssets(fastEnumeration)
2051 | }
2052 |
2053 | }, completionHandler: { success, _ in
2054 | guard let placeholder = placeholder else {
2055 | return
2056 | }
2057 | if success {
2058 | let assets: PHFetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [placeholder.localIdentifier], options: nil)
2059 | let asset: PHAsset? = assets.firstObject
2060 | completion?(asset)
2061 | }
2062 | })
2063 | }
2064 |
2065 | fileprivate func saveVideo(videoAtURL: URL, album: PHAssetCollection?, date: Date = Date(), location: CLLocation? = nil, completion: ((PHAsset?) -> Void)? = nil) {
2066 | var placeholder: PHObjectPlaceholder?
2067 | performChanges({
2068 | let createAssetRequest = PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: videoAtURL)!
2069 | createAssetRequest.creationDate = date
2070 | createAssetRequest.location = location
2071 | if let album = album {
2072 | guard let albumChangeRequest = PHAssetCollectionChangeRequest(for: album),
2073 | let photoPlaceholder = createAssetRequest.placeholderForCreatedAsset else { return }
2074 | placeholder = photoPlaceholder
2075 | let fastEnumeration = NSArray(array: [photoPlaceholder] as [PHObjectPlaceholder])
2076 | albumChangeRequest.addAssets(fastEnumeration)
2077 | }
2078 |
2079 | }, completionHandler: { success, _ in
2080 | guard let placeholder = placeholder else {
2081 | completion?(nil)
2082 | return
2083 | }
2084 | if success {
2085 | let assets: PHFetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [placeholder.localIdentifier], options: nil)
2086 | let asset: PHAsset? = assets.firstObject
2087 | completion?(asset)
2088 | } else {
2089 | completion?(nil)
2090 | }
2091 | })
2092 | }
2093 |
2094 | fileprivate func saveImage(image: UIImage, album: PHAssetCollection, completion: ((PHAsset?) -> Void)? = nil) {
2095 | var placeholder: PHObjectPlaceholder?
2096 | performChanges({
2097 | let createAssetRequest = PHAssetChangeRequest.creationRequestForAsset(from: image)
2098 | createAssetRequest.creationDate = Date()
2099 | guard let albumChangeRequest = PHAssetCollectionChangeRequest(for: album),
2100 | let photoPlaceholder = createAssetRequest.placeholderForCreatedAsset else { return }
2101 | placeholder = photoPlaceholder
2102 | let fastEnumeration = NSArray(array: [photoPlaceholder] as [PHObjectPlaceholder])
2103 | albumChangeRequest.addAssets(fastEnumeration)
2104 | }, completionHandler: { success, _ in
2105 | guard let placeholder = placeholder else {
2106 | completion?(nil)
2107 | return
2108 | }
2109 | if success {
2110 | let assets: PHFetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [placeholder.localIdentifier], options: nil)
2111 | let asset: PHAsset? = assets.firstObject
2112 | completion?(asset)
2113 | } else {
2114 | completion?(nil)
2115 | }
2116 | })
2117 | }
2118 | }
2119 |
2120 | extension CameraManager: AVCaptureMetadataOutputObjectsDelegate {
2121 | /**
2122 | Called when a QR code is detected.
2123 | */
2124 | public func metadataOutput(_: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from _: AVCaptureConnection) {
2125 | // Check if there is a registered handler.
2126 | guard let handler = qrCodeDetectionHandler
2127 | else { return }
2128 |
2129 | // Get the detection result.
2130 | let stringValues = metadataObjects
2131 | .compactMap { $0 as? AVMetadataMachineReadableCodeObject }
2132 | .compactMap { $0.stringValue }
2133 |
2134 | guard let stringValue = stringValues.first
2135 | else { return }
2136 |
2137 | handler(.success(stringValue))
2138 | }
2139 | }
2140 |
--------------------------------------------------------------------------------
/Sources/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 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------