最近做了一款清理類的 APP,說(shuō)實(shí)話,iOS 清理真沒(méi)什么可清理的,只能清理下相似照片,重復(fù)聯(lián)系人,日歷事件等
照片、視頻管理類

我這里獲取了相冊(cè)的所有信息,并將相似照片,相似視頻,照片大于10M 和 視頻大于 20M 的大文件也整理了處理,可以直接調(diào)用,在屬性上直接獲取,記得刷新 UI 的時(shí)候要在主線程進(jìn)行操作,否則會(huì)崩潰
調(diào)用方法如下
- (ClearPhotoManager *)clearPhotoManager
{
if (!_clearPhotoManager)
{
_clearPhotoManager = [ClearPhotoManager shareManager];
}
return _clearPhotoManager;
}
-(void)loadPhotoData
{
// 加載照片數(shù)據(jù)源
CleanSelf(self);
[CleanOneLoading showWithMessage:CleanLanguage(@"Loading...",@"加載中...")];
[self.clearPhotoManager getAllBigAsset:^(BOOL success) {
// 這里有很多屬性,自己打印看看,這里列舉了兩個(gè)
CleanNsLog(@"%@",weakself.clearPhotoManager.similarInfo);
CleanNsLog(@"%@",weakself.clearPhotoManager.screenshotsInfo);
dispatch_async(dispatch_get_main_queue(), ^{
[weakself pushHomeVc];
[CleanOneLoading disMiss];
});
}];
}
ClearPhotoManager.h
//
// ClearPhotoManager.h
//
// Created by Three Project on 21/8/2023.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, PhotoNotificationStatus)
{
PhotoNotificationStatusDefualt = 0, // 相冊(cè)變更默認(rèn)處理
PhotoNotificationStatusClose = 1, // 相冊(cè)變更不處理
PhotoNotificationStatusNeed = 2, // 相冊(cè)變更主動(dòng)處理
};
@protocol ClearPhotoManagerDelegate <NSObject>
@optional
/// 相冊(cè)變動(dòng)代理方法
- (void)clearPhotoLibraryDidChange;
@end
@interface ClearPhotoManager : NSObject
/// 單例
+ (ClearPhotoManager *)shareManager;
/// 代理
@property (nonatomic, weak) id<ClearPhotoManagerDelegate> delegate;
/// 變更狀態(tài)
@property (nonatomic, assign) PhotoNotificationStatus notificationStatus;
// 這里是 PHASet 對(duì)象
@property (nonatomic, strong, readonly) NSMutableArray *allPhotoArr;
@property (nonatomic, strong, readonly) NSMutableArray *allVideoArr;
/// 相似照片信息:存儲(chǔ)了相似圖片數(shù)量及可以節(jié)省的內(nèi)存空間大小
@property (nonatomic, strong, readonly) NSDictionary *similarInfo;
@property (nonatomic, strong, readonly) NSMutableArray *similarArr;
/// 截圖照片信息:存儲(chǔ)了屏幕截圖數(shù)量及可以節(jié)省的內(nèi)存空間大小
@property (nonatomic, strong, readonly) NSDictionary *screenshotsInfo;
@property (nonatomic, strong, readonly) NSMutableArray *screenshotsArr;
/// short視頻信息
@property (nonatomic, strong, readonly) NSDictionary *shortVideoInfo;
@property (nonatomic, strong, readonly) NSMutableArray *shortVideoArr;
/// 相似視頻信息
@property (nonatomic, strong, readonly) NSDictionary *duplicateVideoInfo;
@property (nonatomic, strong, readonly) NSMutableArray *duplicateVideoArr;
/// 大文件圖片
@property (nonatomic, strong, readonly) NSMutableArray *bigPhotoArr;
/// 大文件視頻 - 長(zhǎng)視頻
@property (nonatomic, strong, readonly) NSMutableArray *bigVideoArr;
@property (nonatomic, strong, readonly) NSDictionary *bigVideoInfo;
// 獲取相冊(cè)的 PHAsset 對(duì)象
- (void)loadPhotoCompletionHandler:(void (^)(BOOL success))completion isVideo:(BOOL)isVideo;
/// 刪除照片
+ (void)deleteAssets:(NSArray<PHAsset *> *)assets completionHandler:(void (^)(BOOL success, NSError *error))completion;
// 獲取相冊(cè)中的大文件
- (void)getAllBigAsset:(void (^)(BOOL success))completion;
@end
NS_ASSUME_NONNULL_END
ClearPhotoManager.m
//
// ClearPhotoManager.m
//
// Created by Three Project on 21/8/2023.
//
#import "ClearPhotoManager.h"
#import "CleanAlbumModel.h"
#import "ImageCompare.h"
#define REQUEST_VIDEO_QUEUE "com.video.queue"
#define VIDEO_MAXSIZE 20 * 1024 * 1024
#define PHOTO_MAXSIZE 10 * 1024 * 1024
@interface ClearPhotoManager ()<PHPhotoLibraryChangeObserver>
// 獲取相簿中的所有PHAsset對(duì)象
@property (nonatomic, strong) PHFetchResult *assetArray;
// 這里是 PHASet 對(duì)象
@property (nonatomic, strong, readwrite) NSMutableArray *allPhotoArr;
@property (nonatomic, strong, readwrite) NSMutableArray *allVideoArr;
/// 相似照片信息:存儲(chǔ)了相似圖片數(shù)量及可以節(jié)省的內(nèi)存空間大小
@property (nonatomic, strong, readwrite) NSDictionary *similarInfo;
@property (nonatomic, strong, readwrite) NSMutableArray *similarArr;
/// 截圖照片信息:存儲(chǔ)了屏幕截圖數(shù)量及可以節(jié)省的內(nèi)存空間大小
@property (nonatomic, strong, readwrite) NSDictionary *screenshotsInfo;
@property (nonatomic, strong, readwrite) NSMutableArray *screenshotsArr;
/// short視頻信息
@property (nonatomic, strong, readwrite) NSDictionary *shortVideoInfo;
@property (nonatomic, strong, readwrite) NSMutableArray *shortVideoArr;
/// 相似視頻信息
@property (nonatomic, strong, readwrite) NSDictionary *duplicateVideoInfo;
@property (nonatomic, strong, readwrite) NSMutableArray *duplicateVideoArr;
/// 大文件圖片
@property (nonatomic, strong, readwrite) NSMutableArray *bigPhotoArr;
/// 大文件視頻
@property (nonatomic, strong, readwrite) NSMutableArray *bigVideoArr;
@property (nonatomic, strong, readwrite) NSDictionary *bigVideoInfo;
// 完成的回調(diào)
@property (nonatomic, copy) void (^completionHandler)(BOOL success);
@property (nonatomic, copy) void (^bigCompletionHandler)(BOOL success);
// PHImageManager的requestImageForAsset所需要的options
@property (nonatomic, strong) PHImageRequestOptions *imageRequestOptions;
// PHImageManager的requestImageDataForAsset所需要的options
@property (nonatomic, strong) PHImageRequestOptions *imageSizeRequestOptions;
@property (assign,nonatomic) BOOL isPhotoFinish;
@property (assign,nonatomic) BOOL isVideoFinish;
@end
@implementation ClearPhotoManager
#pragma mark - 單例
+ (ClearPhotoManager *)shareManager
{
static ClearPhotoManager *clearPhotoManager = nil;
static dispatch_once_t token;
dispatch_once(&token, ^{
clearPhotoManager = [[ClearPhotoManager alloc] init];
});
return clearPhotoManager;
}
#pragma mark - 相冊(cè)變換通知
- (instancetype)init
{
self = [super init];
if (self)
{
// 相冊(cè)變換通知
[[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self];
}
return self;
}
- (void)dealloc
{
// 移除相冊(cè)變換通知
[[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self];
}
// 相冊(cè)變換時(shí)候會(huì)調(diào)用
- (void)photoLibraryDidChange:(PHChange *)changeInstance
{
// 篩選出沒(méi)必要的變動(dòng)
PHFetchResultChangeDetails *collectionChanges = [changeInstance changeDetailsForFetchResult:self.assetArray];
if (collectionChanges == nil || self.notificationStatus != PhotoNotificationStatusDefualt)
{
return;
}
// 回到主線程調(diào)用相冊(cè)變動(dòng)代理方法
dispatch_async(dispatch_get_main_queue(), ^{
if ([self.delegate respondsToSelector:@selector(clearPhotoLibraryDidChange)])
{
[self.delegate clearPhotoLibraryDidChange];
}
});
}
// 處理圖片,獲取到需要清理的相似圖片和截屏圖片
- (void)dealImageWithArray:(NSArray *)assetArray
{
CleanSelf(self);
[self getPhotoDetail:assetArray completion:^(NSArray *assets) {
// 相似圖片
weakself.similarArr = [weakself similarPhotoArray:assets];
for(NSInteger i=0;i<assets.count;i++)
{
PHAsset *lastAsset = assets[i][@"asset"];
if (lastAsset.mediaSubtypes == PHAssetMediaSubtypePhotoScreenshot)
{
[weakself.screenshotsArr addObject:assets[I]];
}
NSString *sizeStr = [NSString stringWithFormat:@"%@",assets[i][@"originImageDataLength"]];
CGFloat sizeFloat = [sizeStr floatValue];
if (sizeFloat > PHOTO_MAXSIZE)
{
[weakself.bigPhotoArr addObject:assets[I]];
}
}
// 處理圖片
weakself.screenshotsInfo = [weakself arangeArray:weakself.screenshotsArr];
weakself.isPhotoFinish = YES;
weakself.completionHandler(YES);
}];
}
#pragma mark -- 處理視頻
- (void)dealVideoWithArray:(NSArray *)assetArray
{
CleanSelf(self);
[self getVideoDetail:assetArray completion:^(NSArray *assets) {
// 相似視頻
weakself.duplicateVideoArr = [weakself similarVideoArray:assets];
for(NSInteger i=0;i<assets.count;i++)
{
NSString *sizeStr = [NSString stringWithFormat:@"%@",assets[i][@"originImageDataLength"]];
NSInteger sizeFloat = [sizeStr integerValue];
if (sizeFloat > VIDEO_MAXSIZE)
{
[weakself.bigVideoArr addObject:assets[I]];
}
else
{
[weakself.shortVideoArr addObject:assets[I]];
}
}
// 處理視頻
weakself.bigVideoInfo = [weakself arangeArray:weakself.bigVideoArr];
weakself.shortVideoInfo = [weakself arangeArray:weakself.shortVideoArr];
weakself.isVideoFinish = YES;
weakself.completionHandler(YES);
}];
}
// 處理相似視頻
-(NSMutableArray *)similarVideoArray:(NSArray *)assets
{
NSMutableArray *inputArr = [NSMutableArray arrayWithArray:assets];
NSMutableArray *duplicateVideoArr = [NSMutableArray array];
NSInteger totalSize = 0;
for (int i = 0; i < inputArr.count; i++) {
NSMutableArray *tempArr = [NSMutableArray array];
NSDictionary *dict1 = inputArr[I];
NSInteger fileSize1 = [dict1[@"originImageDataLength"] integerValue];
NSInteger duration1 = [dict1[@"duration"] integerValue];
[tempArr addObject:dict1];
for (int j = i + 1; j < inputArr.count; j++) {
NSDictionary *dict2 = inputArr[j];
NSInteger fileSize2 = [dict2[@"originImageDataLength"] integerValue];
NSInteger duration2 = [dict2[@"duration"] integerValue];
if (fileSize1 == fileSize2 && duration1 == duration2) {
[tempArr addObject:dict2];
}
}
if (tempArr.count > 1) {
[duplicateVideoArr addObject:tempArr];
[inputArr removeObjectsInArray:tempArr];
i -= 1;
}
}
NSInteger totalCount = 0;
for (NSArray *disArr in duplicateVideoArr) {
totalCount += disArr.count;
for (NSDictionary *dict in disArr) {
NSInteger fileSize = [dict[@"originImageDataLength"] integerValue];
totalSize += fileSize;
}
}
NSDictionary *duplicateVideosDict = @{@"totalCount":@(totalCount), @"totalSize":@(totalSize), @"dataArr":duplicateVideoArr};
self.duplicateVideoInfo = duplicateVideosDict;
return duplicateVideoArr;
}
// 處理相似圖片
-(NSMutableArray *)similarPhotoArray:(NSArray *)assets
{
NSMutableArray *inputArr = [NSMutableArray arrayWithArray:assets];
NSMutableArray *duplicateVideoArr = [NSMutableArray array];
NSInteger totalSize = 0;
for (int i = 0; i < inputArr.count; i++) {
NSMutableArray *tempArr = [NSMutableArray array];
NSDictionary *dict1 = inputArr[I];
PHAsset *asset1 = dict1[@"asset"];
UIImage *image1 = dict1[@"exactImage"];
[tempArr addObject:dict1];
for (int j = i + 1; j < inputArr.count; j++) {
NSDictionary *dict2 = inputArr[j];
PHAsset *asset2 = dict2[@"asset"];
UIImage *image2 = dict2[@"exactImage"];
BOOL isSameDay = [self isSameDay:asset1.creationDate date2:asset2.creationDate];
if(isSameDay)
{
CleanNsLog(@"%@ === %@",asset1.creationDate,asset2.creationDate);
// double isLike = [SimalPhotoAction getSimilarityValueWithImgA:image1 ImgB:image2];
BOOL isLike = [ImageCompare isImage:image1 likeImage:image2];
if(isLike)
{
[tempArr addObject:dict2];
}
}
}
if (tempArr.count > 1) {
[duplicateVideoArr addObject:tempArr];
[inputArr removeObjectsInArray:tempArr];
i -= 1;
}
}
NSInteger totalCount = 0;
for (NSArray *disArr in duplicateVideoArr) {
totalCount += disArr.count;
for (NSDictionary *dict in disArr) {
NSInteger fileSize = [dict[@"originImageDataLength"] integerValue];
totalSize += fileSize;
}
}
NSDictionary *duplicateVideosDict = @{@"totalCount":@(totalCount), @"totalSize":@(totalSize), @"dataArr":duplicateVideoArr};
self.similarInfo = duplicateVideosDict;
return duplicateVideoArr;
}
-(NSDictionary *)arangeArray:(NSMutableArray *)muArray
{
CGFloat size = 0;
for(NSDictionary *dict in muArray)
{
size += [dict[@"originImageDataLength"] floatValue];
}
NSDictionary *dict = @{
@"totalCount":@(muArray.count),
@"totalSize":@(size),
@"dataArr":muArray
};
return dict;
}
// 加載照片之前先清除舊數(shù)據(jù)
- (void)resetTagData
{
// 相冊(cè)清空
self.allPhotoArr = nil;
self.similarInfo = nil;
self.similarArr = nil;
self.screenshotsInfo = nil;
self.screenshotsArr = nil;
self.bigPhotoArr = nil;
// 視頻數(shù)據(jù)清空
self.allVideoArr = nil;
self.shortVideoInfo = nil;
self.shortVideoArr = nil;
self.duplicateVideoInfo = nil;
self.duplicateVideoArr = nil;
self.bigVideoArr = nil;
self.bigVideoInfo = nil;
}
#pragma mark - 加載照片:日期
// 是否為同一天
- (BOOL)isSameDay:(NSDate *)date1 date2:(NSDate *)date2
{
// 有一個(gè)日期為空則直接返回
if (!date1 || !date2)
{
return NO;
}
// 從日歷上分別獲取date1、date2的年月日
NSCalendar *calendar = [NSCalendar currentCalendar];
unsigned unitFlags = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute;
NSDateComponents *dateComponents1 = [calendar components:unitFlags fromDate:date1];
NSDateComponents *dateComponents2 = [calendar components:unitFlags fromDate:date2];
// 比較年月日,是否是同一天
if((dateComponents1.day == dateComponents2.day) && (dateComponents1.month == dateComponents2.month) && (dateComponents1.year == dateComponents2.year))
{
// 如果是同一天,那就計(jì)算兩者的時(shí)間差是不是在一個(gè)小時(shí)之內(nèi)
NSInteger dateOneSecond = dateComponents1.minute;
NSInteger dateTwoSecond = dateComponents2.minute;
NSInteger Difference = labs(dateTwoSecond - dateOneSecond);
if(Difference < 30)
{
return YES;
}
else
{
return NO;
}
}
return NO;
}
// NSDate轉(zhuǎn)NSString
- (NSString *)stringWithDate:(NSDate *)date
{
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy-MM-dd"];
return [dateFormatter stringFromDate:date];
}
#pragma mark - 刪除照片
// 刪除照片
+ (void)deleteAssets:(NSArray<PHAsset *> *)assets completionHandler:(void (^)(BOOL, NSError * _Nonnull))completion
{
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
// 刪除當(dāng)前圖片資源
[PHAssetChangeRequest deleteAssets:assets];
} completionHandler:^(BOOL success, NSError * _Nullable error) {
// 調(diào)用刪除后的回調(diào)代碼塊
if (completion)
{
dispatch_async(dispatch_get_main_queue(), ^{
completion(success, error);
});
}
}];
}
#pragma mark - 懶加載
- (NSMutableArray *)allPhotoArr
{
if (!_allPhotoArr)
{
_allPhotoArr = [NSMutableArray array];
}
return _allPhotoArr;
}
- (NSMutableArray *)allVideoArr
{
if (!_allVideoArr)
{
_allVideoArr = [NSMutableArray array];
}
return _allVideoArr;
}
- (NSMutableArray *)bigPhotoArr
{
if (!_bigPhotoArr)
{
_bigPhotoArr = [NSMutableArray array];
}
return _bigPhotoArr;
}
- (NSMutableArray *)bigVideoArr
{
if (!_bigVideoArr)
{
_bigVideoArr = [NSMutableArray array];
}
return _bigVideoArr;
}
- (NSMutableArray *)duplicateVideoArr
{
if (!_duplicateVideoArr)
{
_duplicateVideoArr = [NSMutableArray array];
}
return _duplicateVideoArr;
}
- (NSMutableArray *)shortVideoArr
{
if (!_shortVideoArr)
{
_shortVideoArr = [NSMutableArray array];
}
return _shortVideoArr;
}
- (NSMutableArray *)screenshotsArr
{
if (!_screenshotsArr)
{
_screenshotsArr = [NSMutableArray array];
}
return _screenshotsArr;
}
- (NSMutableArray *)similarArr
{
if (!_similarArr)
{
_similarArr = [NSMutableArray array];
}
return _similarArr;
}
- (PHImageRequestOptions *)imageRequestOptions
{
if (!_imageRequestOptions) {
_imageRequestOptions = [[PHImageRequestOptions alloc] init];
// resizeMode 屬性控制圖像的剪裁
_imageRequestOptions.resizeMode = PHImageRequestOptionsResizeModeNone;// no resize
// deliveryMode 則用于控制請(qǐng)求的圖片質(zhì)量
_imageRequestOptions.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
}
return _imageRequestOptions;
}
- (PHImageRequestOptions *)imageSizeRequestOptions
{
if (!_imageSizeRequestOptions) {
_imageSizeRequestOptions = [[PHImageRequestOptions alloc] init];
// resizeMode 屬性控制圖像的剪裁
_imageSizeRequestOptions.resizeMode = PHImageRequestOptionsResizeModeExact;// exactly targetSize
// deliveryMode 則用于控制請(qǐng)求的圖片質(zhì)量
_imageSizeRequestOptions.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
}
return _imageSizeRequestOptions;
}
#pragma mark -- 獲取視頻
- (void)phAssetToVideo:(PHAsset *)asset completionHandle:(void(^)(AVURLAsset *urlAsset, int duration, NSNumber *fileSize, UIImage *thumbnailImage))completion {
@autoreleasepool {
PHVideoRequestOptions *videoOptions = [[PHVideoRequestOptions alloc] init];
videoOptions.version = PHVideoRequestOptionsVersionOriginal;
videoOptions.deliveryMode = PHVideoRequestOptionsDeliveryModeHighQualityFormat;
videoOptions.networkAccessAllowed = YES;
[[PHCachingImageManager defaultManager] requestAVAssetForVideo:asset options:videoOptions resultHandler:^(AVAsset * _Nullable asset, AVAudioMix * _Nullable audioMix, NSDictionary * _Nullable info) {
if ([asset isKindOfClass:[AVURLAsset class]]) {
AVURLAsset *urlAsset = (AVURLAsset *)asset;
CMTime time = [urlAsset duration];
int seconds = ceil(time.value / time.timescale);
NSNumber *fileSize;
[urlAsset.URL getResourceValue:&fileSize forKey:NSURLFileSizeKey error:nil];
UIImage *thumbnailImage = [self thumbnailImageForVideoAssset:urlAsset];
completion(urlAsset, seconds, fileSize, thumbnailImage);
}
}];
}
}
- (UIImage *)thumbnailImageForVideoAssset:(AVURLAsset *)asset {
NSParameterAssert(asset);
AVAssetImageGenerator *imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:asset];
imageGenerator.appliesPreferredTrackTransform = YES;
imageGenerator.apertureMode = AVAssetImageGeneratorApertureModeEncodedPixels;
/*
如果不需要獲取縮略圖,就設(shè)置為NO,如果需要獲取縮略圖,則maximumSize為獲取的最大尺寸。
以BBC為例,getThumbnail = NO時(shí),打印寬高數(shù)據(jù)為:1920*1072。
getThumbnail = YES時(shí),maximumSize為100*100。打印寬高數(shù)據(jù)為:100*55.
注:不乘[UIScreen mainScreen].scale,會(huì)發(fā)現(xiàn)縮略圖在100*100很虛。
*/
BOOL getThumbnail = YES;
if (getThumbnail) {
CGFloat width = [UIScreen mainScreen].scale * 100;
imageGenerator.maximumSize = CGSizeMake(width, width);
}
NSError *error = nil;
CMTime time = CMTimeMake(1, 1);
CMTime actucalTime;
CGImageRef cgImage = [imageGenerator copyCGImageAtTime:time actualTime:&actucalTime error:&error];
if (error) {
NSLog(@"ERROR:get video thumb image fail,%@",error.domain);
}
CMTimeShow(actucalTime);
UIImage *image = [UIImage imageWithCGImage:cgImage];
CGImageRelease(cgImage);
return image;
}
// 獲取相冊(cè)詳細(xì)信息
-(void)getPhotoDetail:(NSArray *)assetArr completion:(void (^)(NSArray* assets))completion
{
__block NSMutableArray *photos = [NSMutableArray array];
dispatch_queue_t dispatchQueue = dispatch_queue_create(REQUEST_VIDEO_QUEUE, DISPATCH_QUEUE_CONCURRENT);
dispatch_async(dispatchQueue, ^{
UIImage *imageResult = [UIImage imageNamed:@"ic_img"];
for (PHAsset *asset in assetArr) {
PHAssetResource *resource = [[PHAssetResource assetResourcesForAsset:asset] firstObject];
NSInteger assetLength = [[resource valueForKey:@"fileSize"] integerValue];
PHImageManager *imageManager = [PHImageManager defaultManager];
// 獲取壓縮大小后的圖片,即縮略圖
[imageManager requestImageForAsset:asset targetSize:CGSizeMake(125, 125) contentMode:PHImageContentModeDefault options:self.imageRequestOptions resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
NSString *dateStr = [self stringWithDate:asset.creationDate];
if (!result)
{
result = imageResult;
}
NSDictionary *itemDictionary = @{
@"asset" : asset,
@"exactImage" : result,
@"originImageDataLength" : @(assetLength),
@"duration":@(0),
@"dateStr":dateStr,
@"type":@"1"
};
[photos addObject:itemDictionary];
if (assetArr.count == photos.count)
{
completion(photos);
}
}];
}
});
}
// 獲取視頻詳細(xì)信息
-(void)getVideoDetail:(NSArray *)assetArr completion:(void (^)(NSArray* assets))completion
{
CleanSelf(self);
__block NSMutableArray *videos = [NSMutableArray array];
dispatch_queue_t dispatchQueue = dispatch_queue_create(REQUEST_VIDEO_QUEUE, DISPATCH_QUEUE_CONCURRENT);
dispatch_async(dispatchQueue, ^{
for (PHAsset *asset in assetArr) {
[weakself phAssetToVideo:asset completionHandle:^(AVURLAsset *urlAsset, int duration, NSNumber *fileSize, UIImage *thumbnailImage) {
if (urlAsset) {
thumbnailImage = thumbnailImage == nil ? [UIImage imageNamed:@"img_video_nor"]: thumbnailImage;
NSString *dateStr = [self stringWithDate:asset.creationDate];
NSDictionary *dict = @{@"asset" : asset,
@"exactImage" : thumbnailImage,
@"originImageDataLength" : fileSize,
@"duration" : @(duration),
@"dateStr":dateStr,
@"type":@"2"
};
[videos addObject:dict];
if (videos.count == assetArr.count)
{
completion(videos);
}
}
}];
}
});
}
// 判斷相冊(cè)授權(quán)狀態(tài)
- (void)getAllBigAsset:(void (^)(BOOL success))completion
{
// 清除舊數(shù)據(jù)
[self resetTagData];
self.bigCompletionHandler = completion;
// 獲取當(dāng)前App的相冊(cè)授權(quán)狀態(tài)
PHAuthorizationStatus authorizationStatus = [PHPhotoLibrary authorizationStatus];
// 判斷授權(quán)狀態(tài)
if (authorizationStatus == PHAuthorizationStatusAuthorized)
{
[self greatAllPhotoAndVideos];
}
// 如果沒(méi)決定, 彈出指示框, 讓用戶選擇
else if (authorizationStatus == PHAuthorizationStatusNotDetermined)
{
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
// 如果用戶選擇授權(quán), 則獲取圖片
if (status == PHAuthorizationStatusAuthorized)
{
[self greatAllPhotoAndVideos];
}
else
{
// 開(kāi)啟權(quán)限提示
completion(NO);
}
}];
}
else
{
completion(NO);
}
}
-(void)greatAllPhotoAndVideos
{
self.isPhotoFinish = NO;
self.isVideoFinish = NO;
// 獲取相簿中的PHAsset對(duì)象
self.allVideoArr = [self greatAllArr:YES];
self.allPhotoArr = [self greatAllArr:NO];
// 處理相冊(cè)問(wèn)題
[self dealVideoWithArray:self.allVideoArr];
[self dealImageWithArray:self.allPhotoArr];
CleanSelf(self);
self.completionHandler = ^(BOOL success)
{
if (weakself.isPhotoFinish && weakself.isVideoFinish)
{
weakself.bigCompletionHandler(YES);
}
};
}
#pragma mark - 加載照片:處理圖片
-(NSMutableArray *)greatAllArr:(BOOL)isVideo
{
NSMutableArray *resultArray = [[NSMutableArray alloc] init];
// 獲取相冊(cè)
PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeSmartAlbumUserLibrary options:nil];
// 獲取所有資源的集合,并按資源的創(chuàng)建時(shí)間排序,這樣就可以通過(guò)和上一張圖片判斷日期來(lái)分組了
PHFetchOptions *option = [[PHFetchOptions alloc] init];
option.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]];
if(isVideo)
{
option.predicate = [NSPredicate predicateWithFormat:@"mediaType == %ld", PHAssetMediaTypeVideo];
}
else
{
option.predicate = [NSPredicate predicateWithFormat:@"mediaType == %ld", PHAssetMediaTypeImage];
}
[smartAlbums enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (![obj isKindOfClass:PHAssetCollection.class]) {
return;
}
PHAssetCollection *collection = (PHAssetCollection *) obj;
// 過(guò)濾空相冊(cè)
if (collection.estimatedAssetCount <= 0){
return;
}
// 獲取相冊(cè)內(nèi)asset result
PHFetchResult<PHAsset *> *result = [PHAsset fetchAssetsInAssetCollection:collection options:option];
[result enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
//資源圖片檢測(cè)
if (![obj isKindOfClass:[PHAsset class]]) {
return;
}
PHAsset *asset = (PHAsset *) obj;
[resultArray addObject:asset];
}];
}];
return resultArray;
}
@end
相似度的算法使用的是 opencv2 來(lái)計(jì)算的相似度,但是有一點(diǎn),照片過(guò)多時(shí),內(nèi)存會(huì)瘋狂上漲,導(dǎo)致崩潰,原因就是對(duì)比這里出現(xiàn)的問(wèn)題,解決辦法如下
1.通過(guò)時(shí)間,獲取照片(視頻)的拍攝時(shí)間,對(duì)比時(shí)如果在同一天再去對(duì)比,當(dāng)然你也可以再細(xì)致的去判斷,比如判斷相差時(shí)間在幾分鐘之內(nèi)也 ok
// 是否為同一天
- (BOOL)isSameDay:(NSDate *)date1 date2:(NSDate *)date2
{
// 有一個(gè)日期為空則直接返回
if (!date1 || !date2)
{
return NO;
}
// 從日歷上分別獲取date1、date2的年月日
NSCalendar *calendar = [NSCalendar currentCalendar];
unsigned unitFlags = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute;
NSDateComponents *dateComponents1 = [calendar components:unitFlags fromDate:date1];
NSDateComponents *dateComponents2 = [calendar components:unitFlags fromDate:date2];
// 比較年月日,是否是同一天
if((dateComponents1.day == dateComponents2.day) && (dateComponents1.month == dateComponents2.month) && (dateComponents1.year == dateComponents2.year))
{
// 如果是同一天,那就計(jì)算兩者的時(shí)間差是不是在一個(gè)小時(shí)之內(nèi)
NSInteger dateOneSecond = dateComponents1.minute;
NSInteger dateTwoSecond = dateComponents2.minute;
NSInteger Difference = labs(dateTwoSecond - dateOneSecond);
if(Difference < 30)
{
return YES;
}
else
{
return NO;
}
}
return NO;
}
2.修改對(duì)比的方法,減少圖片縮小后的大小,這里是最有效的,這樣會(huì)大大減小內(nèi)存,我減小到了 32 *32,不過(guò)量大肯定也會(huì)崩潰,我測(cè)試在同時(shí)對(duì)比 1500 張照片,210 個(gè)視頻下是不會(huì)崩潰的
// 是否相似
+ (BOOL)isImage:(UIImage *)image1 likeImage:(UIImage *)image2 {
IplImage *iplimage1 = [self convertToIplImage:[self OriginImage:image1 scaleToSize:CGSizeMake(32, 32)]];
IplImage *iplimage2 = [self convertToIplImage:[self OriginImage:image2 scaleToSize:CGSizeMake(32, 32)]];
double sililary = [self ComparePPKHist:iplimage1 withParam2:iplimage2];
if (sililary < 0.1) {
return YES;
}
return NO;
}
下面是對(duì)比的類 和 model ,我這里視頻和圖片共用了一個(gè) Model
CleanAlbumModel.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface CleanAlbumModel : NSObject
/// 圖片資源
@property (nonatomic, strong) PHAsset *asset;
/// 圖片
@property (nonatomic, strong) UIImage *exactImage;
/// 圖片數(shù)據(jù)大小
@property (nonatomic, assign) NSUInteger originImageDataLength;
/// 時(shí)長(zhǎng)
@property (nonatomic, assign) NSUInteger duration;
/// 日期
@property (nonatomic, copy) NSString *dateStr;
/// 類型 1-iamge 2-video
@property (nonatomic, copy) NSString *type;
/// 是否選中
@property (nonatomic, assign) BOOL isSelected;
/// 初始化Model,傳入info
- (instancetype)initWithDict:(NSDictionary *)dict;
@end
NS_ASSUME_NONNULL_END
CleanAlbumModel.m
#import "CleanAlbumModel.h"
@implementation CleanAlbumModel
- (instancetype)initWithDict:(NSDictionary *)dict
{
self = [super init];
if (self)
{
self.asset = dict[@"asset"];
self.exactImage = dict[@"exactImage"];
self.duration = [dict[@"duration"] integerValue];
self.originImageDataLength = [dict[@"originImageDataLength"] unsignedIntegerValue];
self.dateStr = dict[@"dateStr"];
self.type = dict[@"type"];
self.isSelected = NO;
}
return self;
}
@end
ImageCompare.h 圖片相似度對(duì)比
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface ImageCompare : NSObject
/// 是否相似
+ (BOOL)isImage:(UIImage *)image1 likeImage:(UIImage *)image2;
@end
ImageCompare.m
#import "ImageCompare.h"
#import <opencv2/opencv.hpp>
@implementation ImageCompare
// 是否相似
+ (BOOL)isImage:(UIImage *)image1 likeImage:(UIImage *)image2 {
IplImage *iplimage1 = [self convertToIplImage:[self OriginImage:image1 scaleToSize:CGSizeMake(32, 32)]];
IplImage *iplimage2 = [self convertToIplImage:[self OriginImage:image2 scaleToSize:CGSizeMake(32, 32)]];
double sililary = [self ComparePPKHist:iplimage1 withParam2:iplimage2];
if (sililary < 0.1) {
return YES;
}
return NO;
}
// 縮小尺寸
+ (UIImage *)OriginImage:(UIImage *)image scaleToSize:(CGSize)size {
// size 為CGSize類型,即你所需要的圖片尺寸
UIGraphicsBeginImageContext(size);
[image drawInRect:CGRectMake(0, 0, size.width, size.height)];
UIImage* scaledImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return scaledImage;
}
// 獲取相似度
+ (float)isImageFloat:(UIImage *)image1 likeImage:(UIImage *)image2 {
IplImage *iplimage1 = [self convertToIplImage:image1];
IplImage *iplimage2 = [self convertToIplImage:image2];
double sililary = [self ComparePPKHist:iplimage1 withParam2:iplimage2];
return sililary;
}
// 比較
+ (double)ComparePPKHist:(IplImage *)srcIpl withParam2:(IplImage *)srcIpl1 {
if (srcIpl->width==srcIpl1->width && srcIpl->height==srcIpl1->height) {
return [self CompareHist:srcIpl withParam2:srcIpl1];
}
else if (srcIpl->width<srcIpl1->width && srcIpl->height==srcIpl1->height) {
return [self CompareHistWithSmallWidthIpl:srcIpl withBigWidthIplImg:srcIpl1];
}
else if (srcIpl->width>srcIpl1->width && srcIpl->height==srcIpl1->height) {
return [self CompareHistWithSmallWidthIpl:srcIpl1 withBigWidthIplImg:srcIpl];
}
else if (srcIpl->width==srcIpl1->width && srcIpl->height<srcIpl1->height) {
return [self CompareHistWithSmallHeightIpl:srcIpl withBigHeightIplImg:srcIpl1];
}
else if (srcIpl->width==srcIpl1->width && srcIpl->height>srcIpl1->height) {
return [self CompareHistWithSmallHeightIpl:srcIpl1 withBigHeightIplImg:srcIpl];
}
else if (srcIpl->width<srcIpl1->width && srcIpl->height<srcIpl1->height) {
return [self CompareHistWithSmallIpl:srcIpl withBigIplImg:srcIpl1];
}
else if (srcIpl->width>srcIpl1->width && srcIpl->height>srcIpl1->height)
{
return [self CompareHistWithSmallIpl:srcIpl1 withBigIplImg:srcIpl];
}
return 1.f;
}
+ (double)CompareHistWithSmallWidthIpl:(IplImage*)srcIpl withBigWidthIplImg:(IplImage*)srcIpl1 {
// 當(dāng)前匹配結(jié)果,越接近于0.0匹配度越高
double dbRst=1.0;
// 匹配結(jié)果,-1表示正在匹配,0表示匹配失敗,1表示匹配成功
int tfFound = -1;
// 裁剪后的圖片
IplImage *cropImage;
for (int j=0; j<srcIpl1->width-srcIpl->width; j++) {
// 裁剪圖片
cvSetImageROI(srcIpl1, cvRect(j, 0, srcIpl->width, srcIpl->height));
cropImage = cvCreateImage(cvGetSize(srcIpl), IPL_DEPTH_8U, 3);
cvCopy(srcIpl1, cropImage);
cvResetImageROI(srcIpl1);
// 匹配圖片
double dbRst1 =[self CompareHist:srcIpl withParam2:cropImage];
// printf("匹配結(jié)果為:%f\n",dbRst1);
if (dbRst1<=0.01) {
// 匹配成功
tfFound = 1;
break;
}
else if(dbRst==1.0 || dbRst1<dbRst) {
// 本次匹配有進(jìn)步,更新結(jié)果
cvReleaseImage(&cropImage);
dbRst = dbRst1;
}
else if(dbRst1>dbRst) {
cvReleaseImage(&cropImage);
}
}
return dbRst;
}
+ (double)CompareHistWithSmallHeightIpl:(IplImage*)srcIpl withBigHeightIplImg:(IplImage*)srcIpl1 {
// 當(dāng)前匹配結(jié)果,越接近于0.0匹配度越高
double dbRst=1.0;
// 匹配結(jié)果,-1表示正在匹配,0表示匹配失敗,1表示匹配成功
int tfFound = -1;
// 裁剪后的圖片
IplImage *cropImage;
for (int j=0; j<srcIpl1->height-srcIpl->height; j++) {
// 裁剪圖片
cvSetImageROI(srcIpl1, cvRect(0, j, srcIpl->height, srcIpl->height));
cropImage = cvCreateImage(cvGetSize(srcIpl), IPL_DEPTH_8U, 3);
cvCopy(srcIpl1, cropImage);
cvResetImageROI(srcIpl1);
// 匹配圖片
double dbRst1 =[self CompareHist:srcIpl withParam2:cropImage];
// printf("匹配結(jié)果為:%f\n",dbRst1);
if (dbRst1<=0.01) {
// 匹配成功
tfFound = 1;
break;
}
else if(dbRst==1.0 || dbRst1<dbRst) {
// 本次匹配有進(jìn)步,更新結(jié)果
cvReleaseImage(&cropImage);
dbRst = dbRst1;
}
else if(dbRst1>dbRst) {
cvReleaseImage(&cropImage);
}
}
return dbRst;
}
+ (double)CompareHistWithSmallIpl:(IplImage*)srcIpl withBigIplImg:(IplImage*)srcIpl1 {
// 當(dāng)前匹配結(jié)果,越接近于0.0匹配度越高
double dbRst=1.0;
// 水平、豎直偏移量
int xSub=0,ySub=0;
// 匹配結(jié)果,-1表示正在匹配,0表示匹配失敗,1表示匹配成功
int tfFound = -1;
// 裁剪后的圖片
IplImage *cropImage;
// 遍歷方式:先豎后橫
for (int j=0; j<srcIpl1->width-srcIpl->width; j++) {
for (int i=ySub; i<srcIpl1->height-srcIpl->height; i++) {
// 裁剪圖片
cvSetImageROI(srcIpl1, cvRect(j, i, srcIpl->width, srcIpl->height));
cropImage = cvCreateImage(cvGetSize(srcIpl), IPL_DEPTH_8U, 3);
cvCopy(srcIpl1, cropImage);
cvResetImageROI(srcIpl1);
// 匹配圖片
double dbRst1 =[self CompareHist:srcIpl withParam2:cropImage];
// printf("(x=%d,y=%d),豎直匹配結(jié)果為:%f\n",j,i,dbRst1);
if (dbRst1<=0.0375) {
// 匹配成功
tfFound = 1;
break;
} else if(dbRst==1.0 || dbRst1<dbRst) {
// 本次匹配有進(jìn)步,更新結(jié)果
cvReleaseImage(&cropImage);
dbRst = dbRst1;
} else if(dbRst1>dbRst) {
cvReleaseImage(&cropImage);
// 豎直移動(dòng)到點(diǎn)了,該水平移動(dòng)了
ySub = i-1;
for (int k=j+1;k<srcIpl1->width-srcIpl->width; k++) {
// 裁切圖片
cvSetImageROI(srcIpl1, cvRect(k, i, srcIpl->width, srcIpl->height));
cropImage = cvCreateImage(cvGetSize(srcIpl), IPL_DEPTH_8U, 3);
cvCopy(srcIpl1, cropImage);
cvResetImageROI(srcIpl1);
// 匹配圖片
double dbRst1 =[self CompareHist:srcIpl withParam2:cropImage];
// printf("(x=%d,y=%d),水平移動(dòng)匹配結(jié)果為:%f\n",k,i,dbRst1);
if (dbRst1<=0.0375) {
// 匹配成功
tfFound = 1;
xSub = k;
break;
} else if(dbRst1<dbRst) {
// 本次匹配有進(jìn)步,更新結(jié)果
cvReleaseImage(&cropImage);
xSub = k;
j = xSub;
dbRst = dbRst1;
} else {
cvReleaseImage(&cropImage);
xSub = k;
j = xSub;
break;
}
}
}
if (tfFound==1 || tfFound==0) {
break;
}
}
if (tfFound==1 || tfFound==0) {
break;
}
}
return dbRst;
}
// 多通道彩色圖片的直方圖比對(duì)
+ (double)CompareHist:(IplImage*)image1 withParam2:(IplImage*)image2 {
int hist_size = 256;
IplImage *gray_plane = cvCreateImage(cvGetSize(image1), 8, 1);
cvCvtColor(image1, gray_plane, CV_BGR2GRAY);
CvHistogram *gray_hist = cvCreateHist(1, &hist_size, CV_HIST_ARRAY);
cvCalcHist(&gray_plane, gray_hist);
IplImage *gray_plane2 = cvCreateImage(cvGetSize(image2), 8, 1);
cvCvtColor(image2, gray_plane2, CV_BGR2GRAY);
CvHistogram *gray_hist2 = cvCreateHist(1, &hist_size, CV_HIST_ARRAY);
cvCalcHist(&gray_plane2, gray_hist2);
return cvCompareHist(gray_hist, gray_hist2, CV_COMP_BHATTACHARYYA);
}
// 單通道彩色圖片的直方圖
+ (double)CompareHistSignle:(IplImage*)image1 withParam2:(IplImage*)image2 {
int hist_size = 256;
CvHistogram *gray_hist = cvCreateHist(1, &hist_size, CV_HIST_ARRAY);
cvCalcHist(&image1, gray_hist);
CvHistogram *gray_hist2 = cvCreateHist(1, &hist_size, CV_HIST_ARRAY);
cvCalcHist(&image2, gray_hist2);
return cvCompareHist(gray_hist, gray_hist2, CV_COMP_BHATTACHARYYA);
}
// 進(jìn)行膚色檢測(cè)
+ (void)SkinDetect:(IplImage*)src withParam:(IplImage*)dst {
// 創(chuàng)建圖像頭
// 用于存圖像的一個(gè)中間變量,是用來(lái)分通道用的,分成hsv通道
IplImage* hsv = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 3);
// 通道的中間變量,用于膚色檢測(cè)的中間變量
IplImage* tmpH1 = cvCreateImage( cvGetSize(src), IPL_DEPTH_8U, 1);
IplImage* tmpS1 = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);
IplImage* tmpH2 = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);
IplImage* tmpS2 = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);
IplImage* tmpH3 = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);
IplImage* tmpS3 = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);
IplImage* H = cvCreateImage( cvGetSize(src), IPL_DEPTH_8U, 1);
IplImage* S = cvCreateImage( cvGetSize(src), IPL_DEPTH_8U, 1);
IplImage* V = cvCreateImage( cvGetSize(src), IPL_DEPTH_8U, 1);
IplImage* src_tmp1=cvCreateImage(cvGetSize(src),8,3);
// 高斯模糊
cvSmooth(src,src_tmp1,CV_GAUSSIAN,3,3);
// hue色度,saturation飽和度,value純度
cvCvtColor(src_tmp1, hsv, CV_BGR2HSV );
// 分為3個(gè)通道
cvSplit(hsv,H,S,V,0);
/*********************膚色檢測(cè)部分**************/
cvInRangeS(H,cvScalar(0.0,0.0,0,0),cvScalar(20.0,0.0,0,0),tmpH1);
cvInRangeS(S,cvScalar(75.0,0.0,0,0),cvScalar(200.0,0.0,0,0),tmpS1);
cvAnd(tmpH1,tmpS1,tmpH1,0);
// Red Hue with Low Saturation
// Hue 0 to 26 degree and Sat 20 to 90
cvInRangeS(H,cvScalar(0.0,0.0,0,0),cvScalar(13.0,0.0,0,0),tmpH2);
cvInRangeS(S,cvScalar(20.0,0.0,0,0),cvScalar(90.0,0.0,0,0),tmpS2);
cvAnd(tmpH2,tmpS2,tmpH2,0);
// Red Hue to Pink with Low Saturation
// Hue 340 to 360 degree and Sat 15 to 90
cvInRangeS(H,cvScalar(170.0,0.0,0,0),cvScalar(180.0,0.0,0,0),tmpH3);
cvInRangeS(S,cvScalar(15.0,0.0,0,0),cvScalar(90.,0.0,0,0),tmpS3);
cvAnd(tmpH3,tmpS3,tmpH3,0);
// Combine the Hue and Sat detections
cvOr(tmpH3,tmpH2,tmpH2,0);
cvOr(tmpH1,tmpH2,tmpH1,0);
cvCopy(tmpH1,dst);
cvReleaseImage(&hsv);
cvReleaseImage(&tmpH1);
cvReleaseImage(&tmpS1);
cvReleaseImage(&tmpH2);
cvReleaseImage(&tmpS2);
cvReleaseImage(&tmpH3);
cvReleaseImage(&tmpS3);
cvReleaseImage(&H);
cvReleaseImage(&S);
cvReleaseImage(&V);
cvReleaseImage(&src_tmp1);
}
// UIImage類型轉(zhuǎn)換為IPlImage類型
+ (IplImage*)convertToIplImage:(UIImage*)image {
CGImageRef imageRef = image.CGImage;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
IplImage *iplImage = cvCreateImage(cvSize(image.size.width, image.size.height), IPL_DEPTH_8U, 4);
CGContextRef contextRef = CGBitmapContextCreate(iplImage->imageData, iplImage->width, iplImage->height, iplImage->depth, iplImage->widthStep, colorSpace, kCGImageAlphaPremultipliedLast|kCGBitmapByteOrderDefault);
CGContextDrawImage(contextRef, CGRectMake(0, 0, image.size.width, image.size.height), imageRef);
CGContextRelease(contextRef);
CGColorSpaceRelease(colorSpace);
IplImage *ret = cvCreateImage(cvGetSize(iplImage), IPL_DEPTH_8U, 3);
cvCvtColor(iplImage, ret, CV_RGB2BGR);
cvReleaseImage(&iplImage);
return ret;
}
// IplImage類型轉(zhuǎn)換為UIImage類型
+ (UIImage*)convertToUIImage:(IplImage*)image {
cvCvtColor(image, image, CV_BGR2RGB);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
NSData *data = [NSData dataWithBytes:image->imageData length:image->imageSize];
CGDataProviderRef provider = CGDataProviderCreateWithCFData((CFDataRef)data);
CGImageRef imageRef = CGImageCreate(image->width, image->height, image->depth, image->depth * image->nChannels, image->widthStep, colorSpace, kCGImageAlphaNone | kCGBitmapByteOrderDefault, provider, NULL, false, kCGRenderingIntentDefault);
UIImage *ret = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
CGDataProviderRelease(provider);
CGColorSpaceRelease(colorSpace);
return ret;
}

通訊錄管理類
這里獲取了所有聯(lián)系人,以及手機(jī)號(hào),郵箱,姓名重復(fù)的聯(lián)系人,并進(jìn)行統(tǒng)計(jì)!!
聯(lián)系人獲取
獲取聯(lián)系人的方法
- (CleanPersonManager *)personManager
{
if (!_personManager)
{
_personManager = [CleanPersonManager shareManager];
}
return _personManager;
}
// 獲取所有通訊錄里的聯(lián)系人
-(void)getAllPersonArr:(BOOL)isSend
{
CleanSelf(self);
[self.personManager loadPerson:^(BOOL isFinish) {
dispatch_async(dispatch_get_main_queue(), ^{
weakself.isLoadAllPerson = isFinish;
if (isFinish)
{
CleanNsLog(@"%@",weakself.personManager.allPersonInfo);
CleanNsLog(@"%@",weakself.personManager.similaNamerInfo);
CleanNsLog(@"%@",weakself.personManager.similarPhoneNumInfo);
CleanNsLog(@"%@",weakself.personManager.similarEmailInfo);
}
else
{
//沒(méi)權(quán)限
}
});
}];
}
聯(lián)系人管理類 CleanPersonManager.h
//
// CleanPersonManager.h
// CleanOne
//
// Created by iOS Clean on 2023/8/25.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface CleanPersonManager : NSObject
+ (CleanPersonManager *)shareManager;
/// 所有聯(lián)系人的
@property (nonatomic, strong, readonly) NSDictionary *allPersonInfo;
/// 電話相似信息
@property (nonatomic, strong, readonly) NSDictionary *similarPhoneNumInfo;
/// 姓名相似信息
@property (nonatomic, strong, readonly) NSDictionary *similaNamerInfo;
/// 郵件相似信息
@property (nonatomic, strong, readonly) NSDictionary *similarEmailInfo;
// 獲取權(quán)限,并請(qǐng)求全部聯(lián)系人、相似名字、相似電話、相似郵箱
- (void)loadPerson:(void (^)(BOOL isFinish))completion;
// 獲取所有名字相同的聯(lián)系人
- (void)getDuplicateNameCompleteHandle:(void(^)(NSDictionary *duplicateNameDict))complete;
// 獲取所有電話相同的聯(lián)系人
- (void)getDuplicateNumberCompleteHandle:(void(^)(NSDictionary *duplicateNumberDict))complete;
// 獲取所有郵箱相同的聯(lián)系人
- (void)getDuplicateEmailCompleteHandle:(void(^)(NSDictionary *duplicateEmailDict))complete;
// 合并到第幾個(gè)人下面
- (void)mergeContactWithContacts:(NSArray *)contacts index:(NSInteger)index type:(NSInteger)type;
// 刪除對(duì)應(yīng)的聯(lián)系人
- (void)deleteContact:(CNContact *)contact;
@end
NS_ASSUME_NONNULL_END
CleanPersonManager.m
//
// CleanPersonManager.m
// CleanOne
//
// Created by iOS Clean on 2023/8/25.
//
#import "CleanPersonManager.h"
#import "PersonModel.h"
#import "AlertChoosPersonModel.h"
@interface CleanPersonManager ()
@property (nonatomic, strong) CNContactStore *contactStore;
@property (nonatomic, strong) NSMutableArray *allContacts;
/// 所有聯(lián)系人的
@property (nonatomic, strong, readwrite) NSDictionary *allPersonInfo;
/// 電話相似信息
@property (nonatomic, strong, readwrite) NSDictionary *similarPhoneNumInfo;
/// 姓名相似信息
@property (nonatomic, strong, readwrite) NSDictionary *similaNamerInfo;
/// 郵件相似信息
@property (nonatomic, strong, readwrite) NSDictionary *similarEmailInfo;
@end
@implementation CleanPersonManager
+ (CleanPersonManager *)shareManager {
static CleanPersonManager *manager = nil;
static dispatch_once_t token;
dispatch_once(&token, ^{
manager = [[CleanPersonManager alloc] init];
});
return manager;
}
// 判斷通訊錄授權(quán)狀態(tài)
- (void)loadPerson:(void (^)(BOOL isFinish))completion
{
// 獲取當(dāng)前App的通訊錄授權(quán)狀態(tài)
CNAuthorizationStatus status = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];
// 判斷授權(quán)狀態(tài)
if (status == CNAuthorizationStatusAuthorized)
{
// 如果已經(jīng)授權(quán), 獲取通訊錄信息
[self getAllPerson:^(BOOL isFinish) {
completion(YES);
}];
}
// 如果沒(méi)決定, 彈出指示框, 讓用戶選擇
else if (status == CNAuthorizationStatusNotDetermined)
{
// 如果用戶選擇授權(quán), 則獲取圖片
CNContactStore *store = [[CNContactStore alloc] init];
[store requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (!error) {
// 如果已經(jīng)授權(quán), 獲取通訊錄信息
[self getAllPerson:^(BOOL isFinish) {
completion(YES);
}];
}
else
{
// 開(kāi)啟權(quán)限提示
completion(NO);
}
}];
}
else
{
// 開(kāi)啟權(quán)限提示
completion(NO);
}
}
-(void)getAllPerson:(void(^)(BOOL isFinish))complete
{
// 如果已經(jīng)授權(quán), 獲取通訊錄信息
[self.allContacts removeAllObjects];
[self.contactStore requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
CNContactFetchRequest *request = [[CNContactFetchRequest alloc] initWithKeysToFetch:@[CNContactFamilyNameKey,
CNContactGivenNameKey,
CNContactPhoneNumbersKey,
CNContactEmailAddressesKey,
CNContactPostalAddressesKey,
CNContactOrganizationNameKey]];
NSError *conError = nil;
[self.contactStore enumerateContactsWithFetchRequest:request error:&conError usingBlock:^(CNContact * _Nonnull contact, BOOL * _Nonnull stop) {
PersonModel *contactModel = [[PersonModel alloc] init];
contactModel.contact = contact;
// 匹配電話號(hào)碼
NSMutableArray *phoneArray = [NSMutableArray array];
NSCharacterSet *numSet = [[NSCharacterSet characterSetWithCharactersInString:@"0123456789"] invertedSet];
NSString *phoneNumber = @"";
for(NSInteger i=0;i<contact.phoneNumbers.count;i++)
{
phoneNumber = [[contact.phoneNumbers[i].value.stringValue componentsSeparatedByCharactersInSet:numSet] componentsJoinedByString:@""];;
[phoneArray addObject:phoneNumber];
}
contactModel.mobileNumberArr = phoneArray;
contactModel.mobileNumber = phoneNumber;
NSString *contactName = @"";
if ([NSString stringWithFormat:@"%@%@", contact.familyName, contact.givenName]) {
contactName = [NSString stringWithFormat:@"%@%@", contact.familyName, contact.givenName];
}
contactModel.userName = contactName;
NSString * emailAddress = @"";
NSMutableArray *emailArr = [NSMutableArray array];
for (NSInteger i=0;i<contact.emailAddresses.count;i++) {
CNLabeledValue *emaileVale = contact.emailAddresses[I];
emailAddress = emaileVale.value;
[emailArr addObject:emailAddress];
}
contactModel.email = emailAddress;
contactModel.emailArr = emailArr;
NSString *companyName = @"";
if (contact.organizationName.length > 0) {
companyName = contact.organizationName;
}
contactModel.company = companyName;
[self.allContacts addObject:contactModel];
}];
NSInteger count = self.allContacts.count > 0 ? self.allContacts.count : 0;
NSDictionary *dict = @{@"dataArr":self.allContacts, @"totalCount":@(count), @"contactType":@0};
self.allPersonInfo = dict;
}];
[self getDuplicateNameCompleteHandle:^(NSDictionary * _Nonnull duplicateNameDict) {
self.similaNamerInfo = duplicateNameDict;
if (self.similarPhoneNumInfo && self.similarEmailInfo)
{
complete(YES);
}
}];
[self getDuplicateEmailCompleteHandle:^(NSDictionary * _Nonnull duplicateEmailDict) {
self.similarEmailInfo = duplicateEmailDict;
if (self.similaNamerInfo && self.similarPhoneNumInfo)
{
complete(YES);
}
}];
[self getDuplicateNumberCompleteHandle:^(NSDictionary * _Nonnull duplicateNumberDict) {
self.similarPhoneNumInfo = duplicateNumberDict;
if (self.similaNamerInfo && self.similarEmailInfo)
{
complete(YES);
}
}];
}
- (void)getDuplicateNameCompleteHandle:(void(^)(NSDictionary *duplicateNameDict))complete {
NSArray *duplicateNameArr = [self analyseDuplicateNameContact:self.allContacts];
NSInteger count = 0;
if (duplicateNameArr.count > 0) {
for (NSArray *arr in duplicateNameArr) {
count = count + arr.count;
}
NSDictionary *dict = @{@"dataArr":duplicateNameArr, @"totalCount":@(count), @"contactType":@1};
self.similaNamerInfo = dict;
complete(dict);
} else {
NSDictionary *dict = @{@"dataArr":@[], @"totalCount":@(0), @"contactType":@1};
self.similaNamerInfo = dict;
complete(dict);
}
}
- (void)getDuplicateNumberCompleteHandle:(void(^)(NSDictionary *duplicateNumberDict))complete{
NSArray *duplicateNumberArr = [self analyseDuplicateNumberAndEmailContact:self.allContacts type:@"1"];
NSInteger count = 0;
if (duplicateNumberArr.count > 0) {
for (NSArray *arr in duplicateNumberArr) {
count = count + arr.count;
}
NSDictionary *dict = @{@"dataArr":duplicateNumberArr, @"totalCount":@(count), @"contactType":@2};
self.similarPhoneNumInfo = dict;
complete(dict);
} else {
NSDictionary *dict = @{@"dataArr":@[], @"totalCount":@(0), @"contactType":@2};
self.similarPhoneNumInfo = dict;
complete(dict);
}
}
- (void)getDuplicateEmailCompleteHandle:(void(^)(NSDictionary *duplicateEmailDict))complete {
NSArray *duplicateEmailArr = [self analyseDuplicateNumberAndEmailContact:self.allContacts type:@"2"];
NSInteger count = 0;
if (duplicateEmailArr.count > 0) {
for (NSArray *arr in duplicateEmailArr) {
count = count + arr.count;
}
NSDictionary *dict = @{@"dataArr":duplicateEmailArr, @"totalCount":@(count), @"contactType":@3};
self.similarEmailInfo = dict;
complete(dict);
} else {
NSDictionary *dict = @{@"dataArr":@[], @"totalCount":@(0), @"contactType":@3};
self.similarEmailInfo = dict;
complete(dict);
}
}
// 處理相似名字的聯(lián)系人
- (NSArray *)analyseDuplicateNameContact:(NSArray *)contactArr {
NSMutableArray *inputArr = [NSMutableArray arrayWithArray:contactArr];
NSMutableArray *outputArr = [NSMutableArray array];
for (int i = 0; i < inputArr.count; i++) {
PersonModel *model1 = inputArr[i];
NSMutableArray *tempArr = [NSMutableArray array];
[tempArr addObject:model1];
for (int j = i + 1; j < inputArr.count; j++) {
PersonModel *model2 = inputArr[j];
if ([model1.userName isEqualToString:model2.userName]) {
[tempArr addObject:model2];
}
}
if (tempArr.count > 1) {
[outputArr addObject:tempArr];
[inputArr removeObjectsInArray:tempArr];
i -= 1;
}
}
return outputArr;
}
// 處理相似手機(jī)的聯(lián)系人 1:電話 2:郵箱
- (NSArray *)analyseDuplicateNumberAndEmailContact:(NSArray *)contactArr type:(NSString *)type{
NSMutableArray *inputArr = [NSMutableArray arrayWithArray:contactArr];
NSMutableArray *outputArr = [NSMutableArray array];
for (int i = 0; i < inputArr.count; i++) {
PersonModel *model1 = inputArr[i];
NSMutableArray *tempArr = [NSMutableArray array];
[tempArr addObject:model1];
for (int j = i + 1; j < inputArr.count; j++) {
PersonModel *model2 = inputArr[j];
if ([type isEqualToString:@"1"])
{
NSDictionary *dataDict = [self contrastArr:model1.mobileNumberArr twoArr:model2.mobileNumberArr type:type];
if ([dataDict[@"isSame"] isEqualToString:@"1"]) {
model2.mobileNumber = dataDict[@"sameStr"];
[tempArr addObject:model2];
}
}
else
{
NSDictionary *dataDict = [self contrastArr:model1.emailArr twoArr:model2.emailArr type:type];
if ([dataDict[@"isSame"] isEqualToString:@"1"]) {
model2.email = dataDict[@"sameStr"];
[tempArr addObject:model2];
}
}
}
if (tempArr.count > 1) {
[outputArr addObject:tempArr];
[inputArr removeObjectsInArray:tempArr];
i -= 1;
}
}
return outputArr;
}
/// 是否有相同的數(shù)據(jù)
/// - Parameters:
/// - oneArr: 對(duì)比 1 數(shù)組
/// - twoArr: 對(duì)比 2 數(shù)組
/// - type: 1:對(duì)比電話 2:對(duì)比郵箱
-(NSDictionary *)contrastArr:(NSArray *)oneArr twoArr:(NSArray *)twoArr type:(NSString *)type
{
NSDictionary *dataDict = @{@"isSame":@"0",@"sameStr":@""};
BOOL isSame = NO;
for(NSInteger i=0;i<oneArr.count;i++)
{
NSString *contrastContent1 = oneArr[i];
for(NSInteger j=0;j<twoArr.count;j++)
{
NSString *contrastContent2 = twoArr[j];
if ([contrastContent1 isEqualToString:contrastContent2] && contrastContent1.length > 1 && contrastContent2.length > 1)
{
isSame = YES;
dataDict = @{@"isSame":@"1",@"sameStr":contrastContent1};
break;
}
}
if (isSame)
{
break;
}
}
return dataDict;
}
- (void)getAllContactsCompleteHandle:(void(^)(NSDictionary *allcontactsDict))complete {
[self.allContacts removeAllObjects];
[self.contactStore requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
CNContactFetchRequest *request = [[CNContactFetchRequest alloc] initWithKeysToFetch:@[CNContactFamilyNameKey,
CNContactGivenNameKey,
CNContactPhoneNumbersKey,
CNContactEmailAddressesKey,
CNContactPostalAddressesKey,
CNContactOrganizationNameKey]];
NSError *conError = nil;
[self.contactStore enumerateContactsWithFetchRequest:request error:&conError usingBlock:^(CNContact * _Nonnull contact, BOOL * _Nonnull stop) {
PersonModel *contactModel = [[PersonModel alloc] init];
contactModel.contact = contact;
NSCharacterSet *numSet = [[NSCharacterSet characterSetWithCharactersInString:@"0123456789"] invertedSet];
NSString *phoneNumber = @"";
if (contact.phoneNumbers.count > 0) {
phoneNumber = [[[contact.phoneNumbers firstObject].value.stringValue componentsSeparatedByCharactersInSet:numSet] componentsJoinedByString:@""];
}
contactModel.mobileNumber = phoneNumber;
NSString *contactName = @"";
if ([NSString stringWithFormat:@"%@%@", contact.familyName, contact.givenName]) {
contactName = [NSString stringWithFormat:@"%@%@", contact.familyName, contact.givenName];
}
contactModel.userName = contactName;
NSString * emailAddress = @"";
if (contact.emailAddresses.count > 0) {
CNLabeledValue *emaileVale = contact.emailAddresses[0];
emailAddress = emaileVale.value;
}
contactModel.email = emailAddress;
NSString *companyName = @"";
if (contact.organizationName.length > 0) {
companyName = contact.organizationName;
}
contactModel.company = companyName;
[self.allContacts addObject:contactModel];
}];
NSInteger count = self.allContacts.count > 0 ? self.allContacts.count : 0;
NSDictionary *dict = @{@"dataArr":self.allContacts, @"totalCount":@(count), @"contactType":@0};
self.allPersonInfo = dict;
complete(dict);
}];
}
- (void)mergeContactWithContacts:(NSArray *)contacts index:(NSInteger)index type:(NSInteger)type {
[self addContactWithModels:contacts index:index type:type];
CNSaveRequest *saveRequest = [[CNSaveRequest alloc] init];
for (AlertChoosPersonModel *model in contacts) {
CNMutableContact *contact = (CNMutableContact *)[model.contact mutableCopy];
[saveRequest deleteContact:contact];
}
[self.contactStore executeSaveRequest:saveRequest error:nil];
}
- (void)deleteContact:(CNContact *)contact {
CNSaveRequest *saveRequest = [[CNSaveRequest alloc] init];
CNMutableContact *mutContact = (CNMutableContact *)[contact mutableCopy];
[saveRequest deleteContact:mutContact];
[self.contactStore executeSaveRequest:saveRequest error:nil];
}
- (void)addContactWithModels:(NSArray *)models index:(NSInteger)index type:(NSInteger)type {
CNMutableContact *contact = [[CNMutableContact alloc] init];
CNSaveRequest *saveRequest = [[CNSaveRequest alloc] init];
switch (type) {
case 2: {
AlertChoosPersonModel *nameModel = models.firstObject;
contact.familyName = nameModel.contact.familyName;
contact.givenName = nameModel.contact.givenName;
NSMutableArray *numbers = [NSMutableArray array];
NSMutableArray *emails = [NSMutableArray array];
for (AlertChoosPersonModel *model in models) {
if (model.contact.emailAddresses.count > 0) {
CNLabeledValue *emaileValue = model.contact.emailAddresses[0];
[emails addObject:emaileValue];
}
if (model.contact.phoneNumbers.count > 0) {
CNLabeledValue *numberValue = model.contact.phoneNumbers[0];
[numbers addObject:numberValue];
}
}
contact.emailAddresses = emails;
contact.phoneNumbers = numbers;
[saveRequest addContact:contact toContainerWithIdentifier:nil];
}
break;
case 1: {
AlertChoosPersonModel *numberModel = models[index];
contact.familyName = numberModel.contact.familyName;
contact.givenName = numberModel.contact.givenName;
if (numberModel.contact.emailAddresses.count > 0) {
CNLabeledValue *emaileValue = numberModel.contact.emailAddresses[0];
contact.emailAddresses = @[emaileValue];
}
if (numberModel.contact.phoneNumbers.count > 0) {
CNLabeledValue *numberValue = numberModel.contact.phoneNumbers[0];
contact.phoneNumbers = @[numberValue];
}
[saveRequest addContact:contact toContainerWithIdentifier:nil];
}
break;
case 3: {
AlertChoosPersonModel *emailModel = models[index];
contact.familyName = emailModel.contact.familyName;
contact.givenName = emailModel.contact.givenName;
if (emailModel.contact.emailAddresses.count > 0) {
CNLabeledValue *emaileValue = emailModel.contact.emailAddresses[0];
contact.emailAddresses = @[emaileValue];
}
if (emailModel.contact.phoneNumbers.count > 0) {
CNLabeledValue *numberValue = emailModel.contact.phoneNumbers[0];
contact.phoneNumbers = @[numberValue];
}
[saveRequest addContact:contact toContainerWithIdentifier:nil];
}
break;
default:
break;
}
[self.contactStore executeSaveRequest:saveRequest error:nil];
}
- (CNContactStore *)contactStore {
if (!_contactStore) {
_contactStore = [[CNContactStore alloc] init];
}
return _contactStore;
}
- (NSMutableArray *)allContacts {
if (!_allContacts) {
_allContacts = [NSMutableArray array];
}
return _allContacts;
}
@end
重復(fù)姓名、聯(lián)系人等,合并的時(shí)候要選擇合并至哪個(gè)人底下,這里選擇的意義在于放棄未選中人的所有信息,將需要合并的值進(jìn)行合并,這里是有優(yōu)化的點(diǎn)的,可以把兩個(gè)人的所有信息都同步到一個(gè)人的身上,然后相同的過(guò)濾掉,我嫌麻煩所以沒(méi)做

提示選擇的 model
、、、
import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface AlertChoosPersonModel : NSObject
// 聯(lián)系人
@property (nonatomic, strong) CNContact *contact;
// 姓名
@property (nonatomic, copy) NSString *userName;
// 電話
@property (nonatomic, copy) NSString *mobileNumber;
// 郵箱
@property (nonatomic, copy) NSString *email;
// 公司
@property (nonatomic, copy) NSString *company;
// 是否選擇
@property (nonatomic, assign) BOOL isSelected;
@end
NS_ASSUME_NONNULL_END
、、、
PersonModel.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface PersonModel : NSObject
// 聯(lián)系人
@property (nonatomic, strong) CNContact *contact;
// 姓名
@property (nonatomic, copy) NSString *userName;
// 重復(fù)的電話
@property (nonatomic, copy) NSString *mobileNumber;
// 重復(fù)的郵箱
@property (nonatomic, copy) NSString *email;
// 公司
@property (nonatomic, copy) NSString *company;
// 是否選擇
@property (nonatomic, assign) BOOL isSelected;
// 電話
@property (nonatomic, copy) NSArray *mobileNumberArr;
// 郵箱
@property (nonatomic, copy) NSArray *emailArr;
@end
NS_ASSUME_NONNULL_END
日歷管理類
這就是獲取了日歷上的事件,方便進(jìn)行清理

日歷獲取使用方法
- (CleanDateManager *)dateManager
{
if (!_dateManager)
{
_dateManager = [CleanDateManager shareManager];
}
return _dateManager;
}
-(void)requestData
{
CleanSelf(self);
NSDate *startDate = [NSDate dateWithTimeInterval:-24*3600*365*3 sinceDate:[NSDate date]];
[self.dateManager getLocalCalendarAuthorization:startDate dataArray:^(NSArray * _Nonnull dataArray) {
NSLog(@"%@",dataArray);
}];
}
CleanDateManager.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface CleanDateManager : NSObject
/// 所有日歷的
@property (nonatomic, strong, readonly) NSArray *allDateArray;
+ (CleanDateManager *)shareManager;
// 判斷權(quán)限
- (void)getLocalCalendarAuthorization:(NSDate *)startDate dataArray:(void (^)(NSArray *dataArray))completion;
// 獲取數(shù)據(jù)
- (NSArray *)getLocalSystemCalendarEventWithStartDate:(NSDate *)startDate endDate:(NSDate *)endDate;
// 刪除數(shù)據(jù)
- (BOOL)deleteCalendarEventIdentifier:(NSString *)eventIdentifier;
@end
NS_ASSUME_NONNULL_END
CleanDateManager.m
//
// CleanDateManager.m
// CleanOne
//
// Created by iOS Clean on 2023/8/28.
//
#import "CleanDateManager.h"
#import "CleanDateModel.h"
@interface CleanDateManager ()
@property (nonatomic, strong) EKEventStore *eventStore;
/// 所有日歷的
@property (nonatomic, strong, readwrite) NSArray *allDateArray;
@end
@implementation CleanDateManager
+ (CleanDateManager *)shareManager {
static CleanDateManager *manager = nil;
static dispatch_once_t token;
dispatch_once(&token, ^{
manager = [[CleanDateManager alloc] init];
});
return manager;
}
- (instancetype)init {
self = [super init];
if (self) {
}
return self;
}
/**
EKAuthorizationStatusNotDetermined = 0, // 未進(jìn)行授權(quán)選擇
EKAuthorizationStatusRestricted, // 未授權(quán),且用戶無(wú)法更新,如家長(zhǎng)控制情況下
EKAuthorizationStatusDenied, // 用戶拒絕App使用
EKAuthorizationStatusAuthorized, // 已授權(quán),可使用
*/
- (void)getLocalCalendarAuthorization:(NSDate *)startDate dataArray:(void (^)(NSArray *dataArray))completion{
EKAuthorizationStatus eventStatus = [EKEventStore authorizationStatusForEntityType:EKEntityTypeEvent];
if (eventStatus == EKAuthorizationStatusNotDetermined) {
// 用戶尚未授權(quán),提示用戶授權(quán)。下邊的requestAccessToEntityType:方法可以調(diào)出系統(tǒng)授權(quán)彈窗
[self.eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError * _Nullable error) {
if (granted) {
// 允許
NSArray *array = [self getLocalSystemCalendarEventWithStartDate:startDate endDate:[NSDate date]];
completion(array);
} else {
// 不允許
completion(nil);
}
}];
} else if (eventStatus == EKAuthorizationStatusAuthorized) {
// 用戶已經(jīng)允許授權(quán)。作相應(yīng)處理,比如查詢?nèi)諝v里今天的所有事件..
NSArray *array = [self getLocalSystemCalendarEventWithStartDate:startDate endDate:[NSDate date]];
completion(array);
}
else
{
completion(nil);
}
}
- (NSArray *)getLocalSystemCalendarEventWithStartDate:(NSDate *)startDate endDate:(NSDate *)endDate {
NSArray *eventArray = [self.eventStore calendarsForEntityType:EKEntityTypeEvent];
NSMutableArray *calendar = [NSMutableArray array];
for (int i = 0; i < eventArray.count; i++) {
EKCalendar *temp = eventArray[I];
EKCalendarType type = temp.type;
if (type == EKCalendarTypeLocal || type == EKCalendarTypeCalDAV) {
[calendar addObject:temp];
}
}
NSPredicate *predicate = [self.eventStore predicateForEventsWithStartDate:startDate endDate:endDate calendars:calendar];
NSArray *events = [self.eventStore eventsMatchingPredicate:predicate];
events = [events sortedArrayUsingSelector:@selector(compareStartDateWithEvent:)];
NSMutableArray *results = [NSMutableArray array];
for (EKEvent *event in events) {
CleanDateModel *model = [[CleanDateModel alloc] init];
model.eventTitle = event.title;
model.eventDate = event.startDate;
model.event = event;
[results addObject:model];
}
return results;
}
- (void)writeToLocalCalendarAction {
EKEventStore *eventStore = [[EKEventStore alloc] init];
/**
事件保存到日歷
06.07 元素
title(標(biāo)題 NSString),
location(位置NSString),
startDate(開(kāi)始時(shí)間 2016/06/07 11:14AM),
endDate(結(jié)束時(shí)間 2016/06/07 11:14AM),
addAlarm(提醒時(shí)間 2016/06/07 11:14AM),
notes(備注類容NSString)
*/
/// 06.07 時(shí)間格式
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setAMSymbol:@"AM"];
[dateFormatter setPMSymbol:@"PM"];
[dateFormatter setDateFormat:@"hh:mm"];
NSDate *date = [NSDate date];
NSString *dateStr = [dateFormatter stringFromDate:date];
NSLog(@"%@",dateStr);
/// 創(chuàng)建事件
EKEvent *event = [EKEvent eventWithEventStore:eventStore];
event.title = [NSString stringWithFormat:@"寫入日歷事件-%@",dateStr];
event.location = @"北京app";
/// 開(kāi)始時(shí)間(必須傳)
event.startDate = [date dateByAddingTimeInterval:60 * 2];
/// 結(jié)束時(shí)間(必須傳)
event.endDate = [date dateByAddingTimeInterval:60 * 5 * 24];
/// event.allDay = YES;//全天
/// 添加提醒
/// 第一次提醒 (幾分鐘后)
[event addAlarm:[EKAlarm alarmWithRelativeOffset:60.0f * -1.0f]];
/// 第二次提醒 ()
/// [event addAlarm:[EKAlarm alarmWithRelativeOffset:60.0f * -10.0f * 24]];
/// 06.07 add 事件類容備注
NSString *str = @"這是備注";
event.notes = [NSString stringWithFormat:@"%@:%@", str, dateStr];
/// 將日歷事件添加到默認(rèn)的日歷源中
[event setCalendar:[eventStore defaultCalendarForNewEvents]];
/// 保存日歷事件
NSError *err;
[eventStore saveEvent:event span:EKSpanThisEvent error:&err];
}
/// 刪除日歷事件(刪除單個(gè))
/// @param eventIdentifier 事件ID(標(biāo)識(shí)符)
- (BOOL)deleteCalendarEventIdentifier:(NSString *)eventIdentifier {
EKEvent *event;
NSError *error = nil;
if (eventIdentifier && ![eventIdentifier isEqualToString:@""]) {
event = [self.eventStore eventWithIdentifier:eventIdentifier];
BOOL isSuccess = [self.eventStore removeEvent:event span:EKSpanThisEvent commit:YES error:&error];
return isSuccess;
}
else
{
return NO;
}
}
/// 刪除日歷事件(可刪除一段時(shí)間內(nèi)的事件)
/// @param startDate 開(kāi)始時(shí)間
/// @param endDate 結(jié)束時(shí)間
- (BOOL)deleteCalendarStartDate:(NSDate *)startDate addEndDate:(NSDate *)endDate {
// 獲取到此事件
NSArray * eventArray = [self.eventStore calendarsForEntityType:EKEntityTypeEvent];
NSMutableArray *onlyArray = [NSMutableArray array];
for (int i = 0; i < eventArray.count; i++) {
EKCalendar *tempCalendar = eventArray[I];
EKCalendarType type = tempCalendar.type;
if (type == EKCalendarTypeCalDAV) {
[onlyArray addObject:tempCalendar];
}
}
NSPredicate *predicate = [self.eventStore predicateForEventsWithStartDate:startDate endDate:endDate calendars:onlyArray];
NSArray *events = [self.eventStore eventsMatchingPredicate:predicate];
for (int i = 0; i < events.count; i ++) {
// 刪除這一條事件
EKEvent *event = events[I];
NSError *error = nil;
// commit:NO:最后再一次性提交
[self.eventStore removeEvent:event span:EKSpanThisEvent commit:NO error:&error];
}
//一次提交所有操作到事件庫(kù)
NSError *errored = nil;
BOOL commitSuccess = [self.eventStore commit:&errored];
return commitSuccess;
}
- (EKEventStore *)eventStore {
if (!_eventStore) {
_eventStore = [[EKEventStore alloc] init];
}
return _eventStore;
}
@end
日歷的 Model
CleanDateModel.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface CleanDateModel : NSObject
// 日歷
@property (nonatomic, strong) EKEvent *event;
// 日歷標(biāo)題
@property (nonatomic, copy) NSString *eventTitle;
// 日期
@property (nonatomic, strong) NSDate *eventDate;
// 是否選擇
@property (nonatomic, assign) BOOL isSelected;
@end
NS_ASSUME_NONNULL_END
CleanDateModel.m
#import "CleanDateModel.h"
@implementation CleanDateModel
- (instancetype)init {
if (self = [super init]) {
self.isSelected = NO;
}
return self;
}
@end
下面再說(shuō)下多語(yǔ)言的切換,我搜了下,應(yīng)用內(nèi)的切換是獲取 storyboard,然后進(jìn)行重新加載的,我只是在界面給了個(gè)提示,設(shè)置成功后下次啟動(dòng)生效

語(yǔ)言設(shè)置管理類
JHLanguage.h
//
// JHLanguage.h
// CleanOne
//
// Created by iOS Clean on 2023/9/27.
//
#import <Foundation/Foundation.h>
#define kAppLanguage @"cleanLanguage"
#define kAppLanguage_CH @"zh-Hans"
#define kAppLanguage_EN @"en"
#define kAppLanguage_FR @"fr-CN"
#define kAppLanguage_DE @"de-CN"
#define kAppLanguage_Je @"ja-CN"
#define kJHCurrentLanguage \
[[JHLanguage language] getIPhoneLanguage]
#define kJHSetLanguage(lan) \
[[JHLanguage language] setLanguageWith:lan]
#define kJHLocalizedString(key,tab) \
[[JHLanguage language] jh_stringForKey:key table:tab]
typedef NS_ENUM(NSUInteger, LanagueType) {
English,
Chinese,
German,
Japanese,
French
};
@interface JHLanguage : NSObject
+ (instancetype)language;
// 獲取當(dāng)前的語(yǔ)言
- (NSString *)getIPhoneLanguage;
// 設(shè)置語(yǔ)言
- (void)setLanguageWith:(LanagueType)language;
- (NSString *)jh_stringForKey:(NSString *)key table:(NSString *)table;
@end
JHLanguage.m
#import "JHLanguage.h"
@interface JHLanguage()
@property (nonatomic, strong) NSString *currentLanguage;
@property (nonatomic, strong) NSBundle *bundle;
@end
@implementation JHLanguage
+ (instancetype)language{
static JHLanguage *lan = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
lan = [[JHLanguage alloc] init];
});
return lan;
}
- (instancetype)init{
if (self = [super init]) {
_currentLanguage = [[NSUserDefaults standardUserDefaults] objectForKey:kAppLanguage];
if (!_currentLanguage) {
_currentLanguage = [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleLanguages"][0]; // system default.
}
_bundle = [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:_currentLanguage ofType:@"lproj"]];
}
return self;
}
- (NSString *)getIPhoneLanguage{
NSString *language = [[NSUserDefaults standardUserDefaults] objectForKey:kAppLanguage];
if (language == nil) {
language = [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleLanguages"][0];
}
return language;
}
- (void)setLanguageWith:(LanagueType)language{
if (language == Chinese) {
_currentLanguage = kAppLanguage_CH;
}else if (language == English){
_currentLanguage = kAppLanguage_EN;
}else if (language == German){
_currentLanguage = kAppLanguage_DE;
}else if (language == French){
_currentLanguage = kAppLanguage_FR;
}
else if (language == Japanese)
{
_currentLanguage = kAppLanguage_Je;
}
_bundle = [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:_currentLanguage ofType:@"lproj"]];
[[NSUserDefaults standardUserDefaults] setObject:_currentLanguage forKey:kAppLanguage];
[[NSUserDefaults standardUserDefaults] setValue:@[_currentLanguage] forKey:@"AppleLanguages"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
- (NSString *)jh_stringForKey:(NSString *)key table:(NSString *)table{
if (_bundle) {
return NSLocalizedStringFromTableInBundle(key, table, _bundle, nil);
}
return NSLocalizedStringFromTable(key, table, nil);
}
@end
國(guó)際化語(yǔ)言添加的方式說(shuō)一下
提前創(chuàng)建這個(gè)文件,名字不可更改


當(dāng)然你也可以對(duì) Info.Plist文件中的提示信息進(jìn)行設(shè)置
//比如說(shuō)日歷權(quán)限獲取就這樣寫,在切換語(yǔ)言后,權(quán)限的提示語(yǔ)也會(huì)跟著變
NSCalendarsUsageDescription = "應(yīng)用程序可以幫助用戶分析日歷中已過(guò)期或未使用的日程信息,以便用戶可以查看或刪除這些信息";

// 這是我定義的宏
#define CleanLanguage(key,comment) NSLocalizedStringFromTable(key,@"",comment)
// 使用方法如下
self.label.text = CleanLanguage(@"Charging", @"這里只是為了記錄是什么");
清理類的就這么多了,由于是公司項(xiàng)目,不能放到網(wǎng)上,有問(wèn)題自行研究吧。