首页
网站首页
公司简介
资讯中心
推荐内容
返回顶部
配置获取隐私数据权限声明,hierarchy两个问题的解决方法
发布时间:2020-04-15 14:55
浏览次数:

www.64222.com 1gif study

iOS8之后,苹果推出了PhotoKit,让开发者在处理相册相关的业务时,可以更加得心应手。github上的开发者针对PhotoKit做了一层很优秀的封装CTAssetsPickerController,如果只需要支持iOS8+,那么可定制程度非常高的CTAssetsPickerController是个不错的选择。但是由于现有的业务还是需要支持iOS7,所以并不能完全舍弃使用AssetsLibrary的方式来访问相册。因此也就需要自己封装一套兼容iOS7的相册管理器。

www.64222.com 2

最近在做一个邮政的项目,在刚进入邮寄控制器的时候需要对用户是否有未交寄的订单进行判断,如果有的话需要用提醒框告诉用户,如下图

众所周知,iOS默认是不支持gif类型图片的显示的,但是我们项目中常常是需要显示gif为动态图片。那肿么办?第三方库?是的 ,很多第三方都支持gif , 如果一直只停留在用第三方上,技术难有提高。上版本的 Kingfisher 也支持gif ,研究了一番,也在网上搜索了一番,稍微了解了下iOS实现gif的显示,在此略做记录。

本文涉及代码:TBVAssetsPicker

iOS 10的一大变化是更强的隐私数据保护。在文档中是这么描述的:

www.64222.com 3提示页面

本篇文章要实现的效果如图:

统一asset以及collection

AssetsLibrary PhotoKit
ALAssetsGroup PHAssetCollection
ALAsset PHAsset
TBVAsset TBVCollection

相片选择器最终需要向外部提供统一的标识相片的结构。同样,统一结构能让相片选择器更加优雅地实现内部逻辑。所以这里我声明了两个对应的类:TBVAssetTBVCollection,并提供一些最基本的功能。

@interface TBVAsset : NSObject/** * PHAsset or ALAsset */@property (strong, nonatomic) NSObject *asset;+ (instancetype)assetWithOriginAsset:(NSObject *)asset;/** 本地标识 */- (NSString *)assetLocalIdentifer;/** 源照片尺寸 */- assetPixelSize;@end@interface TBVCollection : NSObject/** * ALAssetsGroup or PHAssetCollection */@property (strong, nonatomic) NSObject *collection;+ (instancetype)collectionWithOriginCollection:(NSObject *)aCollection;/** 相簿名 */- (NSString *)collectionTitle;/** 估算的相簿相片个数 */- (NSInteger)collectionEstimatedAssetCount;/** 精确的相簿相片个数 */- (NSInteger)collectionAccurateAssetCountWithFetchOptions:filterOptions;- (NSInteger)collectionAccurateAssetCountWithMediaType:(TBVAssetsPickerMediaType)mediaType;@end

有了这些最基本的功能,在实现相册选择器时,就可以方便地对资源进行操作了。其实对于这部分的兼容处理,主要就是对两个不同的库进行封装,使其呈现同样的外观,后续的几步大体也是围绕这个目标进行。

You must statically declare your app’s intended use of protected data classes by including the appropriate purpose string keys in your Info.plist file.

可是在网络状态不好的情况下,接口的响应需要时间,在没有弹出UIAlertController的时候,用户可能会进入其他的页面就回导致以下的两个问题。

www.64222.com 4gif显示效果

封装manager

由于是两个不同版本的库,并且AssetsLibrary已经在iOS9时被弃用,使用时会产生deprecated警告,所以我分别对ALAssetsLibraryPHCachingImageManager进行了封装,然后通过统一的接口TBVAssetsManagerProtocol暴露其功能。

一般相册选择器具有如下页面及对应功能:

  • 首页
    • 相簿名
    • 相簿缩略图
    • 相簿拥有相片数
  • 预览页
    • 相片缩略图
    • 选中相片大小
  • 浏览页
    • 相片大图
    • 选中相片大小

所以我提供的接口如下:

//====================================// image//====================================/** requestImage返回都是一个RACTuple,first是Image,second是是否为degraded *//** 请求特定大小的图片 */- (RACSignal *)requestImageForAsset:(TBVAsset *)asset targetSize:targetSize contentMode:(TBVAssetsPickerContentMode)contentMode;/** 请求相簿缩略图 */- (RACSignal *)requestPosterImageForCollection:(TBVCollection *)collection mediaType:(TBVAssetsPickerMediaType)mediaType;/** 请求相片缩略图 */- (RACSignal *)requestPosterImageForAsset:(TBVAsset *)asset;/** 请求相片原图 */- (RACSignal *)requestFullResolutionImageForAsset:(TBVAsset *)asset;//====================================// asset / collection//====================================/** 请求相片资源大小 */- (RACSignal *)requestSizeForAssets:(NSArray <TBVAsset *> *)assets;/** 请求所有相簿 */- (RACSignal *)requestAllCollections;/** 请求所有相片资源 */- (RACSignal *)requestAssetsForCollection:(TBVCollection *)collection mediaType:(TBVAssetsPickerMediaType)mediaType;/** 请求相机胶卷相簿(针对一般业务首先进入相机胶卷的预览页) */- (RACSignal *)requestCameraRollCollection;//====================================// video//====================================/** 请求AVPlayerItem */- (RACSignal *)requestVideoForAsset:(TBVAsset *)asset;/** 请求AVURLAsset */- (RACSignal *)requestURLAssetForAsset:(TBVAsset *)asset;

实现以上接口,一般相册选择器的功能点就已经完成大半了。

由于自定义的相册manager都遵守TBVAssetsManagerProtocolTBVAssetsPickerManager的实现就变得相对简单,没有一大串令人厌烦的if-else。当然TBVAssetsPickerManager本身也是遵守TBVAssetsManagerProtocol的。

@interface TBVAssetsPickerManager() @property (strong, nonatomic) NSObject<TBVAssetsManagerProtocol> *realManager;@property (strong, nonatomic) NSMutableArray *requestIdList;@property (assign, nonatomic) BOOL photoKitAvailable;@end#pragma mark life cycle- (instancetype)init { self = [super init]; if  { _photoKitAvailable = NSClassFromString(@"PHImageManager") != nil; } return self;}#pragma mark TBVAssetsManagerProtocol....#pragma mark getter setter- (NSObject *)realManager{ if (_realManager == nil) { if (self.photoKitAvailable) { _realManager = [[TBVCachingImageManager alloc] init]; } else { _realManager = [[TBVAssetsLibrary alloc] init]; } } return _realManager;}

这样_realManager拿到的就是当前版本最新的相册manager了。

简单的说访问用户数据都需要现在Info.plist中声明,否则会crash。这些用户数据包括:

  • Presenting view controllers on detached view controllers is discouraged当用户在没有弹出提醒框的时候,push进入了下一个控制器。虽然在下一个控制器可以弹出提醒框,但是会报出这个警告。这个警告的条件是需要弹出UIAlertController的控制器仍然在导航控制器的栈中,但不是栈顶控制器。

  • Attempt to present whose view is not in the window hierarchy这个警告和上个警告的区别在于,控制器已经不在导航控制器的栈中了(用户pop返回了上级页面)。控制器已经不在窗口的层级中了。

可以开始和暂停gif的播放,滑动时停止播放,这个简书也是这么做得,好多app为了滑动时顺畅,停止了gif。

接口的实现

其实接口文档描述的还是非常清晰的,所以这里只是罗列了下代码,并没有针对每一步做解释,因为这些基本的操作进去头文件看看就全明白了。

Contacts, Calendar, Reminders, Photos, Bluetooth Sharing, Microphone, Camera, Location, Health, HomeKit, Media Library, Motion, CallKit, Speech Recognition, SiriKit, TV Provider.

虽然上面的问题不会让程序崩溃,但是会让以后项目的层级混乱,不易于维护.而且会导致控制器一直无法被销毁.

下面要进入正文啦!

- requestImageForAsset:targetSize:targetSize:contentMode:

这个接口主要用来获取非原图。

  • TBVCachingImageManager
    • 关于PHImageRequestOptions的deliveryMode,
      • 设置为PHImageRequestOptionsDeliveryModeOpportunistic并且synchronous为NO时,请求可能会先返回一张缩略图,然后再返回一张大图,这个可以通过获取请求回调字典中PHImageResultIsDegradedKey对应value来判别
      • PHImageRequestOptionsDeliveryModeHighQualityFormat和PHImageRequestOptionsDeliveryModeFastFormat都返回一张图片,只不过前者返回的图片的质量高于或等于请求的质量,而后者可能返回一张质量稍低的图片
  • TBVAssetsLibrary
    • 由于AssetsLibrary并没有提供获取特定尺寸的相片接口,所以这里只是返回thumbnail、aspectRatioThumbnail、fullScreenImage中尺寸和目标大小最接近的一张图片。
// TBVCachingImageManager- (RACSignal *)requestImageForAsset:(TBVAsset *)asset targetSize:targetSize contentMode:(TBVAssetsPickerContentMode)contentMode { return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { PHImageRequestID requestId = [self.imageManager requestImageForAsset:(PHAsset *)asset.asset targetSize:targetSize contentMode:[self contentModeForCustomContentMode:contentMode] options:self.defaultImageRequestOptions resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) { [subscriber sendNext:RACTuplePack(result, info[PHImageResultIsDegradedKey])]; if (![info[PHImageResultIsDegradedKey] boolValue]) { [subscriber sendCompleted]; } }]; return [RACDisposable disposableWithBlock:^{ [self.imageManager cancelImageRequest:requestId]; }]; }];}// TBVAssetsLibrary- (RACSignal *)requestImageForAsset:(TBVAsset *)aAsset targetSize:targetSize contentMode:(TBVAssetsPickerContentMode)contentMode { return [[[RACSignal return:aAsset.asset] deliverOn:[RACScheduler scheduler]] map:^id(ALAsset * asset) { CGImageRef resultImageRef = nil; BOOL degraded = NO; if (targetSize.width < CGImageGetWidth(asset.thumbnail) && targetSize.height < CGImageGetHeight(asset.thumbnail)) { // TBVAssetsPickerContentModeFill resultImageRef = asset.thumbnail; degraded = YES; } if (!resultImageRef) { CGImageRef aspectRatioThumbnail = asset.aspectRatioThumbnail; if (targetSize.width < CGImageGetWidth(aspectRatioThumbnail) && targetSize.height < CGImageGetHeight(aspectRatioThumbnail)) { // TBVAssetsPickerContentModeFit resultImageRef = aspectRatioThumbnail; } if (!resultImageRef) { ALAssetRepresentation *assetRepresentation = [asset defaultRepresentation]; resultImageRef = [assetRepresentation fullScreenImage]; } } UIImage *resultImage = nil; if (resultImageRef) { resultImage = [UIImage imageWithCGImage:resultImageRef scale:BQAP_SCREEN_SCALE orientation:UIImageOrientationUp]; } return RACTuplePack(resultImage, @); }];}

www.64222.com 510之前只需要获取位置时配置,现在更严格了,比如需要调用相册访问权限,也需要在Info.plist中配置privacy。好在这些key的名字在Xcode 8中已经有了自动补全。添加一个属性,输入Privacy后就会出现自动提示:www.64222.com 6后面填的string会在弹出用户允许时展示在描述里。谢谢@Nidom提醒,如果描述空着提交AppStore时会拒绝。www.64222.com 7

  1. 可以使用蒙板,在提示框没有展示之前,防止用户点击。
  2. 判断导航控制器的栈顶控制器是否为当前控制器

www.64222.com 8期待...

-requestPosterImageForCollection:mediaType:
  • TBVCachingImageManager
    • 通过-fetchKeyAssetsInAssetCollection:options:获取相簿keyAssets,最多可以返回三个
    • 最终还是通过requestPosterImageForAsset:获取缩略图
    • 获取keyAssets前,需要设置options对资源进行过滤:
+ (instancetype)tbv_fetchOptionsWithCustomMediaType:(TBVAssetsPickerMediaType)mediaType { NSArray *mediaTypes = [self tbv_mediaTypesWithCustonMediaType:mediaType]; if (!mediaTypes.count) return nil; PHFetchOptions *options = [[PHFetchOptions alloc] init]; NSMutableString *predicateString = [NSMutableString string]; for (NSNumber *mediaType in mediaTypes) { [predicateString appendFormat:@"mediaType = %@", mediaType]; if (![mediaType isEqual:mediaTypes.lastObject]) [predicateString appendString:@" || "]; } options.predicate = [NSPredicate predicateWithFormat:predicateString]; return options;}
  • TBVAssetsLibrary
    • ALAssetsGroup有posterImage属性,直接返回相簿缩略图
    • 和PhotoKit不同,AssetLibrary通过ALAssetsGroup的setAssetsFilter方法进行过滤
[group setAssetsFilter:[ALAssetsFilter tbv_assetsFilterWithCustomMediaType:mediaType]];

这样设置以后,后续针对group的操作都会在过滤的结果中进行了。

// TBVCachingImageManager- (RACSignal *)requestPosterImageForCollection:(TBVCollection *)collection mediaType:(TBVAssetsPickerMediaType)mediaType { return [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { PHFetchOptions *fetchOptions = [PHFetchOptions tbv_fetchOptionsWithCustomMediaType:mediaType]; fetchOptions.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:YES]]; PHAssetCollection *realCollection = (PHAssetCollection *)collection.collection; /* fetchKeyAssetsInAssetCollection 获取至多三张 */ PHFetchResult *result = [PHAsset fetchKeyAssetsInAssetCollection:realCollection options:fetchOptions]; if (!result.count) { [subscriber sendNext:[RACSignal empty]]; [subscriber sendCompleted]; return nil; } TBVAsset *posterAsset = [TBVAsset assetWithOriginAsset:result.firstObject]; [subscriber sendNext:[self requestPosterImageForAsset:posterAsset]]; [subscriber sendCompleted]; return nil; }] switchToLatest];}// TBVAssetsLibrary- (RACSignal *)requestAssetsForCollection:(TBVCollection *)collection mediaType:(TBVAssetsPickerMediaType)mediaType { return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { NSMutableArray *assets = [NSMutableArray array]; ALAssetsGroup *group = (ALAssetsGroup *)collection.collection; [group setAssetsFilter:[ALAssetsFilter tbv_assetsFilterWithCustomMediaType:mediaType]]; [group enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) { if  { [assets addObject:[TBVAsset assetWithOriginAsset:result]]; } else { [subscriber sendNext:assets]; [subscriber sendCompleted]; } }]; return nil; }];}

欢迎关注我的微博:@没故事的卓同学相关链接:原文:Privacy Settings in iOS 10WWDC 2016 Session 709 Engineering Privacy for Your UsersFull list of Info.plist keys

我们一般从网络上下载的gif图片其实是将很多帧静态图片循环播放产生的动态效果,那么在iOS中,如果我们想要显示动态图,同样需要先把gif资源解析为一阵一阵的UIImage然后设定间隔时长,不断播放即可。思路是不是很简单呢?那么看看如何实现。

-requestPosterImageForAsset:

获取asset的缩略图,需要注意的一点就是:在获取缩略图的情况下,Fill比Fit获取的图片要清晰

// TBVCachingImageManager- (RACSignal *)requestPosterImageForAsset:(TBVAsset *)asset { return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { CGSize posterSize = CGSizeMake(kBQPosterImageWidth * BQAP_SCREEN_SCALE, kBQPosterImageHeight * BQAP_SCREEN_SCALE); /* 在获取缩略图的情况下,Fill比Fit获取的图片要清晰 */ PHImageRequestID requestId = [self.imageManager requestImageForAsset:(PHAsset *)asset.asset targetSize:posterSize contentMode:PHImageContentModeAspectFill options:self.defaultImageRequestOptions resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) { [subscriber sendNext:RACTuplePack(result, info[PHImageResultIsDegradedKey])]; if (![info[PHImageResultIsDegradedKey] boolValue]) { [subscriber sendCompleted]; } }]; return [RACDisposable disposableWithBlock:^{ [self.imageManager cancelImageRequest:requestId]; }]; }];}// TBVAssetsLibrary- (RACSignal *)requestPosterImageForAsset:(TBVAsset *)asset { return [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { CGSize posterSize = CGSizeMake(kBQPosterImageWidth * BQAP_SCREEN_SCALE, kBQPosterImageHeight * BQAP_SCREEN_SCALE); [subscriber sendNext:[self requestImageForAsset:asset targetSize:posterSize contentMode:TBVAssetsPickerContentModeFill]]; [subscriber sendCompleted]; return nil; }] switchToLatest];}
if ([self.navigationController.topViewController isMemberOfClass:[self class]]) { [self presentViewController:alertC animated:YES completion:nil]; }

分几个步骤:

-requestFullResolutionImageForAsset:

获取原图时有一点很重要,就是尽量不要快速连续地获取原图,大图也可以列入这个范畴。连续地获取大图或者原图,设备的内存会急剧增高,甚至崩溃,这种情况通常在上传图片时比较常见。所以在上传图片时,尽量上传一张原图后再获取下一张原图进行上传,而不是全部获取完成之后再上传。

// TBVCachingImageManager- (RACSignal *)requestFullResolutionImageForAsset:(TBVAsset *)asset { return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { self.defaultImageRequestOptions.networkAccessAllowed = YES; PHImageRequestID requestId = [self.imageManager requestImageForAsset:(PHAsset *)asset.asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeDefault options:self.defaultImageRequestOptions resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) { [subscriber sendNext:RACTuplePack(result, info[PHImageResultIsDegradedKey])]; if (![info[PHImageResultIsDegradedKey] boolValue]) { [subscriber sendCompleted]; } }]; return [RACDisposable disposableWithBlock:^{ [self.imageManager cancelImageRequest:requestId]; }]; }];}// TBVAssetsLibrary- (RACSignal *)requestFullResolutionImageForAsset:(TBVAsset *)asset { return [[[RACSignal return:asset.asset] deliverOn:[RACScheduler scheduler]] map:^id(ALAsset * asset) { ALAssetRepresentation *assetRepresentation = [asset defaultRepresentation]; CGImageRef fullResolutionImage = [assetRepresentation fullResolutionImage]; UIImage *resultImage = [UIImage imageWithCGImage:fullResolutionImage scale:BQAP_SCREEN_SCALE orientation:UIImageOrientationUp]; return RACTuplePack(resultImage, @; }];}

3.不要再viewDidLoad里面去modal一个控制器, 要将modal代码放进viewDidAppear中,否则会导致Attempt to present whose view is not in the window hierarchy.4.将modal代码改为

  1. 将gif图片转为NSData
  2. 根据NSData获取CGImageSourcewww.64222.com,对象
  3. 获取帧数
  4. 根据帧数获取每一帧对应的UIImage对象和时间间隔
  5. 循环播放
-requestSizeForAssets:

请求大小是针对的图片,所以对非图片的asset进行了过滤

// TBVCachingImageManager- (RACSignal *)requestSizeForAssets:(NSArray<TBVAsset *> *)assets { RACSequence *requestSequence = [[assets.rac_sequence filter:^BOOL(TBVAsset *asset) { return ((PHAsset *)asset.asset).mediaType == PHAssetMediaTypeImage; }] map:^id(TBVAsset *asset) { return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { self.defaultImageRequestOptions.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat; PHImageRequestID requestId =[self.imageManager requestImageDataForAsset:(PHAsset *)asset.asset options:self.defaultImageRequestOptions resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) { [subscriber sendNext:@(imageData.length)]; [subscriber sendCompleted]; }]; return [RACDisposable disposableWithBlock:^{ [self.imageManager cancelImageRequest:requestId]; }]; }]; }]; return [[RACSignal zip:requestSequence] map:^id(RACTuple *value) { return [value.rac_sequence foldLeftWithStart:@0 reduce:^id(id accumulator, id value) { return @([accumulator integerValue] + [value integerValue]); }]; }];}// TBVAssetsLibrary- (RACSignal *)requestSizeForAssets:(NSArray<TBVAsset *> *)assets { return [RACSignal return:[[[[assets.rac_sequence map:^id(TBVAsset *asset) { return asset.asset; }] filter:^BOOL(ALAsset *asset) { return [asset valueForProperty:ALAssetPropertyType] == ALAssetTypePhoto; }] map:^id(ALAsset *asset) { return @([asset defaultRepresentation].size); }] foldLeftWithStart:@ reduce:^id(id accumulator, id value) { return @([accumulator integerValue] + [value integerValue]); }]]; }
[self.view.window.rootViewController presentViewController:viewController animated:YES completion:nil];
友情链接: 网站地图
Copyright © 2015-2019 http://www.nflfreepicks.net. 新葡萄京娱乐场网址有限公司 版权所有