Last active
March 20, 2024 17:41
-
-
Save bergusman/8d511ebfd67ea3f8498f62261d316c93 to your computer and use it in GitHub Desktop.
Handle camera permission for web image file input
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// WebWithCameraViewController.swift | |
// | |
// Created by Vitaly Berg on 3/20/24. | |
// | |
import UIKit | |
import WebKit | |
import AVFoundation | |
let html = """ | |
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
</head> | |
<body style="padding: 20px"> | |
<input type="file" accept="image/*" /> | |
</body> | |
</html> | |
""" | |
class WebWithCameraViewController: UIViewController { | |
private var webView: WKWebView! | |
// MARK: - UIViewController | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
webView = WKWebView() | |
webView.translatesAutoresizingMaskIntoConstraints = false | |
view.addSubview(webView) | |
NSLayoutConstraint.activate([ | |
webView.topAnchor.constraint(equalTo: view.topAnchor), | |
webView.leftAnchor.constraint(equalTo: view.leftAnchor), | |
webView.rightAnchor.constraint(equalTo: view.rightAnchor), | |
webView.bottomAnchor.constraint(equalTo: view.bottomAnchor) | |
]) | |
webView.loadHTMLString(html, baseURL: nil) | |
} | |
override func viewWillDisappear(_ animated: Bool) { | |
super.viewWillDisappear(animated) | |
if let imagePickerVC = presentedViewController as? UIImagePickerController { | |
if imagePickerVC.sourceType != .camera { | |
// Optional checking, system not use it to pick photos, instead it uses PHPickerViewController | |
return | |
} | |
let status = AVCaptureDevice.authorizationStatus(for: .video) | |
switch status { | |
case .notDetermined: | |
// Request permissions by UIImagePickerController | |
break | |
case .authorized: | |
// All good! | |
break | |
case .restricted: | |
// Sometimes when restricted by parental controls | |
showRestricted(for: imagePickerVC) | |
case .denied: | |
// User denied | |
showDenied(for: imagePickerVC); | |
@unknown default: | |
break | |
} | |
} | |
} | |
private func showDenied(for imagePickerVC: UIImagePickerController) { | |
showPermissionsAlert(for: imagePickerVC, restricted: false) | |
} | |
private func showRestricted(for imagePickerVC: UIImagePickerController) { | |
showPermissionsAlert(for: imagePickerVC, restricted: true) | |
} | |
private var useCustomPermissionsView = false | |
private func showPermissionsAlert(for imagePickerVC: UIImagePickerController, restricted: Bool) { | |
// First option with modifing camera screen | |
if useCustomPermissionsView { | |
if let overlayView = imagePickerVC.cameraOverlayView { | |
// TODO: don't want to create good example | |
let customView = UIView(frame: overlayView.bounds) | |
customView.autoresizingMask = [.flexibleWidth, .flexibleHeight] | |
customView.backgroundColor = .black | |
overlayView.addSubview(customView) | |
let label = UILabel(frame: customView.bounds) | |
label.autoresizingMask = [.flexibleWidth, .flexibleHeight] | |
label.font = .systemFont(ofSize: 100, weight: .bold) | |
label.text = ":(" | |
label.textAlignment = .center | |
label.textColor = .white | |
customView.addSubview(label) | |
let button = UIButton(type: .system) | |
button.setTitle("Dismiss", for: .normal) | |
button.addAction(.init(handler: { [weak self] _ in | |
self?.dismissImagePicker(imagePickerVC) | |
}), for: .touchUpInside) | |
button.tintColor = .white | |
button.translatesAutoresizingMaskIntoConstraints = false | |
customView.addSubview(button) | |
button.centerXAnchor.constraint(equalTo: customView.centerXAnchor).isActive = true | |
button.bottomAnchor.constraint(equalTo: customView.safeAreaLayoutGuide.bottomAnchor, constant: -20).isActive = true | |
return | |
} | |
} | |
// Second option with alert and fallback in case of first option | |
let alertVC = UIAlertController(title: "No Permissions", message: "Something about that need enable permissions", preferredStyle: .alert) | |
if restricted { | |
// TODO: handle a little different than denied, for example change message and open settings app | |
alertVC.addAction(.init(title: "Open Settings", style: .default, handler: { [weak self] _ in | |
self?.dismissImagePicker(imagePickerVC) | |
})) | |
} else { | |
alertVC.addAction(.init(title: "Open App Settings", style: .default, handler: { [weak self] _ in | |
self?.dismissImagePicker(imagePickerVC) | |
UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!) | |
})) | |
} | |
alertVC.addAction(.init(title: "Ok", style: .cancel, handler: { [weak self] _ in | |
self?.dismissImagePicker(imagePickerVC) | |
})) | |
imagePickerVC.present(alertVC, animated: true) | |
} | |
// It just dismisses picker. Context menu on input will be unaviable if just dismiss without delegate invoking. | |
private func dismissImagePicker(_ imagePickerVC: UIImagePickerController) { | |
if imagePickerVC.delegate?.imagePickerControllerDidCancel != nil { | |
imagePickerVC.delegate?.imagePickerControllerDidCancel?(imagePickerVC) | |
} else { | |
// Fallback | |
imagePickerVC.dismiss(animated: true) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment