如何在UIImagePickerController中根据陀螺仪姿态禁用/启用拍摄按钮?
Hey there! Let's work through this problem together. The key issue here is that UIImagePickerController doesn't expose a direct way to disable its built-in capture button (since its internal controls are private), but we have two solid workarounds to achieve your goal of blocking photos when the iPad isn't vertical.
Solution 1: Block Capture with an Overlay Interceptor (Quick Fix)
This approach keeps using UIImagePickerController but adds a transparent full-screen button to its overlay. When the iPad isn't vertical (red light), this button intercepts all taps to the capture area; when it's vertical (green light), taps pass through to the native capture button.
First, adjust your view controller to track permission state and manage the overlay:
import UIKit import CoreMotion class YourViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate { private let motion = CMMotionManager() private var isCaptureAllowed = false @IBAction func camera1(_ sender: Any) { // Create status light image view let statusLightImageView = UIImageView(frame: CGRect(x: 10, y: 10, width: 50, height: 50)) statusLightImageView.contentMode = .scaleAspectFit statusLightImageView.image = #imageLiteral(resourceName: "Red_Light") // Create full-screen interceptor button to block taps when disabled let captureInterceptor = UIButton(frame: UIScreen.main.bounds) captureInterceptor.addTarget(self, action: #selector(handleInterceptedCapture), for: .touchUpInside) captureInterceptor.isUserInteractionEnabled = true // Start blocking // Combine elements into a single overlay view let overlayView = UIView(frame: UIScreen.main.bounds) overlayView.addSubview(statusLightImageView) overlayView.addSubview(captureInterceptor) // Set up image picker let imagePicker = UIImagePickerController() imagePicker.delegate = self imagePicker.allowsEditing = true imagePicker.sourceType = .camera imagePicker.cameraCaptureMode = .photo imagePicker.cameraOverlayView = overlayView imagePicker.cameraViewTransform = imagePicker.cameraViewTransform.scaledBy(x: 3, y: 3) // Start gyroscope monitoring startGyroscopeUpdates(with: statusLightImageView, interceptor: captureInterceptor) present(imagePicker, animated: true) } private func startGyroscopeUpdates(with statusLight: UIImageView, interceptor: UIButton) { guard motion.isDeviceMotionAvailable else { print("Device motion is not supported on this device!") return } motion.deviceMotionUpdateInterval = 0.2 motion.startDeviceMotionUpdates(to: OperationQueue()) { [weak self] (motionData, error) in guard let self = self, let attitude = motionData?.attitude else { return } let rollAngle = (attitude.roll * 180 / Double.pi) * -1 DispatchQueue.main.async { // Update permission state and UI self.isCaptureAllowed = rollAngle > 88 && rollAngle < 92 statusLight.image = self.isCaptureAllowed ? #imageLiteral(resourceName: "GREEN_Light") : #imageLiteral(resourceName: "Red_Light") // Enable/disable tap interception based on permission interceptor.isUserInteractionEnabled = !self.isCaptureAllowed } } } @objc private func handleInterceptedCapture() { // Show alert when user tries to take photo in invalid state let alert = UIAlertController(title: "Tip", message: "Please hold your iPad vertically to take a photo.", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "Got it", style: .default)) presentedViewController?.present(alert, animated: true) } // Your existing image picker delegate method func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { if let pickedImage = info[.originalImage] as? UIImage { frontpic.contentMode = .scaleAspectFit frontpic.image = picker.sourceType == .camera ? pickedImage.cropedToRatio(ratio: 0.33) : pickedImage } dismiss(animated: true) } }
Solution 2: Custom Camera with AVFoundation (Full Control)
If you want complete control over the capture button (and all camera UI), use AVFoundation to build a custom camera. This lets you directly enable/disable the capture button based on gyroscope data.
Here's a simplified implementation:
import UIKit import AVFoundation import CoreMotion class CustomCameraVC: UIViewController, AVCapturePhotoCaptureDelegate { private let motion = CMMotionManager() private var isCaptureAllowed = false private let captureSession = AVCaptureSession() private let photoOutput = AVCapturePhotoOutput() private lazy var previewLayer: AVCaptureVideoPreviewLayer = { let layer = AVCaptureVideoPreviewLayer(session: captureSession) layer.videoGravity = .resizeAspectFill return layer }() // Custom capture button (we control its enabled state) private lazy var captureButton: UIButton = { let button = UIButton(type: .system) button.setTitle("Take Photo", for: .normal) button.titleLabel?.font = UIFont.systemFont(ofSize: 24, weight: .bold) button.isEnabled = false button.addTarget(self, action: #selector(takePhoto), for: .touchUpInside) return button }() private let statusLightImageView: UIImageView = { let iv = UIImageView(frame: CGRect(x: 10, y: 10, width: 50, height: 50)) iv.contentMode = .scaleAspectFit iv.image = #imageLiteral(resourceName: "Red_Light") return iv }() override func viewDidLoad() { super.viewDidLoad() setupCamera() setupUI() startGyroscopeUpdates() } private func setupCamera() { captureSession.sessionPreset = .photo // Get front camera guard let camera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front) else { print("Front camera unavailable") return } do { let input = try AVCaptureDeviceInput(device: camera) if captureSession.canAddInput(input) && captureSession.canAddOutput(photoOutput) { captureSession.addInput(input) captureSession.addOutput(photoOutput) } } catch { print("Camera setup error: \(error.localizedDescription)") } // Add preview layer to view previewLayer.frame = view.bounds view.layer.addSublayer(previewLayer) captureSession.startRunning() } private func setupUI() { view.addSubview(statusLightImageView) view.addSubview(captureButton) captureButton.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ captureButton.centerXAnchor.constraint(equalTo: view.centerXAnchor), captureButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20) ]) } private func startGyroscopeUpdates() { guard motion.isDeviceMotionAvailable else { print("Device motion not supported") return } motion.deviceMotionUpdateInterval = 0.2 motion.startDeviceMotionUpdates(to: OperationQueue()) { [weak self] (motionData, error) in guard let self = self, let attitude = motionData?.attitude else { return } let rollAngle = (attitude.roll * 180 / Double.pi) * -1 DispatchQueue.main.async { self.isCaptureAllowed = rollAngle > 88 && rollAngle < 92 self.statusLightImageView.image = self.isCaptureAllowed ? #imageLiteral(resourceName: "GREEN_Light") : #imageLiteral(resourceName: "Red_Light") self.captureButton.isEnabled = self.isCaptureAllowed } } } @objc private func takePhoto() { guard isCaptureAllowed else { return } let photoSettings = AVCapturePhotoSettings() if let previewFormat = photoSettings.availablePreviewPhotoPixelFormatTypes.first { photoSettings.previewPhotoFormat = [kCVPixelBufferPixelFormatTypeKey as String: previewFormat] } photoOutput.capturePhoto(with: photoSettings, delegate: self) } func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { if let error = error { print("Photo capture error: \(error.localizedDescription)") return } if let imageData = photo.fileDataRepresentation(), let image = UIImage(data: imageData) { // Process the captured image (e.g., send back to your main view controller) dismiss(animated: true) { // Add your image handling logic here } } } }
Key Notes for Both Solutions
- Permissions: Don't forget to add these entries to your
Info.plist:NSCameraUsageDescription(explain why you need camera access)NSMotionUsageDescription(explain why you need gyroscope access)
- Memory Safety: Use
[weak self]in gyroscope closures to avoid memory leaks. - Device Compatibility: Test on real devices (simulators don't support gyroscope or camera fully).
内容的提问来源于stack exchange,提问作者farzad

