I'm trying to figure out how to save a video in my Photo Library. To get started I tried to simply pick a video by using the UIImagePickerController and after picking it saving it again in the library using the UISaveVideoAtPathToSavedPhotosAlbum. Doesn't make much sense but I try to understand how saving videos work.
The following code does not work, as it does not save the video:
#IBAction func ChooseVideo(_ sender: Any) {
let imagePickerController = UIImagePickerController()
imagePickerController.delegate = self
imagePickerController.mediaTypes = ["public.movie"]
self.present(imagePickerController, animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
let videoURL = info[UIImagePickerController.InfoKey.mediaURL] as? URL
print(UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(videoURL!.path))
dismiss(animated: true, completion: {
UISaveVideoAtPathToSavedPhotosAlbum(videoURL!.path, self, #selector(self.video(_:didFinishSavingWithError:contextInfo:)), nil)
})
}
I hope you can help me as I couldn't find much about saving videos.
Best regards,
MB
Solution:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any])
{
// *** store the video URL returned by UIImagePickerController *** //
let videoURL = info[UIImagePickerController.InfoKey.mediaURL] as! URL
// *** load video data from URL *** //
let videoData = NSData(contentsOf: videoURL)
// *** Get documents directory path *** //
let paths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)[0]
// *** Append video file name *** //
print(paths)
let dataPath = paths.appending("/videoFileName.mp4")
// *** Write video file data to path *** //
videoData?.write(toFile: dataPath, atomically: false)
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: URL(fileURLWithPath: dataPath))
}) { saved, error in
if saved {
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
let fetchResult = PHAsset.fetchAssets(with: .video, options: fetchOptions).firstObject
// fetchResult is your latest video PHAsset
// To fetch latest image replace .video with .image
}
}
}
Use following func to save video to documents directory
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject : AnyObject])
{
// *** store the video URL returned by UIImagePickerController *** //
let videoURL = info[UIImagePickerControllerMediaURL] as! NSURL
// *** load video data from URL *** //
let videoData = NSData(contentsOfURL: videoURL)
// *** Get documents directory path *** //
let paths = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)[0]
// *** Append video file name *** //
let dataPath = documentsDirectory.stringByAppendingPathComponent("/videoFileName.mp4")
// *** Write video file data to path *** //
videoData?.writeToFile(dataPath, atomically: false)
}
now save this video in photo gellary
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: Your document directory file)
}) { saved, error in
if saved {
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
let fetchResult = PHAsset.fetchAssets(with: .video, options: fetchOptions).firstObject
// fetchResult is your latest video PHAsset
// To fetch latest image replace .video with .image
}
}
after it if you don't need then delete the image from document directory
, I hope it will work for you ...:)
Related
I'm developping an iOS phone app and I want to take a picture and store it in the app.
I am able to take the picture using a UIImagePickerController but I can't store it in my database. I'm using CoreData to store data in this app but for the picture, it seems that it is easier to store the picture in a folder and store the file path in Coredata. The issue is that I can get the picture i've took with the camera but I can't get the PNG data to store it in my folder.
func takePhoto(){
//Take the picture with the camera
imagePickerController = UIImagePickerController()
imagePickerController.delegate = self
if !UIImagePickerController.isSourceTypeAvailable(.camera){
let alertController = UIAlertController.init(title: nil, message: "Camera is not available", preferredStyle: .alert)
let okAction = UIAlertAction.init(title: "Alright", style: .default, handler: {(alert: UIAlertAction!) in })
alertController.addAction(okAction)
self.present(alertController, animated: true, completion: nil)
}
else{ imagePickerController.sourceType = .camera }
present(imagePickerController, animated: true, completion: nil)
//Store the picture in an app folder
let fileManager = FileManager.default
let imagePath = (NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString).appendingPathComponent("image")
let picture = UIImagePickerController.InfoKey.originalImage as? UIImage
let data = picture?.pngData()
fileManager.createFile(atPath: imagePath as String, contents: data, attributes: nil)
//Store the file path in CoreData
let image = Image(context: self.persistenceManager.context)
image.pathName = imagePath
self.persistenceManager.saveContext()
}
The problem is when I try to get the png data. I need a UIImage to use .pngData() but when I try to convert my picture object into UIImage from UIImagePickerController.InfoKey.originalKey I have the following warning message:
"Cast from 'UIImagePickerController.InfoKey' to unrelated type 'UIImage' always fails".
Also, I don't understand why I need to convert it because I found in the AppleDevelopper website that: "The value for this key is a UIImage object." (the key is "static let originalImage: UIImagePickerController.InfoKey" )
I've also tried to let my picture object as an UIImagePickerController.InfoKey object without converting it into an UIImage object but in this case I have the following error message:
"Value of type 'UIImagePickerController.InfoKey' has no member 'pngData' "
and the method UIImagePNGRepresentation() is not working because the expected argument type is also 'UIImage'
UIImagePickerController.InfoKey.originalImage is a String... it's a key, not the actual image.
You should use the UIImagePickerControllerDelegate, like so
extension ViewController: UIImagePickerControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
let image = info[.originalImage] as? UIImage
}
}
You see, the information from the UIPickerController is passed as a dictionary with the type [UIImagePickerController.InfoKey : Any], UIImagePicker.InfoKey contains all of the Keys that are used in this dictionary, so you use these to access the values.
EDIT:
So your takePhoto function only needs to display the picker, then handle the response later in the delegate method:
func takePhoto(){
//Take the picture with the camera
imagePickerController = UIImagePickerController()
imagePickerController.delegate = self
if !UIImagePickerController.isSourceTypeAvailable(.camera){
let alertController = UIAlertController.init(title: nil, message: "Camera is not available", preferredStyle: .alert)
let okAction = UIAlertAction.init(title: "Alright", style: .default, handler: {(alert: UIAlertAction!) in })
alertController.addAction(okAction)
self.present(alertController, animated: true, completion: nil)
}
else{ imagePickerController.sourceType = .camera }
present(imagePickerController, animated: true, completion: nil)
}
Then in your delegate you can work with the selected image
extension ViewController: UIImagePickerControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
let fileManager = FileManager.default
let imagePath = (NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString).appendingPathComponent("image")
let picture = info[.originalImage] as? UIImage
let data = picture?.pngData()
fileManager.createFile(atPath: imagePath as String, contents: data, attributes: nil)
//Store the file path in CoreData
let image = Image(context: self.persistenceManager.context)
image.pathName = imagePath
self.persistenceManager.saveContext()
}
}
As #Scriptabel has stated,
you'll need to use this delegate and access/save your image inside the function.
Class VC: UIViewController, UIImagePickerControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
let picture = info[UIImagePickerController.InfoKey.originalImage] as! UIImage
let data = picture.pngData()
//Store the picture in an app folder
let fileManager = FileManager.default
let imagePath = (NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString).appendingPathComponent("image")
fileManager.createFile(atPath: imagePath as String, contents: data, attributes: nil)
//Store the file path in CoreData
let image = Image(context: self.persistenceManager.context)
image.pathName = imagePath
self.persistenceManager.saveContext()
}
}
When I use an ImagePicker by sourceType.camera, I want to get AssetUrl in order to get a PHAsset object. I called Info[UIImagePickerControllerReferenceURL], but it's always nil. Is there any way to resolve this? or some other ways replaced? I use Xcode 8.3 and Swift 3.1
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
//
if info[UIImagePickerControllerReferenceURL] != nil{
let imageAssetUrl: URL = (info[UIImagePickerControllerReferenceURL] as? URL)!
print("\(imageAssetUrl)")
let allPhotosOptions = PHFetchOptions()
// sort by create
allPhotosOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
//OriginalImage
allPhotosOptions.predicate = NSPredicate(format: "mediaType = %d", PHAssetMediaType.image.rawValue)
let fetchResult = PHAsset.fetchAssets(withALAssetURLs: [imageAssetUrl], options: allPhotosOptions)
if fetchResult.count > 0 {
let asset = fetchResult.firstObject
}
}
picker.dismiss(animated: true) {
//
}
}
I'm trying to save a video in my app directory and then play it back in my view controller. I'm having an issue with saving and making the path. Can anyone help?
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any])
{
// Save the video to the app directory
let videoURL = info[UIImagePickerControllerMediaURL] as! NSURL
let videoData = NSData(contentsOf: videoURL as URL)
let paths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)
let documentsDirectory: AnyObject = paths[0] as AnyObject
let dataPath = documentsDirectory.appendingPathComponent("/vid1.mp4")
videoData?.write(toFile: dataPath, atomically: false)
self.dismiss(animated: true, completion: nil)
}
#IBAction func playVideoAction(_ sender: Any)
{
let paths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)
let documentsDirectory: AnyObject = paths[0] as AnyObject
let dataPath = documentsDirectory.appendingPathComponent("/vid1.mp4")
let videoAsset = (AVAsset(url: NSURL(fileURLWithPath: dataPath) as URL))
let playerItem = AVPlayerItem(asset: videoAsset)
let player = AVPlayer(playerItem: playerItem)
let playerViewController = AVPlayerViewController()
playerViewController.player = player
self.present(playerViewController, animated: true)
{
playerViewController.player!.play()
}
}
how to save video file into document directory
I've used code from this link but with the update to xcode 8/swift 3 it is not as helpful. Been stuck on this problem for awhile now.
You want to use PHImageManager.requestExportSession(forVideo:options:). This will prepare the asset asynchronously (including downloading it if needed), and create an AVExportSession you can use to save the file. By specifying you want the original, and to passthrough the content (if possible) you should get the best quality video available.
var dataPath: URL {
let paths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)
let documentsDirectory = URL(fileURLWithPath: paths[0])
return documentsDirectory.appendingPathComponent("/vid1.mp4")
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
let url = info[UIImagePickerControllerReferenceURL] as! URL
let assets = PHAsset.fetchAssets(withALAssetURLs: [url], options: nil)
let asset = assets.object(at: 0)
let options = PHVideoRequestOptions()
options.version = .original
PHImageManager.default().requestExportSession(forVideo: asset, options: options, exportPreset: AVAssetExportPresetPassthrough) { (exportSession, info) in
guard let session = exportSession else { return }
session.outputURL = self.dataPath
session.outputFileType = AVFileTypeMPEG4
session.exportAsynchronously {
DispatchQueue.main.async {
self.dismiss(animated: true, completion: nil)
}
}
}
}
If you didn't want to save to disk but just playback, you can use requestPlayerItem to get an AVPlayerItem you can use in your player:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
let url = info[UIImagePickerControllerReferenceURL] as! URL
let assets = PHAsset.fetchAssets(withALAssetURLs: [url], options: nil)
let asset = assets.object(at: 0)
let options = PHVideoRequestOptions()
options.version = .original
PHImageManager.default().requestPlayerItem(forVideo: asset, options: options) { (playerItem, info) in
let player = AVPlayer(playerItem: playerItem)
let playerViewController = AVPlayerViewController()
playerViewController.player = player
DispatchQueue.main.async {
self.dismiss(animated: true, completion: nil)
self.present(playerViewController, animated: true) {
playerViewController.player!.play()
}
}
}
}
I'm trying to pick image from device's Photo Library in method:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any])
{
userPhoto.image = info[UIImagePickerControllerOriginalImage] as! UIImage?
userPhoto.contentMode = .scaleAspectFill
userPhoto.clipsToBounds = true
dismiss(animated: true, completion: nil)
}
and save this picture in Realm (as NSData):
asset.assetImage = UIImagePNGRepresentation(userPhoto.image!)! as NSData?
...
try! myRealm.write
{
user.assetsList.append(asset)
myRealm.add(user)
}
After build succeeded and trying to pick and save image (in the app) i have app error:
'Binary too big'
What i'm doing wrong?
P.S. Sorry for my English :)
After some actions i have this code. But it's overwrite my image.
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any])
{
let imageUrl = info[UIImagePickerControllerReferenceURL] as! NSURL
let imageName = imageUrl.lastPathComponent
let documentDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
let photoURL = NSURL(fileURLWithPath: documentDirectory)
let localPath = photoURL.appendingPathComponent(imageName!)
let image = info[UIImagePickerControllerOriginalImage]as! UIImage
let data = UIImagePNGRepresentation(image)
do
{
try data?.write(to: localPath!, options: Data.WritingOptions.atomic)
}
catch
{
// Catch exception here and act accordingly
}
userPhoto.image = image
userPhoto.contentMode = .scaleAspectFill
userPhoto.clipsToBounds = true
urlCatch = (localPath?.path)!
self.dismiss(animated: true, completion: nil);
}
Don't save the image itself into realm, just save the location of the image into realm as String or NSString and load the image from that saved path. Performance wise it's always better to load images from that physical location and your database doesn't get too big
func loadImageFromPath(_ path: NSString) -> UIImage? {
let image = UIImage(contentsOfFile: path as String)
if image == nil {
return UIImage()
} else{
return image
}
}
or you just save the image name, if it's in your documents directory anyhow
func loadImageFromName(_ imgName: String) -> UIImage? {
guard imgName.characters.count > 0 else {
print("ERROR: No image name")
return UIImage()
}
let imgPath = Utils.getDocumentsDirectory().appendingPathComponent(imgName)
let image = ImageUtils.loadImageFromPath(imgPath as NSString)
return image
}
and here a rough example how to save a captured image to your directory with a unique name:
#IBAction func capture(_ sender: AnyObject) {
let videoConnection = stillImageOutput?.connection(withMediaType: AVMediaTypeVideo)
stillImageOutput?.captureStillImageAsynchronously(from: videoConnection, completionHandler: { (imageDataSampleBuffer, error) -> Void in
let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(imageDataSampleBuffer)
//self.stillImage = UIImage(data: imageData!)
//self.savedImage.image = self.stillImage
let timestampFilename = String(Int(Date().timeIntervalSince1970)) + "someName.png"
let filenamePath = URL(fileReferenceLiteralResourceName: getDocumentsDirectory().appendingPathComponent(timestampFilename))
let imgData = try! imageData?.write(to: filenamePath, options: [])
})
/* helper get Document Directory */
class func getDocumentsDirectory() -> NSString {
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let documentsDirectory = paths[0]
//print("Path: \(documentsDirectory)")
return documentsDirectory as NSString
}
https://realm.io/docs/objc/latest/#current-limitations
maximum data size is 16 MB . this is limitation of realm
Depending on how your serializing the image data (for example if it's a lossless bitmap), it's quite possible that this data exceed 16MB, which as you've stated is Realm's maximum supported size for binary properties.
When you invoke NSData.length, how large does it say your data is?
I am simply trying to have the user take a short video in the application, and when the video is recorded, the app would send it to the Parse back-end once the user clicks 'Use Video' using UIImagePickerController.
I am able to save the video locally in the idFinishPickingMediaWithInfo with
UISaveVideoAtPathToSavedPhotosAlbum(pathString!, self, nil, nil)
But I can't find anything online to save the video to Parse.
The line that that is running the error is
let videoFile:PFFile = PFFile(data: videoData)
With error handler - Cannot convert value of type 'String?' to expected argument type 'NSData'
I believe it has something to do with this line above it causing the error:
let videoData = tempImage.relativePath
I can't find code to get it working. Any help would me amazing!
#IBAction func recordAction(sender: AnyObject) {
if UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.Camera) {
print("Camera Avilable")
let imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.sourceType = .Camera
imagePicker.mediaTypes = [kUTTypeMovie as String]
imagePicker.videoMaximumDuration = 180 // Perhaps reduce 180 to 120
imagePicker.videoQuality = UIImagePickerControllerQualityType.TypeMedium
imagePicker.allowsEditing = false
imagePicker.showsCameraControls = true
self.presentViewController(imagePicker, animated: true, completion: nil)
} else {
print("Camera Unavailable")
}
}
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
let tempImage = info[UIImagePickerControllerMediaURL] as! NSURL!
let pathString = tempImage.relativePath
self.dismissViewControllerAnimated(true, completion: nil)
let videoData = tempImage.relativePath
let videoFile:PFFile = PFFile(data: videoData)
PFUser.currentUser()?.setObject(videoFile, forKey: "videoFile")
PFUser.currentUser()?.saveInBackground()
}
let videoData = NSData(contentsOfFile:tempImage.relativePath)
let videoFile:PFFile = PFFile(name:"yourfilenamewithtype", data:videoData)
file.saveInBackgroundWithBlock({(succeeded:Bool,error:NSError?)
//Handle success or failure here.
},progressBlock:{(percentDone: Int32) -> Void in
//Update your progress spinner here.percentDone will be between 0 and 100.
})
yourfilenamewithtype like this,resume.txt
The method is the try to make file to NSdata.
I have no experience with Parse, but the documentation suggests that the PFFile(data:) constructor indeed expects the contents of the file, and you give the name. You likely want to use PFFile(name: nil, contentsAtPath: pathString!).
Note that you set pathString and videoData to the same value, which is the path and not the data.
More importantly, note that if you simply load the file with NSData(contentsOfFile: pathString!) as RYANCOAL9999 suggested, you are putting the entire video file into RAM. You could use NSData(contentsOfFile: pathString!, options: .DataReadingMappedIfSafe), but I believe it's best to simply delegate the entire file copying process to the libraries.