182 lines
6.2 KiB
Swift
182 lines
6.2 KiB
Swift
//
|
||
// MCamera.swift of MijickCamera
|
||
//
|
||
// Created by Tomasz Kurylik. Sending ❤️ from Kraków!
|
||
// - Mail: tomasz.kurylik@mijick.com
|
||
// - GitHub: https://github.com/FulcrumOne
|
||
// - Medium: https://medium.com/@mijick
|
||
//
|
||
// Copyright ©2024 Mijick. All rights reserved.
|
||
|
||
|
||
import SwiftUI
|
||
|
||
/**
|
||
A view that displays a camera with state-specific screens.
|
||
|
||
By default, it includes three screens that change depending on the status of the camera; **Error Screen**, **Camera Screen** and **Captured Media Screen**.
|
||
|
||
Handles issues related to asking for permissions, and if permissions are not granted, it displays the **Error Screen**.
|
||
|
||
Optionally shows the **Captured Media Screen**, which is displayed after the user captures an image or video.
|
||
|
||
|
||
# Customization
|
||
All of the MCamera's default settings can be changed during initialisation.
|
||
- important: To start a camera session, simply call the ``startSession()`` method. For more details, see the **Usage** section.
|
||
|
||
## Camera Screens
|
||
Use one of the methods below to change the default screens:
|
||
- ``setCameraScreen(_:)``
|
||
- ``setCapturedMediaScreen(_:)``
|
||
- ``setErrorScreen(_:)``
|
||
|
||
- tip: To disable displaying captured media, call the ``setCapturedMediaScreen(_:)`` method with a nil value.
|
||
|
||
## Actions after capturing media
|
||
Use one of the methods below to set actions that will be called after capturing media:
|
||
- ``onImageCaptured(_:)``
|
||
- ``onVideoCaptured(_:)``
|
||
- note: If there is no **Captured Media Screen**, the action is called immediately after the media is captured, otherwise it is triggered after the user accepts the captured media in the **Captured Media Screen**.
|
||
|
||
## Camera Configuration
|
||
To change the initial camera settings, use the following methods:
|
||
- ``setCameraOutputType(_:)``
|
||
- ``setCameraPosition(_:)``
|
||
- ``setAudioAvailability(_:)``
|
||
- ``setZoomFactor(_:)``
|
||
- ``setFlashMode(_:)``
|
||
- ``setLightMode(_:)``
|
||
- ``setResolution(_:)``
|
||
- ``setFrameRate(_:)``
|
||
- ``setCameraExposureDuration(_:)``
|
||
- ``setCameraTargetBias(_:)``
|
||
- ``setCameraISO(_:)``
|
||
- ``setCameraExposureMode(_:)``
|
||
- ``setCameraHDRMode(_:)``
|
||
- ``setCameraFilters(_:)``
|
||
- ``setMirrorOutput(_:)``
|
||
- ``setGridVisibility(_:)``
|
||
- ``setFocusImage(_:)``
|
||
- ``setFocusImageColor(_:)``
|
||
- ``setFocusImageSize(_:)``
|
||
- important: Note that if you try to set a value that exceeds the camera's capabilities, the camera will automatically set the closest possible value and show you which value has been set.
|
||
|
||
## Other
|
||
There are other methods that you can use to customize your experience:
|
||
- ``setCloseMCameraAction(_:)``
|
||
- ``lockCameraInPortraitOrientation(_:)``
|
||
|
||
# Usage
|
||
```swift
|
||
struct ContentView: View {
|
||
var body: some View {
|
||
MCamera()
|
||
.setCameraFilters([.init(name: "CISepiaTone")!])
|
||
.setCameraPosition(.back)
|
||
.setCameraOutputType(.video)
|
||
.setAudioAvailability(false)
|
||
.setResolution(.hd4K3840x2160)
|
||
.setFrameRate(30)
|
||
.setZoomFactor(1.2)
|
||
.setCameraISO(3)
|
||
.setCameraTargetBias(1.2)
|
||
.setLightMode(.on)
|
||
.setFlashMode(.auto)
|
||
|
||
// MUST BE CALLED!
|
||
.startSession()
|
||
}
|
||
}
|
||
```
|
||
*/
|
||
public struct MCamera: View {
|
||
@ObservedObject var manager: CameraManager
|
||
@Namespace var namespace
|
||
var config: Config = .init()
|
||
|
||
|
||
public var body: some View { if config.isCameraConfigured {
|
||
ZStack(content: createContent)
|
||
.onDisappear(perform: onDisappear)
|
||
.onChange(of: manager.attributes.capturedMedia, perform: onCapturedMediaChange)
|
||
}}
|
||
}
|
||
private extension MCamera {
|
||
@ViewBuilder func createContent() -> some View {
|
||
if let error = manager.attributes.error { createErrorScreen(error) }
|
||
else if let capturedMedia = manager.attributes.capturedMedia, config.capturedMediaScreen != nil { createCapturedMediaScreen(capturedMedia) }
|
||
else { createCameraScreen() }
|
||
}
|
||
}
|
||
private extension MCamera {
|
||
func createErrorScreen(_ error: MCameraError) -> some View {
|
||
config.errorScreen(error, config.closeMCameraAction).erased()
|
||
}
|
||
func createCapturedMediaScreen(_ media: MCameraMedia) -> some View {
|
||
config.capturedMediaScreen?(media, namespace, onCapturedMediaRejected, onCapturedMediaAccepted)
|
||
.erased()
|
||
.onAppear(perform: onCaptureMediaScreenAppear)
|
||
}
|
||
func createCameraScreen() -> some View {
|
||
config.cameraScreen(manager, namespace, config.closeMCameraAction)
|
||
.erased()
|
||
.onAppear(perform: onCameraAppear)
|
||
.onDisappear(perform: onCameraDisappear)
|
||
}
|
||
}
|
||
|
||
|
||
// MARK: - ACTIONS
|
||
|
||
|
||
|
||
// MARK: MCamera
|
||
private extension MCamera {
|
||
func onDisappear() {
|
||
lockScreenOrientation(nil)
|
||
manager.cancel()
|
||
}
|
||
func onCapturedMediaChange(_ capturedMedia: MCameraMedia?) {
|
||
guard let capturedMedia, config.capturedMediaScreen == nil else { return }
|
||
notifyUserOfMediaCaptured(capturedMedia)
|
||
}
|
||
}
|
||
private extension MCamera {
|
||
func lockScreenOrientation(_ orientation: UIInterfaceOrientationMask?) {
|
||
config.appDelegate?.orientationLock = orientation ?? .all
|
||
UINavigationController.attemptRotationToDeviceOrientation()
|
||
}
|
||
func notifyUserOfMediaCaptured(_ capturedMedia: MCameraMedia) {
|
||
if let image = capturedMedia.getImage() { config.imageCapturedAction(image, .init(mCamera: self)) }
|
||
else if let video = capturedMedia.getVideo() { config.videoCapturedAction(video, .init(mCamera: self)) }
|
||
}
|
||
}
|
||
|
||
// MARK: Camera Screen
|
||
private extension MCamera {
|
||
func onCameraAppear() { Task {
|
||
do {
|
||
try await manager.setup()
|
||
lockScreenOrientation(.portrait)
|
||
} catch { print("(MijickCamera) ERROR DURING SETUP: \(error)") }
|
||
}}
|
||
func onCameraDisappear() {
|
||
manager.cancel()
|
||
}
|
||
}
|
||
|
||
// MARK: Captured Media Screen
|
||
private extension MCamera {
|
||
func onCaptureMediaScreenAppear() {
|
||
lockScreenOrientation(nil)
|
||
}
|
||
func onCapturedMediaRejected() {
|
||
manager.setCapturedMedia(nil)
|
||
}
|
||
func onCapturedMediaAccepted() {
|
||
guard let capturedMedia = manager.attributes.capturedMedia else { return }
|
||
notifyUserOfMediaCaptured(capturedMedia)
|
||
}
|
||
}
|