bcattle bcattle - 2 months ago 24
Objective-C Question

How to to filter by media subtype using NSPredicate with PHFetchOptions

How do I filter by media subtype using

NSPredicate
with
PHFetchOptions
?
I'm trying to exclude slow mo (high frame rate) and time lapse videos. I keep getting strange results when I try to use the
predicate
field of
PHFetchOptions
.

My phone has a bunch (120+) regular videos, and one slow mo video. When I run the example from Apple's docs, I get the correct result back: 1 slow mo video.

PHFetchOptions *options = [PHFetchOptions new];
options.predicate = [NSPredicate predicateWithFormat:@"(mediaSubtype & %d) != 0 || (mediaSubtype & %d) != 0", PHAssetMediaSubtypeVideoTimelapse, PHAssetMediaSubtypeVideoHighFrameRate];


But I'm trying to exclude slow mo, rather than select it. However if I negate the filter condition, I get zero results back:

options.predicate = [NSPredicate predicateWithFormat:@"(mediaSubtype & %d) == 0", PHAssetMediaSubtypeVideoHighFrameRate];

<PHFetchResult: 0x1702a6660> count=0


Confusingly, the Apple docs list the name of the field as
mediaSubtypes
(with an "s"), while their sample predicate is filtering on
mediaSubtype
(without an "s").

Trying to filter on
mediaSubtypes
produces an error:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Can't do bit operators on non-numbers'


Has anyone been able to make heads or tails of this predicate?

Answer

First of all, let me say about the s in mediaSubtypes available on PHAsset and only Photos team at Apple knows why and how mediaSubtype works without an s.

@property (nonatomic, assign, readonly) PHAssetMediaType mediaType;
@property (nonatomic, assign, readonly) PHAssetMediaSubtype mediaSubtypes;

Now let me try to explain why it doesn't work as expected. Here's how you compare using NS_OPTIONS or any other bitwise operation.

PHAsset* asset = // my asset;
if((asset.mediaSubtypes & PHAssetMediaSubtypePhotoScreenshot) == PHAssetMediaSubtypePhotoScreenshot) {
    // This is a screenshot
}

Here's how you would compare to see if asset is not a screenshot-

PHAsset* asset = // my asset;
if(!((asset.mediaSubtypes & PHAssetMediaSubtypePhotoScreenshot) == PHAssetMediaSubtypePhotoScreenshot)) {
// This is not a screenshot
}

Now in your case the predicate should be-

  • Fetch all videos with high frame rate

    [NSPredicate predicateWithFormat:@"((mediaSubtype & %d) == %d)", PHAssetMediaSubtypeVideoHighFrameRate, PHAssetMediaSubtypeVideoHighFrameRate];
    
  • Fetch all videos without high frame rate

    [NSPredicate predicateWithFormat:@"!((mediaSubtype & %d) == %d)", PHAssetMediaSubtypeVideoHighFrameRate, PHAssetMediaSubtypeVideoHighFrameRate];
    

This works beautifully in my case. Hope it helps somebody else.