Goal: The user selects photos from her photo gallery > Tap "Done" > Photos show up.
Problem: The photos don't show up immediately after "Done". They do show up if I open the picker again and tap "Cancel."
This worked fine (photo showed up instantly) when I was using UIImagePickerController, but then I switched to ELCImagePickerController for multiple selection and it no longer works.
Hunches
CollectionView that displays all images is not being reloaded? But I tried calling collectionView.reloadData() after the images are selected (on the completionHandler). This did not work.
If helpful, this is my elcImagePickerController function from my ViewController.swift
func elcImagePickerController(picker: ELCImagePickerController!, didFinishPickingMediaWithInfo info: [AnyObject]!) {
println("inside elcImagePickerController")
self.dismissViewControllerAnimated(true, completion: nil)
if (info.count == 0) {
return
}
var pickedImages = NSMutableArray()
for any in info {
let dict = any as! NSMutableDictionary
let image = dict.objectForKey(UIImagePickerControllerOriginalImage) as! UIImage
pickedImages.addObject(image)
}
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0), {
PHPhotoLibrary.sharedPhotoLibrary().performChanges(
{
for img in pickedImages{
// Request creating an asset from the image
// create multiple creationRequestForAssetFromImage
let createAssetRequest = PHAssetChangeRequest.creationRequestForAssetFromImage(img as! UIImage) // add to main gallery
// / Request editing the album
let albumChangeRequest = PHAssetCollectionChangeRequest(forAssetCollection: self.assetCollection, assets: self.photosAsset)
// Get a placeholder for the new asset and add it to the album editing request
let assetPlaceholder = createAssetRequest.placeholderForCreatedAsset
albumChangeRequest.addAssets([assetPlaceholder])
}
}, completionHandler: {(success, error) in
NSLog("Adding Image to Library -> %#", (success ? "Success" : "Error"))
picker.dismissViewControllerAnimated(true, completion: nil)
}
)
})
It took me a while to duplicate your scenario and error, but I managed and I found a solution!
You were on the right path with calling collectionView.reloadData() in the completionHandler, but it needed a bit more work.
1)
In my testing scenario, I've used a PHFetchResult called photosAsset as the main source for the UICollectionView (I'm presuming you have a similar setup).
You have to update this PHFetchResult to reflect the recent changes, in my case I used the following line:
self.photosAsset = PHAsset.fetchAssetsInAssetCollection(self.assetCollection, options: nil)
2)
It's correct that you have to reload the view with collectionView.reloadData(), but the code inside of the completionHandler is not run on the main thread/UI thread. So you have to call it like the following:
dispatch_async(dispatch_get_main_queue(), {self.collectionView?.reloadData()})
Sum up:
In final, your completionHandler should look something similar to this:
completionHandler: {(success, error) in
NSLog("Adding Image to Library -> %#", (success ? "Success" : "Error"))
self.photosAsset = PHAsset.fetchAssetsInAssetCollection(self.assetCollection, options: nil)
dispatch_async(dispatch_get_main_queue(), {self.collectionView?.reloadData()})
picker.dismissViewControllerAnimated(true, completion: nil)
}
Related
In iOS Swift I'm having difficulty loading an animated GIF from the device photo library to a UIImageView or creating a .gif file from it. I have no problem displaying the animated GIF if it's a file on a server or if it's right in the app. However, when I load it from the photo library, I lose the animation. I found some Objective-C solutions and tried to convert them over to Swift, but haven't had any luck.
Here is what I have so far:
#IBAction func buttonTapped(sender: UIButton) {
selectPhoto()
}
func selectPhoto() {
let photoLibraryController = UIImagePickerController()
photoLibraryController.delegate = self
photoLibraryController.sourceType = UIImagePickerControllerSourceType.PhotoLibrary
let mediaTypes:[String] = [kUTTypeImage as String]
photoLibraryController.mediaTypes = mediaTypes
photoLibraryController.allowsEditing = false
self.presentViewController(photoLibraryController, animated: true, completion: nil)
}
// UIImagePickerControllerDelegate
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
let origImage = info[UIImagePickerControllerOriginalImage] as! UIImage
self.imageView.image = origImage
picker.dismissViewControllerAnimated(true, completion: nil)
}
Optimally, I would like to save the photo library image to a gif file on the server. From there I have no trouble displaying it. However, I need some help getting the data from the photo library into some type of property, without losing the animation. Do I need to use something like ALAssetsLibrary for this?
Thanks.
Yes, Use ALAssetsLibrary → now called PHAsset.
You should get the NSData of the gif, not UIImage( because UIImage will only get the first frame.)
So basically you would do something like this:
One you get the asset
let requestOptions = PHImageRequestOptions()
requestOptions.isSynchronous = true // adjust the parameters as you wish
PHImageManager.default().requestImageData(for: asset, options: requestOptions, resultHandler: { (imageData, UTI, _, _) in
if let uti = UTI,let data = imageData ,
// you can also use UTI to make sure it's a gif
UTTypeConformsTo(uti as CFString, kUTTypeGIF) {
// save data here
}
})
Resource: PHAsset
You can get an access to the location of the image file using InfoKey .imageURL and retrieve Data from this URL
// UIImagePickerControllerDelegate
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
if let path = info[.imageURL] as? URL {
if let data = try? Data(contentsOf: path) {
self.imageView.image = UIImage(data: data)
//Now you have Data of a file: so you can convert it to image or gif or send to server or start animation (e.g. Framework JellyGIF)
self.imageView.startGif(with: .data(data))
}
picker.dismissViewControllerAnimated(true, completion: nil)
}
In iOS Swift I'm having difficulty loading an animated GIF from the device photo library to a UIImageView or creating a .gif file from it. I have no problem displaying the animated GIF if it's a file on a server or if it's right in the app. However, when I load it from the photo library, I lose the animation. I found some Objective-C solutions and tried to convert them over to Swift, but haven't had any luck.
Here is what I have so far:
#IBAction func buttonTapped(sender: UIButton) {
selectPhoto()
}
func selectPhoto() {
let photoLibraryController = UIImagePickerController()
photoLibraryController.delegate = self
photoLibraryController.sourceType = UIImagePickerControllerSourceType.PhotoLibrary
let mediaTypes:[String] = [kUTTypeImage as String]
photoLibraryController.mediaTypes = mediaTypes
photoLibraryController.allowsEditing = false
self.presentViewController(photoLibraryController, animated: true, completion: nil)
}
// UIImagePickerControllerDelegate
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
let origImage = info[UIImagePickerControllerOriginalImage] as! UIImage
self.imageView.image = origImage
picker.dismissViewControllerAnimated(true, completion: nil)
}
Optimally, I would like to save the photo library image to a gif file on the server. From there I have no trouble displaying it. However, I need some help getting the data from the photo library into some type of property, without losing the animation. Do I need to use something like ALAssetsLibrary for this?
Thanks.
Yes, Use ALAssetsLibrary → now called PHAsset.
You should get the NSData of the gif, not UIImage( because UIImage will only get the first frame.)
So basically you would do something like this:
One you get the asset
let requestOptions = PHImageRequestOptions()
requestOptions.isSynchronous = true // adjust the parameters as you wish
PHImageManager.default().requestImageData(for: asset, options: requestOptions, resultHandler: { (imageData, UTI, _, _) in
if let uti = UTI,let data = imageData ,
// you can also use UTI to make sure it's a gif
UTTypeConformsTo(uti as CFString, kUTTypeGIF) {
// save data here
}
})
Resource: PHAsset
You can get an access to the location of the image file using InfoKey .imageURL and retrieve Data from this URL
// UIImagePickerControllerDelegate
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
if let path = info[.imageURL] as? URL {
if let data = try? Data(contentsOf: path) {
self.imageView.image = UIImage(data: data)
//Now you have Data of a file: so you can convert it to image or gif or send to server or start animation (e.g. Framework JellyGIF)
self.imageView.startGif(with: .data(data))
}
picker.dismissViewControllerAnimated(true, completion: nil)
}
I'm displaying a photo in my app and I'm using UIImage for that. So far this is how I'm doing that:
func getDataFromUrl(url:NSURL, completion: ((data: NSData?, response: NSURLResponse?, error: NSError? ) -> Void)) {
NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, error) in
completion(data: data, response: response, error: error)
}.resume()
}
func downloadImage(url: NSURL){
getDataFromUrl(url) { (data, response, error) in
dispatch_async(dispatch_get_main_queue()) { () -> Void in
guard let data = data where error == nil else { return }
print("Download Finished")
self.requestPhoto.image = UIImage(data: data)
}
}
}
and then in viewDidLoad() I'm doing:
if let checkedUrl = NSURL(string: photo) {
requestPhoto.contentMode = .ScaleAspectFit
downloadImage(checkedUrl)
}
That leaves the UIImage filled with my photo, but it's not clickable and it's the size of the original component. Is there a way of adding some kind of listener or something that will display the photo on fullscreen when user taps the UIImage component?
What you need is to add a UITapGestureRecognizer to your requestPhoto image view. Something like this :
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: Selector("tapHandler"))
self.requestPhoto.addGestureRecognizer(tapGestureRecognizer)
self.requestPhoto.userInteractionEnabled = true
The last line is needed as UIImageView has it turned of by default, so the touches get canceled.
And then in the same class :
func tapHandler(sender: UITapGestureRecognizer) {
if sender.state == .Ended {
// change the size of the image view so that it fills the whole screen
}
}
The whole thing could also benefit from a flag saying if the view is fullscreen or not, so that when the user taps the fullscreen image you can reverse the process.
As always, you can read more in the docs.
How to enlarge thumbnail photo into fullscreen photo view on iOS?
I resolved this problem by creating new UIImageView object with the same image in it and set the frame of it equal to thumbnail image:
let fullscreenPhoto = UIImageView(frame: thumbnailImage.frame)
Then add image as sublayer into main window:
UIApplication.sharedApplication().windows.first!.addSubview(fullscreenPhoto)
Resizing is made with animation using block:
let windowFrame = UIApplication.sharedApplication().windows.first!.frame
UIView.animateWithDuration(0.4, delay: 0.0, options: .CurveEaseInOut, animations: {
fullscreenPhoto.frame = windowFrame
fullscreenPhoto.alpha = 1
fullscreenPhoto.layoutSubviews()
}, completion: { _ in
})
You can find ready solution here: https://github.com/lukszar/SLImageView
There is provided gesture recognizers for show and hide fullscreen.
You just need to set your UIImageView to SLImageView class in Storyboard and all magic is done.
I'm trying to integrate DKImagePicker into my app, because it simply looks better. When the user selects an album I want the app to save the images the user chose, in a new folder that I've created. Here is how I'm attempting to handle the save:
...
#IBAction func openAlbum(sender: AnyObject) {
// pickerController is DKImagePickerController
pickerController.didSelectAssets = { [unowned self] (asset: [DKAsset]) in
print("didSelectAssets")
self.transferSelectedAssetsToAlbum(asset)
}
self.presentViewController(pickerController, animated: true, completion: nil)
}
func transferSelectedAssetsToAlbum(assets: [DKAsset]) {
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
// Here I want to save all items in assets in the album
for asset in assets {
let createAssetRequest = PHAssetChangeRequest.creationRequestForAssetFromImage(asset.fullScreenImage!)
let assetPlaceholder = createAssetRequest.placeholderForCreatedAsset
let albumChangeRequest = PHAssetCollectionChangeRequest(forAssetCollection: self.assetCollection)
albumChangeRequest?.addAssets([assetPlaceholder])
}
}, completionHandler: {
success, error in
print(success)
print(error)
})
}
...
When running this Xcode, it complains saying 'NSFastEnumeration' cannot be used with array litteral. I understand this is because of albumChangeRequest?.addAssets([assetPlaholder]) should not be under for .. in loop. But, I want to save every image in the assets? How can I make it work without a for loop?
Hopefully, I made that clear enough. If not please let me know so I can clarify my question.
Apparently the error is misleading. All that was missing to make this work is ! for assetPlaceholder. So all I did was replace albumChangeRequest?.addAssets([assetPlaceholder]) with albumChangeRequest?.addAssets([assetPlaceholder!])
Besides browsing the Photolibrary I've enabled the Camera to add a photo to my
#IBAction func startCameraButtonTapped(sender: AnyObject) {
let startCameraController = UIImagePickerController()
startCameraController.delegate = self
startCameraController.sourceType = UIImagePickerControllerSourceType.Camera
startCameraController.allowsEditing = true
self.presentViewController(startCameraController, animated: true, completion: nil)
//invoiceImageHolder.image!.scaleAndRotateImage2(280)
//self.invoiceImageHolder.transform = CGAffineTransformMakeRotation((180.0 * CGFloat(M_PI)) / 180.0)
}
Every user input can be deleted, but now I'd like to know where the corresponding photo is. When I browse the photo library I can't see the photos taken with my app? Where are those photos stored?
[edit]
This is my saveImage process
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
let selectedImage = info[UIImagePickerControllerOriginalImage] as! UIImage
saveImage(selectedImage, path: fileInDocumentsDirectory("\(uniqueImageName)"))
// set the unique image reference in textfield
// Set photoImageView to display the selected image
self.invoiceImageHolder.image = selectedImage
// Dismiss the picker
dismissViewControllerAnimated(true, completion: nil)
}
func saveImage(image: UIImage, path: String) -> Bool{
let pngImageData = UIImagePNGRepresentation(image)
// let jpgImageData = UIImageJPEGRepresentation(image, 1.0)
let result = pngImageData?.writeToFile(path, atomically: true)
//print("Save result \(result)")
return result!
}
EDIT hmm I think I see where the photos are stored, but now how do I browse to them for deletions?
I think it's here:
/var/mobile/Containers/Data/Application/CBFE9E9D-A657-4011-8B9F-EF0C9BB2C603/Documents/
BUT everytime i restart the app the part between Application and /Documents is different??
Still not a working solution, but for those newbies as me this is a valuable readup: http://www.techotopia.com/index.php/Working_with_Directories_in_Swift_on_iOS_8
[EDIT 2]
with this code i am able to see all whiles stored with my app. I was a bit surprise to see all the files
// Files stored in my app filemanager
let filemgr = NSFileManager.defaultManager()
do {
print("Start filemanager")
let filelist = try filemgr.contentsOfDirectoryAtPath(documentsDirectory())
for filename in filelist {
print(filename)
}
} catch {
print("No directory path \(error)")
}
// end filemanager
UIImagePickerController() doesn't automatically save your photos into the library. See its UIImagePickerControllerDelegate protocol and you'll see that it has a method returning your UIImage after making a photo. Then you can save it:
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
if let image = info[UIImagePickerControllerOriginalImage] as? UIImage {
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
}
}
you save them - not in the photo library - but as 'simply' files inside your app's documents folder. There is no standard UI for deletion then.
You need to write your own ViewController to show & handle deletions then.