Last active
November 1, 2023 08:40
-
-
Save deepakraj27/2b5066c488c38d8456678297b17e0bea to your computer and use it in GitHub Desktop.
Access Camera, Photo Library, Video and File from User device using Swift 4
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
// | |
// AttachmentHandler.swift | |
// AttachmentHandler | |
// | |
// Created by Deepak on 25/01/18. | |
// Copyright © 2018 Deepak. All rights reserved. | |
// | |
import Foundation | |
import UIKit | |
import MobileCoreServices | |
import AVFoundation | |
import Photos | |
/* | |
AttachmentHandler.shared.showAttachmentActionSheet(vc: self) | |
AttachmentHandler.shared.imagePickedBlock = { (image) in | |
/* get your image here */ | |
} | |
*/ | |
class AttachmentHandler: NSObject{ | |
static let shared = AttachmentHandler() | |
fileprivate var currentVC: UIViewController? | |
//MARK: - Internal Properties | |
var imagePickedBlock: ((UIImage) -> Void)? | |
var videoPickedBlock: ((NSURL) -> Void)? | |
var filePickedBlock: ((URL) -> Void)? | |
enum AttachmentType: String{ | |
case camera, video, photoLibrary | |
} | |
//MARK: - Constants | |
struct Constants { | |
static let actionFileTypeHeading = "Add a File" | |
static let actionFileTypeDescription = "Choose a filetype to add..." | |
static let camera = "Camera" | |
static let phoneLibrary = "Phone Library" | |
static let video = "Video" | |
static let file = "File" | |
static let alertForPhotoLibraryMessage = "App does not have access to your photos. To enable access, tap settings and turn on Photo Library Access." | |
static let alertForCameraAccessMessage = "App does not have access to your camera. To enable access, tap settings and turn on Camera." | |
static let alertForVideoLibraryMessage = "App does not have access to your video. To enable access, tap settings and turn on Video Library Access." | |
static let settingsBtnTitle = "Settings" | |
static let cancelBtnTitle = "Cancel" | |
} | |
//MARK: - showAttachmentActionSheet | |
// This function is used to show the attachment sheet for image, video, photo and file. | |
func showAttachmentActionSheet(vc: UIViewController) { | |
currentVC = vc | |
let actionSheet = UIAlertController(title: Constants.actionFileTypeHeading, message: Constants.actionFileTypeDescription, preferredStyle: .actionSheet) | |
actionSheet.addAction(UIAlertAction(title: Constants.camera, style: .default, handler: { (action) -> Void in | |
self.authorisationStatus(attachmentTypeEnum: .camera, vc: self.currentVC!) | |
})) | |
actionSheet.addAction(UIAlertAction(title: Constants.phoneLibrary, style: .default, handler: { (action) -> Void in | |
self.authorisationStatus(attachmentTypeEnum: .photoLibrary, vc: self.currentVC!) | |
})) | |
actionSheet.addAction(UIAlertAction(title: Constants.video, style: .default, handler: { (action) -> Void in | |
self.authorisationStatus(attachmentTypeEnum: .video, vc: self.currentVC!) | |
})) | |
actionSheet.addAction(UIAlertAction(title: Constants.file, style: .default, handler: { (action) -> Void in | |
self.documentPicker() | |
})) | |
actionSheet.addAction(UIAlertAction(title: Constants.cancelBtnTitle, style: .cancel, handler: nil)) | |
vc.present(actionSheet, animated: true, completion: nil) | |
} | |
//MARK: - Authorisation Status | |
// This is used to check the authorisation status whether user gives access to import the image, photo library, video. | |
// if the user gives access, then we can import the data safely | |
// if not show them alert to access from settings. | |
func authorisationStatus(attachmentTypeEnum: AttachmentType, vc: UIViewController){ | |
currentVC = vc | |
let status = PHPhotoLibrary.authorizationStatus() | |
switch status { | |
case .authorized: | |
if attachmentTypeEnum == AttachmentType.camera{ | |
openCamera() | |
} | |
if attachmentTypeEnum == AttachmentType.photoLibrary{ | |
photoLibrary() | |
} | |
if attachmentTypeEnum == AttachmentType.video{ | |
videoLibrary() | |
} | |
case .denied: | |
print("permission denied") | |
self.addAlertForSettings(attachmentTypeEnum) | |
case .notDetermined: | |
print("Permission Not Determined") | |
PHPhotoLibrary.requestAuthorization({ (status) in | |
if status == PHAuthorizationStatus.authorized{ | |
// photo library access given | |
print("access given") | |
if attachmentTypeEnum == AttachmentType.camera{ | |
self.openCamera() | |
} | |
if attachmentTypeEnum == AttachmentType.photoLibrary{ | |
self.photoLibrary() | |
} | |
if attachmentTypeEnum == AttachmentType.video{ | |
self.videoLibrary() | |
} | |
}else{ | |
print("restriced manually") | |
self.addAlertForSettings(attachmentTypeEnum) | |
} | |
}) | |
case .restricted: | |
print("permission restricted") | |
self.addAlertForSettings(attachmentTypeEnum) | |
default: | |
break | |
} | |
} | |
//MARK: - CAMERA PICKER | |
//This function is used to open camera from the iphone and | |
func openCamera(){ | |
if UIImagePickerController.isSourceTypeAvailable(.camera){ | |
let myPickerController = UIImagePickerController() | |
myPickerController.delegate = self | |
myPickerController.sourceType = .camera | |
currentVC?.present(myPickerController, animated: true, completion: nil) | |
} | |
} | |
//MARK: - PHOTO PICKER | |
func photoLibrary(){ | |
if UIImagePickerController.isSourceTypeAvailable(.photoLibrary){ | |
let myPickerController = UIImagePickerController() | |
myPickerController.delegate = self | |
myPickerController.sourceType = .photoLibrary | |
currentVC?.present(myPickerController, animated: true, completion: nil) | |
} | |
} | |
//MARK: - VIDEO PICKER | |
func videoLibrary(){ | |
if UIImagePickerController.isSourceTypeAvailable(.photoLibrary){ | |
let myPickerController = UIImagePickerController() | |
myPickerController.delegate = self | |
myPickerController.sourceType = .photoLibrary | |
myPickerController.mediaTypes = [kUTTypeMovie as String, kUTTypeVideo as String] | |
currentVC?.present(myPickerController, animated: true, completion: nil) | |
} | |
} | |
//MARK: - FILE PICKER | |
func documentPicker(){ | |
let importMenu = UIDocumentMenuViewController(documentTypes: [String(kUTTypePDF)], in: .import) | |
importMenu.delegate = self | |
importMenu.modalPresentationStyle = .formSheet | |
currentVC?.present(importMenu, animated: true, completion: nil) | |
} | |
//MARK: - SETTINGS ALERT | |
func addAlertForSettings(_ attachmentTypeEnum: AttachmentType){ | |
var alertTitle: String = "" | |
if attachmentTypeEnum == AttachmentType.camera{ | |
alertTitle = Constants.alertForCameraAccessMessage | |
} | |
if attachmentTypeEnum == AttachmentType.photoLibrary{ | |
alertTitle = Constants.alertForPhotoLibraryMessage | |
} | |
if attachmentTypeEnum == AttachmentType.video{ | |
alertTitle = Constants.alertForVideoLibraryMessage | |
} | |
let cameraUnavailableAlertController = UIAlertController (title: alertTitle , message: nil, preferredStyle: .alert) | |
let settingsAction = UIAlertAction(title: Constants.settingsBtnTitle, style: .destructive) { (_) -> Void in | |
let settingsUrl = NSURL(string:UIApplicationOpenSettingsURLString) | |
if let url = settingsUrl { | |
UIApplication.shared.open(url as URL, options: [:], completionHandler: nil) | |
} | |
} | |
let cancelAction = UIAlertAction(title: Constants.cancelBtnTitle, style: .default, handler: nil) | |
cameraUnavailableAlertController .addAction(cancelAction) | |
cameraUnavailableAlertController .addAction(settingsAction) | |
currentVC?.present(cameraUnavailableAlertController , animated: true, completion: nil) | |
} | |
} | |
//MARK: - IMAGE PICKER DELEGATE | |
// This is responsible for image picker interface to access image, video and then responsibel for canceling the picker | |
extension AttachmentHandler: UIImagePickerControllerDelegate, UINavigationControllerDelegate{ | |
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { | |
currentVC?.dismiss(animated: true, completion: nil) | |
} | |
@objc func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) { | |
if let image = info[UIImagePickerControllerOriginalImage] as? UIImage { | |
self.imagePickedBlock?(image) | |
} else{ | |
print("Something went wrong in image") | |
} | |
if let videoUrl = info[UIImagePickerControllerMediaURL] as? NSURL{ | |
print("videourl: ", videoUrl) | |
//trying compression of video | |
let data = NSData(contentsOf: videoUrl as URL)! | |
print("File size before compression: \(Double(data.length / 1048576)) mb") | |
compressWithSessionStatusFunc(videoUrl) | |
} | |
else{ | |
print("Something went wrong in video") | |
} | |
currentVC?.dismiss(animated: true, completion: nil) | |
} | |
//MARK: Video Compressing technique | |
fileprivate func compressWithSessionStatusFunc(_ videoUrl: NSURL) { | |
let compressedURL = NSURL.fileURL(withPath: NSTemporaryDirectory() + NSUUID().uuidString + ".MOV") | |
compressVideo(inputURL: videoUrl as URL, outputURL: compressedURL) { (exportSession) in | |
guard let session = exportSession else { | |
return | |
} | |
switch session.status { | |
case .unknown: | |
break | |
case .waiting: | |
break | |
case .exporting: | |
break | |
case .completed: | |
guard let compressedData = NSData(contentsOf: compressedURL) else { | |
return | |
} | |
print("File size after compression: \(Double(compressedData.length / 1048576)) mb") | |
DispatchQueue.main.async { | |
self.videoPickedBlock?(compressedURL as NSURL) | |
} | |
case .failed: | |
break | |
case .cancelled: | |
break | |
} | |
} | |
} | |
// Now compression is happening with medium quality, we can change when ever it is needed | |
func compressVideo(inputURL: URL, outputURL: URL, handler:@escaping (_ exportSession: AVAssetExportSession?)-> Void) { | |
let urlAsset = AVURLAsset(url: inputURL, options: nil) | |
guard let exportSession = AVAssetExportSession(asset: urlAsset, presetName: AVAssetExportPreset1280x720) else { | |
handler(nil) | |
return | |
} | |
exportSession.outputURL = outputURL | |
exportSession.outputFileType = AVFileType.mov | |
exportSession.shouldOptimizeForNetworkUse = true | |
exportSession.exportAsynchronously { () -> Void in | |
handler(exportSession) | |
} | |
} | |
} | |
//MARK: - FILE IMPORT DELEGATE | |
extension AttachmentHandler: UIDocumentMenuDelegate, UIDocumentPickerDelegate{ | |
func documentMenu(_ documentMenu: UIDocumentMenuViewController, didPickDocumentPicker documentPicker: UIDocumentPickerViewController) { | |
documentPicker.delegate = self | |
currentVC?.present(documentPicker, animated: true, completion: nil) | |
} | |
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL) { | |
print("url", url) | |
self.filePickedBlock?(url) | |
} | |
// Method to handle cancel action. | |
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) { | |
currentVC?.dismiss(animated: true, completion: nil) | |
} | |
} |
It crashes on file action :/
UIDocumentMenuViewController is depracted, use UIDocumentPickerViewController instead do get rid of the crash on file selection
There is a memory leak in this when I attach multiple images on the same view memory increases continuously.
line 218: @objc func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any])
should be (for swift 5 at least):
imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any])
Under what license is your code? is it free to use/modify?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Inspired from DejanEnspyra