You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

  1. 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)
  2. Memory Safety: Use [weak self] in gyroscope closures to avoid memory leaks.
  3. Device Compatibility: Test on real devices (simulators don't support gyroscope or camera fully).

内容的提问来源于stack exchange,提问作者farzad

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 07:28:11