react-native-camera-kit/ios/ReactNativeCameraKit/FocusInterfaceView.swift
David Bertet a13aa787c4
Add iOS linter & report issues on PR (#634)
* Add iOS linter & report issues on PR

* Fix lint errors

---------

Co-authored-by: Seph Soliman <github@seph.dk>
2024-04-30 16:10:07 -07:00

212 lines
7.3 KiB
Swift

//
// FocusInterfaceView.swift
// ReactNativeCameraKit
//
import UIKit
import AVFoundation
enum FocusBehavior {
case customFocus(resetFocusWhenMotionDetected: Bool, resetFocus: () -> Void, focusFinished: () -> Void)
case continuousAutoFocus
var isSubjectAreaChangeMonitoringEnabled: Bool {
switch self {
case let .customFocus(resetFocusWhenMotionDetected, _, _):
return true && resetFocusWhenMotionDetected
case .continuousAutoFocus:
return false
}
}
var avFocusMode: AVCaptureDevice.FocusMode {
switch self {
case .customFocus:
return .autoFocus
case .continuousAutoFocus:
return .continuousAutoFocus
}
}
var exposureMode: AVCaptureDevice.ExposureMode {
switch self {
case .customFocus:
return .autoExpose
case .continuousAutoFocus:
return .continuousAutoExposure
}
}
}
protocol FocusInterfaceViewDelegate: AnyObject {
func focus(at touchPoint: CGPoint, focusBehavior: FocusBehavior)
}
/*
* Full screen focus interface
*/
class FocusInterfaceView: UIView {
weak var delegate: FocusInterfaceViewDelegate?
private var resetFocusTimeout = 0
private var resetFocusWhenMotionDetected = false
private let focusView: UIView = UIView(frame: .zero)
private var hideFocusViewTimer: Timer?
private var focusResetTimer: Timer?
private var startFocusResetTimerAfterFocusing: Bool = false
private var tapToFocusEngaged: Bool = false
private var focusGestureRecognizer: UITapGestureRecognizer?
// MARK: - Lifecycle
override init(frame: CGRect) {
super.init(frame: frame)
focusView.backgroundColor = .clear
focusView.layer.borderColor = UIColor.yellow.cgColor
focusView.layer.borderWidth = 1
focusView.isHidden = true
addSubview(focusView)
isUserInteractionEnabled = true
}
@available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Public
func update(focusMode: FocusMode) {
if focusMode == .on {
if focusGestureRecognizer == nil {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(focusAndExposeTap(_:)))
addGestureRecognizer(tapGesture)
focusGestureRecognizer = tapGesture
}
} else {
if let focusGestureRecognizer {
removeGestureRecognizer(focusGestureRecognizer)
self.focusGestureRecognizer = nil
}
}
}
func update(resetFocusTimeout: Int) {
self.resetFocusTimeout = resetFocusTimeout
}
func update(resetFocusWhenMotionDetected: Bool) {
self.resetFocusWhenMotionDetected = resetFocusWhenMotionDetected
}
func focusFinished() {
if startFocusResetTimerAfterFocusing, resetFocusTimeout > 0 {
startFocusResetTimerAfterFocusing = false
// Disengage manual focus after focusTimeout milliseconds
let focusTimeoutSeconds = TimeInterval(self.resetFocusTimeout) / 1000
focusResetTimer = Timer.scheduledTimer(withTimeInterval: focusTimeoutSeconds,
repeats: false) { [weak self] _ in
self?.resetFocus()
}
}
}
func resetFocus() {
if let focusResetTimer {
focusResetTimer.invalidate()
self.focusResetTimer = nil
}
// Resetting focus to continuous focus, so not interested in resetting anymore
startFocusResetTimerAfterFocusing = false
// Avoid showing reset-focus animation after each photo capture
if !tapToFocusEngaged {
return
}
tapToFocusEngaged = false
DispatchQueue.main.async {
let layerCenter = self.center
// Reset current camera focus
self.delegate?.focus(at: layerCenter, focusBehavior: .continuousAutoFocus)
// Create animation to indicate the new focus location
let halfDiagonal: CGFloat = 123
let halfDiagonalAnimation = halfDiagonal * 2
let focusViewFrame = CGRect(x: layerCenter.x - (halfDiagonal / 2),
y: layerCenter.y - (halfDiagonal / 2),
width: halfDiagonal,
height: halfDiagonal)
let focusViewFrameForAnimation = CGRect(x: layerCenter.x - (halfDiagonalAnimation / 2),
y: layerCenter.y - (halfDiagonalAnimation / 2),
width: halfDiagonalAnimation,
height: halfDiagonalAnimation)
self.focusView.alpha = 0
self.focusView.isHidden = false
self.focusView.frame = focusViewFrameForAnimation
UIView.animate(withDuration: 0.2, animations: {
self.focusView.frame = focusViewFrame
self.focusView.alpha = 1
}, completion: { _ in
self.hideFocusViewTimer?.invalidate()
self.hideFocusViewTimer = Timer.scheduledTimer(withTimeInterval: 2, repeats: false, block: { [weak self] _ in
guard let self else { return }
UIView.animate(withDuration: 0.2, animations: {
self.focusView.alpha = 0
}, completion: { _ in
self.focusView.isHidden = true
})
})
})
}
}
// MARK: - Gesture selectors
@objc func focusAndExposeTap(_ gestureRecognizer: UIGestureRecognizer) {
let touchPoint = gestureRecognizer.location(in: self)
delegate?.focus(at: touchPoint,
focusBehavior: .customFocus(resetFocusWhenMotionDetected: resetFocusWhenMotionDetected,
resetFocus: resetFocus,
focusFinished: focusFinished))
// Disengage manual focus once focusing finishing (if focusTimeout > 0)
// See [self observeValueForKeyPath]
focusResetTimer?.invalidate()
hideFocusViewTimer?.invalidate()
startFocusResetTimerAfterFocusing = true
tapToFocusEngaged = true
// Animate focus rectangle
let halfDiagonal: CGFloat = 73
let halfDiagonalAnimation = halfDiagonal * 2
let focusViewFrame = CGRect(x: touchPoint.x - (halfDiagonal / 2),
y: touchPoint.y - (halfDiagonal / 2),
width: halfDiagonal,
height: halfDiagonal)
focusView.alpha = 0
focusView.isHidden = false
focusView.frame = CGRect(x: touchPoint.x - (halfDiagonalAnimation / 2),
y: touchPoint.y - (halfDiagonalAnimation / 2),
width: halfDiagonalAnimation,
height: halfDiagonalAnimation)
UIView.animate(withDuration: 0.2, animations: {
self.focusView.frame = focusViewFrame
self.focusView.alpha = 1
})
}
}