I'm using [PHCachingImageManager] to cache videos in a collection view. I can display the images generated with the cache manager, but pulling AVAssets isn't working.
I try to get an AVAsset by using the .requestAVAsset(forVideo:...) method but the [asset] that is returned to the result handler is nil. I get an error when trying to unwrap the returned [asset].
I also looked at the [audioMix] and [info] parameters returned to the result handler and they were nil was well. Does anyone know what I'm doing wrong/missing here?
viewDidLoad() {
let videosAlbum = PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .smartAlbumVideos, options: nil)
self.videoAssets = PHAsset.fetchAssets(in: videosAlbum.object(at: 0), options: nil)
self.imgCacheManager.startCachingImages(for: self.videoAssets.objects(at: [0, videoAssets.count-1]), targetSize: CGSize(width: 150, height: 150), contentMode: .aspectFit, options: nil)
}
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let selectedAsset = self.imgCacheManager.requestAVAsset(forVideo: self.videoAssets.object(at: indexPath.item), options: nil, resultHandler: {
asset,_,_ in
self.delegate.selectedVideo(Asset: asset!) // this is the line I get the error
})
}
My delegate is an optional but for sure has something in it, so I'm confident it's the unwrapped [asset] that's throwing the error.
It looks like I was trying to request video assets that were stored in iCloud without setting the video request options to allow network access.
When requesting the video from the PHCachingImageManager, I failed to pass any PHVideoRequestOptions object. Adding the lines below just prior to requesting the asset fixes this.
let options = PHVideoRequestOptions()
options.isNetworkAccessAllowed = true
I found this thread on an open source image picker using the Photos framework. The videos I was testing with were indeed in iCloud which caused my returned asset to be nil. The linked thread also has a good example of hooking up a progress handler in objective C:
options.networkAccessAllowed = YES;
options.progressHandler = ^(double progress, NSError *error, BOOL *stop, NSDictionary *info)
{
...
};
Related
Are there any approaches for getting the URL of a PHAsset that does not involve asynchronous requests/processes?
I realize that blocking the main thread when making requests like this is a no-no, but if you have a small number of assets to query, is it possible to get them any other way?
So far, the only ways I've found to get asset URLs seem to be things like the following:
somePHAssetObject.requestContentEditingInput(with: nil, completionHandler: {
input, info in
guard let input = input
else { fatalError("can't get info: \(info)") }
let assetURL = input.audiovisualAsset as! AVURLAsset
})
Or
PHCachingImageManager().requestAVAsset(forVideo: SomePHAssetObject, options: nil, resultHandler: {
(asset: AVAsset?, audioMix: AVAudioMix?, info: [AnyHashable: Any]?) in
let vidURL = asset as! AVURLAsset
})
Are there any ways to do something like this without an async approach? I'm just not seeing a URL property anywhere on a PHAsset, but could be missing something really obvious.
I don't believe that there is an API like that, but you can force the thread to wait for the handler using semaphore, e.g., for the second example you have presented:
let semaphore = DispatchSemaphore(value: 0)
PHCachingImageManager().requestAVAsset(forVideo: SomePHAssetObject, options: nil, resultHandler: { (asset: AVAsset?, audioMix: AVAudioMix?, info: [AnyHashable: Any]?) in
let vidURL = asset as! AVURLAsset
semaphore.signal()
})
_ = semaphore.wait(timeout: DispatchTime.distantFuture)
Although I really don't understand why you would want to do it synchrously.
I'm currently building a share extension that accepts URLs. As part of this I've customised my share screen as outlined in a previous question to create a full screen view controller. This is all working fine. However in the default share composer view I noticed there was a preview image of the web page. I'm trying to access this in my extension but I can't seem to get hold of it.
Specifically I've been trying to use the method
loadPreviewImage
https://developer.apple.com/reference/foundation/nsitemprovider/1403925-loadpreviewimage
You will note in the docs that this says the following for the completion handler
completionHandler
A completion handler block to execute with the results. The first parameter of this block must be a parameter of type NSData, NSURL, UIImage (in iOS), or NSImage (in macOS) for receiving the image data. For more information about implementing the block, see CompletionHandler.
However if I try to set this as a UIImage in my completion block I get an error of
Cannot convert value of type '(UIImage, _) -> ()' to expected argument
type 'NSItemProvider.CompletionHandler!'
example code where itemProvider is confirmed to be an instance of NSItemProvider via guard statements
itemProvider.loadPreviewImage(options: nil) { (image: UIImage, error) in
}
The docs for the completion Handler say to set this to what type you want and it will attempt to coerce the data to the type you specify. Has anyone seen this before? I'm not sure what to do here as I can't see what I'm doing wrong.
https://developer.apple.com/reference/foundation/nsitemprovider/completionhandler
If all else fails I'll look at using some Javascript to get an image from the dom but I would have liked the preview image that Apple seemed to provide
I don't know why the code in
itemProvider.loadPreviewImage(options: nil) { (image: UIImage, error) in
}
not called when Post button tapped.
My round way is saving the preview image in method
override func configurationItems() -> [Any]! {
}
as
let inputItem: NSExtensionItem = self.extensionContext?.inputItems[0] as! NSExtensionItem
let itemProvider = inputItem.attachments![0] as! NSItemProvider
if (itemProvider.hasItemConformingToTypeIdentifier("public.url")) {
itemProvider.loadPreviewImage(options: nil, completionHandler: { (item, error) in // 画像を取得する
if let image = item as? UIImage {
if let data = UIImagePNGRepresentation(image) {
self.photoNSURL = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent("preview.png") as NSURL!
do {
try data.write(to: self.photoNSURL as URL, options: .atomic)
} catch {
print("\(#file)#\(#function)(\(#line)): error: \(error.localizedDescription)")
}
}
}
})
}
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)
}
I am getting this error when I am trying to play multiple videos using this swift library (https://github.com/piemonte/player). Not sure if it's related to that player, or to the Photos framework or what though.
What happens is, I have a view that will display either a photo or a video. Everything works fine a few times until a few videos have played and then this message will pop up, followed by all the videos not being able to play and in their place you just see a black screen, and then I get a memory usage error.
I am using a library called SwipeView and here is some relevant code which may be helpful.
func swipeView(swipeView: SwipeView!, viewForItemAtIndex index: Int, reusingView view: UIView!) -> UIView! {
let asset: PHAsset = self.photosAsset[index] as PHAsset
// Create options for retrieving image (Degrades quality if using .Fast)
// let imageOptions = PHImageRequestOptions()
// imageOptions.resizeMode = PHImageRequestOptionsResizeMode.Fast
var imageView: UIImageView!
let screenSize: CGSize = UIScreen.mainScreen().bounds.size
let targetSize = CGSizeMake(screenSize.width, screenSize.height)
var options = PHImageRequestOptions()
options.resizeMode = PHImageRequestOptionsResizeMode.Exact
options.synchronous = true
if (asset.mediaType == PHAssetMediaType.Image) {
PHImageManager.defaultManager().requestImageForAsset(asset, targetSize: targetSize, contentMode: .AspectFill, options: options, resultHandler: {(result, info) in
if (result.size.width > 200) {
imageView = UIImageView(image: result)
}
})
return imageView
} else if (asset.mediaType == PHAssetMediaType.Video) {
self.currentlyPlaying = Player()
PHImageManager.defaultManager().requestAVAssetForVideo(asset, options: nil, resultHandler: {result, audio, info in
self.currentlyPlaying.delegate = self
self.currentlyPlaying.playbackLoops = true
self.addChildViewController(self.currentlyPlaying)
self.currentlyPlaying.didMoveToParentViewController(self)
var t = result as AVURLAsset
var url = t.valueForKey("URL") as NSURL
var urlString = url.absoluteString
self.currentlyPlaying.path = urlString
})
return self.currentlyPlaying.view
}
return UIView()
}
func swipeViewItemSize(swipeView: SwipeView!) -> CGSize {
return self.swipeView.bounds.size;
}
func swipeView(swipeView: SwipeView!, didSelectItemAtIndex index: Int) {
self.currentlyPlaying.playFromBeginning()
}
func swipeViewCurrentItemIndexDidChange(swipeView: SwipeView!) {
self.currentlyPlaying.stop()
}
Any thoughts would be great.
I had this same "Connection to assetsd was interrupted or assetsd died" error.
Usually followed by a memory warning.
I tried to find retain cycles, but it wasn't it.
The problem for me had to do with the known fact that the resultHandler block of any PHImageManager request...() is not called on the main queue.
So we cannot run UIView code directly within those blocks without asking for trouble later on in the app life.
To fix this we may for instance run all UIView code that would be executed within the scope of the resultHandler block inside a dispatch_async(dispatch_get_main_queue(), block)
I was having this problem because I did not realize that one of the functions that I was calling within one of the resultHandler.s of my app did eventually call some UIView code down the line.
And so I was not forwarding that call to the main thread.
Hope this helps.
The issue is occurring because of the TargetSize only. The PHImageManagerMaximumSize option is the culprit. Set the CGSIZE as you want for the imageview.
Your problem might be that you create a new Player-object everytime you start playing a video:
self.currentlyPlaying = Player()
I would recommend you to either remove it after the file has finished playing or to create one player which you will reuse. Otherwise it will stay in the memory.v
I am trying to request the full image from a PHAsset.
My code is as follows
#IBAction func nextTap(sender: AnyObject)
{
for asset in _selectedAssets
{
PHImageManager.defaultManager().requestImageDataForAsset(asset, options: nil)
{
imageData,dataUTI,orientation,info in
{
println("worked")
}
}
}
}
I am getting this error though:
I have read the documentation on this call (https://developer.apple.com/library/IOs/documentation/Photos/Reference/PHImageManager_Class/index.html#//apple_ref/occ/instm/PHImageManager/requestImageDataForAsset:options:resultHandler:)
And I have, I think, followed the trailing syntax closure required, and put in the appropriate parameters.
I watched the WWDC 2014 video on this as well.
Cannot seem to get this to work for the full image.
If I want a smaller sized image, I used this code:
PHImageManager.defaultManager().requestImageForAsset(asset, targetSize:_cellSize, contentMode: .AspectFill, options: nil)
{
result, info in
if reuseCount == cell.reuseCount
{
cell.imageView.image = result
cell.imageView.frame = CGRectMake(0, 0,self._cellSize.width,self._cellSize.height)
}
}
And this works.
So Im not sure what I am doing wrong in my fist call but would love some insight in to how to request a full size image from a PHAsset and/or whats wrong with my syntax in the first code block.
Thanks
Update:
Here is the error message
"Cannot invoke 'println()' with an argument list type of '(PHAsset, options:NilLiteralConvertible, (($T6, ($T6,$T7,$T8,($T6,$T7,$T8)...."
It turns out the brackets after the 'in' screw everything up.
Removing those brackets fixed the problem
for asset in _selectedAssets
{
PHImageManager.defaultManager().requestImageDataForAsset(asset, options: nil)
{
imageData,dataUTI,orientation,info in
println("worked")
}
}
This works for me:
PHImageManager.defaultManager().requestImageDataForAsset(asset, options: requestOptions) {
(imageData: NSData!, dataUTI: String!, orientation: UIImageOrientation, info: [NSObject : AnyObject]!) -> Void in
println("Yay!")
}