Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Sources/Internal/Extensions/AVCaptureVideoOrientation++.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,14 @@ extension AVCaptureVideoOrientation {
default: .up
}}
}

// MARK: - To UIDeviceOrientation
extension AVCaptureVideoOrientation {
func toDeviceOrientation() -> UIDeviceOrientation { switch self {
case .portrait: .portrait
case .portraitUpsideDown: .portraitUpsideDown
case .landscapeLeft: .landscapeLeft
case .landscapeRight: .landscapeRight
default: .portrait
}}
}
7 changes: 6 additions & 1 deletion Sources/Internal/Extensions/Bundle++.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,10 @@
import Foundation

extension Bundle {
static var mijick: Bundle { .init(identifier: "MijickCameraView-MijickCameraView-resources") ?? .main }
static var mijick: Bundle {
.allBundles
.compactMap { $0.resourceURL?.appendingPathComponent("MijickCameraView_MijickCameraView", isDirectory: false).appendingPathExtension("bundle") }
.compactMap { Bundle(url: $0) }
.first ?? .main
}
}
41 changes: 26 additions & 15 deletions Sources/Internal/Managers/CameraManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ private extension CameraManager {
cameraMetalView = nil
cameraGridView = nil
cameraBlurView = nil
cameraFocusView = .init()
cameraFocusView = .create(image: .iconCrosshair, tintColor: .yellow, size: 92)
motionManager = .init()
}
func removeObservers() {
Expand Down Expand Up @@ -305,18 +305,6 @@ extension CameraManager {
}
}

// MARK: - Camera Rotation
extension CameraManager {
func fixCameraRotation() { if !orientationLocked {
redrawGrid()
}}
}
private extension CameraManager {
func redrawGrid() {
cameraGridView?.draw(.zero)
}
}

// MARK: - Changing Output Type
extension CameraManager {
func changeOutputType(_ newOutputType: CameraOutputType) throws { if newOutputType != attributes.outputType && !isChanging {
Expand Down Expand Up @@ -398,7 +386,7 @@ private extension CameraManager {
animateCameraFocusView()
}}
func setCameraFocus(_ touchPoint: CGPoint, _ device: AVCaptureDevice) throws {
let focusPoint = cameraLayer.captureDevicePointConverted(fromLayerPoint: touchPoint)
let focusPoint = convertTouchPointToFocusPoint(touchPoint)
try configureCameraFocus(focusPoint, device)
}
}
Expand All @@ -417,6 +405,10 @@ private extension CameraManager {
UIView.animate(withDuration: 0.5, delay: 3.5) { [self] in cameraFocusView.alpha = 0 }
}
}
func convertTouchPointToFocusPoint(_ touchPoint: CGPoint) -> CGPoint { .init(
x: touchPoint.y / cameraView.frame.height,
y: 1 - touchPoint.x / cameraView.frame.width
)}
func configureCameraFocus(_ focusPoint: CGPoint, _ device: AVCaptureDevice) throws { try withLockingDeviceForConfiguration(device) { device in
setFocusPointOfInterest(focusPoint, device)
setExposurePointOfInterest(focusPoint, device)
Expand Down Expand Up @@ -684,7 +676,22 @@ private extension CameraManager {

extension CameraManager: AVCapturePhotoCaptureDelegate {
public func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: (any Swift.Error)?) {
attributes.capturedMedia = .create(imageData: photo, orientation: frameOrientation, filters: attributes.cameraFilters)
attributes.capturedMedia = .create(imageData: photo, orientation: fixedFrameOrientation(), filters: attributes.cameraFilters)
}
}
private extension CameraManager {
func fixedFrameOrientation() -> CGImagePropertyOrientation { guard UIDevice.current.orientation != attributes.deviceOrientation.toDeviceOrientation() else { return frameOrientation }
return switch (attributes.deviceOrientation, attributes.cameraPosition) {
case (.portrait, .front): .left
case (.portrait, .back): .right
case (.landscapeLeft, .back): .down
case (.landscapeRight, .back): .up
case (.landscapeLeft, .front) where attributes.mirrorOutput: .up
case (.landscapeLeft, .front): .upMirrored
case (.landscapeRight, .front) where attributes.mirrorOutput: .down
case (.landscapeRight, .front): .downMirrored
default: .right
}
}
}

Expand Down Expand Up @@ -751,6 +758,7 @@ private extension CameraManager {
updateDeviceOrientation(newDeviceOrientation)
updateUserBlockedScreenRotation()
updateFrameOrientation()
redrawGrid()
}}
}
private extension CameraManager {
Expand All @@ -772,6 +780,9 @@ private extension CameraManager {
let newFrameOrientation = getNewFrameOrientation(orientationLocked ? .portrait : UIDevice.current.orientation)
updateFrameOrientation(newFrameOrientation)
}}
func redrawGrid() { if !orientationLocked {
cameraGridView?.draw(.zero)
}}
}
private extension CameraManager {
func getNewUserBlockedScreenRotation() -> Bool { switch attributes.deviceOrientation.rawValue == UIDevice.current.orientation.rawValue {
Expand Down
102 changes: 43 additions & 59 deletions Sources/Internal/Views/Main/CameraInputBridgeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,84 +11,68 @@

import SwiftUI

struct CameraInputBridgeView: UIViewRepresentable {
struct CameraInputBridgeView {
let cameraManager: CameraManager
private var inputView: UICameraInputView = .init()

init(_ cameraManager: CameraManager) { self.cameraManager = cameraManager }
}
extension CameraInputBridgeView {
func makeUIView(context: Context) -> some UIView {
inputView.cameraManager = cameraManager
return inputView.view
}
func updateUIView(_ uiView: UIViewType, context: Context) {}
}
extension CameraInputBridgeView: Equatable {
static func == (lhs: Self, rhs: Self) -> Bool { true }
let inputView: UIView = .init()
}


// MARK: - UIViewController
fileprivate class UICameraInputView: UIViewController {
var cameraManager: CameraManager!
// MARK: - PROTOCOLS CONFORMANCE


override func viewDidLoad() {
super.viewDidLoad()

// MARK: UIViewRepresentable
extension CameraInputBridgeView: UIViewRepresentable {
func makeUIView(context: Context) -> some UIView {
setupCameraManager()
setupTapGesture()
setupPinchGesture()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()

cameraManager.fixCameraRotation()
setupTapGesture(context)
setupPinchGesture(context)
return inputView
}
func updateUIView(_ uiView: UIViewType, context: Context) {}
func makeCoordinator() -> Coordinator { .init(self) }
}

// MARK: - Setup
private extension UICameraInputView {
private extension CameraInputBridgeView {
func setupCameraManager() {
cameraManager.setup(in: view)
cameraManager.setup(in: inputView)
}
func setupTapGesture() {
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTapGesture))
view.addGestureRecognizer(tapRecognizer)
func setupTapGesture(_ context: Context) {
let tapRecognizer = UITapGestureRecognizer(target: context.coordinator, action: #selector(context.coordinator.onTapGesture))
inputView.addGestureRecognizer(tapRecognizer)
}
func setupPinchGesture() {
let pinchRecognizer = UIPinchGestureRecognizer(target: self, action: #selector(handlePinchGesture))
view.addGestureRecognizer(pinchRecognizer)
func setupPinchGesture(_ context: Context) {
let pinchRecognizer = UIPinchGestureRecognizer(target: context.coordinator, action: #selector(context.coordinator.onPinchGesture))
inputView.addGestureRecognizer(pinchRecognizer)
}
}

// MARK: - Gestures

// MARK: Tap
private extension UICameraInputView {
@objc func handleTapGesture(_ tap: UITapGestureRecognizer) {
let touchPoint = tap.location(in: view)
setCameraFocus(touchPoint)
}
// MARK: Equatable
extension CameraInputBridgeView: Equatable {
static func ==(lhs: Self, rhs: Self) -> Bool { true }
}
private extension UICameraInputView {
func setCameraFocus(_ touchPoint: CGPoint) {
do { try cameraManager.setCameraFocus(touchPoint) }
catch {}


// MARK: - LOGIC
extension CameraInputBridgeView { class Coordinator: NSObject { init(_ parent: CameraInputBridgeView) { self.parent = parent }
let parent: CameraInputBridgeView
}}

// MARK: On Tap
extension CameraInputBridgeView.Coordinator {
@objc func onTapGesture(_ tap: UITapGestureRecognizer) {
do {
let touchPoint = tap.location(in: parent.inputView)
try parent.cameraManager.setCameraFocus(touchPoint)
} catch {}
}
}

// MARK: Pinch
private extension UICameraInputView {
@objc func handlePinchGesture(_ pinch: UIPinchGestureRecognizer) { if pinch.state == .changed {
let desiredZoomFactor = cameraManager.attributes.zoomFactor + atan2(pinch.velocity, 33)
changeZoomFactor(desiredZoomFactor)
// MARK: On Pinch
extension CameraInputBridgeView.Coordinator {
@objc func onPinchGesture(_ pinch: UIPinchGestureRecognizer) { if pinch.state == .changed {
do {
let desiredZoomFactor = parent.cameraManager.attributes.zoomFactor + atan2(pinch.velocity, 33)
try parent.cameraManager.changeZoomFactor(desiredZoomFactor)
} catch {}
}}
}
private extension UICameraInputView {
func changeZoomFactor(_ desiredZoomFactor: CGFloat) {
do { try cameraManager.changeZoomFactor(desiredZoomFactor) }
catch {}
}
}
2 changes: 1 addition & 1 deletion Sources/Public/View Protocols/Public+MCameraView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public protocol MCameraView: View {

// MARK: - Use-only View Methods
public extension MCameraView {
func createCameraView() -> some View { CameraInputBridgeView(cameraManager).equatable() }
func createCameraView() -> some View { CameraInputBridgeView(cameraManager: cameraManager).equatable() }
}

// MARK: - Use-only Logic Methods
Expand Down