* Add iOS linter & report issues on PR * Fix lint errors --------- Co-authored-by: Seph Soliman <github@seph.dk>
212 lines
7.3 KiB
Swift
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
|
|
})
|
|
}
|
|
}
|