// // 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 }) } }