Skip to content

Instantly share code, notes, and snippets.

@deepakraj27
Last active November 1, 2023 08:40
Show Gist options
  • Save deepakraj27/2b5066c488c38d8456678297b17e0bea to your computer and use it in GitHub Desktop.
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
//
// 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)
}
}
@deepakraj27
Copy link
Author

deepakraj27 commented Jan 25, 2018

Inspired from DejanEnspyra

@grifas
Copy link

grifas commented Sep 13, 2018

It crashes on file action :/

@aparvaiz
Copy link

aparvaiz commented Oct 9, 2018

UIDocumentMenuViewController is depracted, use UIDocumentPickerViewController instead do get rid of the crash on file selection

@Sajjad-Zafar
Copy link

There is a memory leak in this when I attach multiple images on the same view memory increases continuously.

@eamonnalphin
Copy link

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])

@RaghadAb
Copy link

RaghadAb commented Mar 21, 2021

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