最近做了一款清理类的 APP,说实话,iOS 清理真没什么可清理的,只能清理下相似照片,重复联系人,日历事件等
照片、视频管理类
我这里获取了相册的所有信息,并将相似照片,相似视频,照片大于10M 和 视频大于 20M 的大文件也整理了处理,可以直接调用,在属性上直接获取,记得刷新 UI 的时候要在主线程进行操作,否则会崩溃
调用方法如下
- (ClearPhotoManager *)clearPhotoManager
{
if (!_clearPhotoManager)
{
_clearPhotoManager = [ClearPhotoManager shareManager];
}
return _clearPhotoManager;
}
-(void)loadPhotoData
{
// 加载照片数据源
CleanSelf(self);
[CleanOneLoading showWithMessage:CleanLanguage(@"Loading...",@"加载中...")];
[self.clearPhotoManager getAllBigAsset:^(BOOL success) {
// 这里有很多属性,自己打印看看,这里列举了两个
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, // 相册变更默认处理
PhotoNotificationStatusClose = 1, // 相册变更不处理
PhotoNotificationStatusNeed = 2, // 相册变更主动处理
};
@protocol ClearPhotoManagerDelegate <NSObject>
@optional
/// 相册变动代理方法
- (void)clearPhotoLibraryDidChange;
@end
@interface ClearPhotoManager : NSObject
/// 单例
+ (ClearPhotoManager *)shareManager;
/// 代理
@property (nonatomic, weak) id<ClearPhotoManagerDelegate> delegate;
/// 变更状态
@property (nonatomic, assign) PhotoNotificationStatus notificationStatus;
// 这里是 PHASet 对象
@property (nonatomic, strong, readonly) NSMutableArray *allPhotoArr;
@property (nonatomic, strong, readonly) NSMutableArray *allVideoArr;
/// 相似照片信息:存储了相似图片数量及可以节省的内存空间大小
@property (nonatomic, strong, readonly) NSDictionary *similarInfo;
@property (nonatomic, strong, readonly) NSMutableArray *similarArr;
/// 截图照片信息:存储了屏幕截图数量及可以节省的内存空间大小
@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;
/// 大文件视频 - 长视频
@property (nonatomic, strong, readonly) NSMutableArray *bigVideoArr;
@property (nonatomic, strong, readonly) NSDictionary *bigVideoInfo;
// 获取相册的 PHAsset 对象
- (void)loadPhotoCompletionHandler:(void (^)(BOOL success))completion isVideo:(BOOL)isVideo;
/// 删除照片
+ (void)deleteAssets:(NSArray<PHAsset *> *)assets completionHandler:(void (^)(BOOL success, NSError *error))completion;
// 获取相册中的大文件
- (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对象
@property (nonatomic, strong) PHFetchResult *assetArray;
// 这里是 PHASet 对象
@property (nonatomic, strong, readwrite) NSMutableArray *allPhotoArr;
@property (nonatomic, strong, readwrite) NSMutableArray *allVideoArr;
/// 相似照片信息:存储了相似图片数量及可以节省的内存空间大小
@property (nonatomic, strong, readwrite) NSDictionary *similarInfo;
@property (nonatomic, strong, readwrite) NSMutableArray *similarArr;
/// 截图照片信息:存储了屏幕截图数量及可以节省的内存空间大小
@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;
// 完成的回调
@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 - 相册变换通知
- (instancetype)init
{
self = [super init];
if (self)
{
// 相册变换通知
[[PHPhotoLibrary sharedPhotoLibrary] registerChangeObserver:self];
}
return self;
}
- (void)dealloc
{
// 移除相册变换通知
[[PHPhotoLibrary sharedPhotoLibrary] unregisterChangeObserver:self];
}
// 相册变换时候会调用
- (void)photoLibraryDidChange:(PHChange *)changeInstance
{
// 筛选出没必要的变动
PHFetchResultChangeDetails *collectionChanges = [changeInstance changeDetailsForFetchResult:self.assetArray];
if (collectionChanges == nil || self.notificationStatus != PhotoNotificationStatusDefualt)
{
return;
}
// 回到主线程调用相册变动代理方法
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;
}
// 加载照片之前先清除旧数据
- (void)resetTagData
{
// 相册清空
self.allPhotoArr = nil;
self.similarInfo = nil;
self.similarArr = nil;
self.screenshotsInfo = nil;
self.screenshotsArr = nil;
self.bigPhotoArr = nil;
// 视频数据清空
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
{
// 有一个日期为空则直接返回
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))
{
// 如果是同一天,那就计算两者的时间差是不是在一个小时之内
NSInteger dateOneSecond = dateComponents1.minute;
NSInteger dateTwoSecond = dateComponents2.minute;
NSInteger Difference = labs(dateTwoSecond - dateOneSecond);
if(Difference < 30)
{
return YES;
}
else
{
return NO;
}
}
return NO;
}
// NSDate转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:^{
// 删除当前图片资源
[PHAssetChangeRequest deleteAssets:assets];
} completionHandler:^(BOOL success, NSError * _Nullable error) {
// 调用删除后的回调代码块
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 则用于控制请求的图片质量
_imageRequestOptions.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
}
return _imageRequestOptions;
}
- (PHImageRequestOptions *)imageSizeRequestOptions
{
if (!_imageSizeRequestOptions) {
_imageSizeRequestOptions = [[PHImageRequestOptions alloc] init];
// resizeMode 属性控制图像的剪裁
_imageSizeRequestOptions.resizeMode = PHImageRequestOptionsResizeModeExact;// exactly targetSize
// deliveryMode 则用于控制请求的图片质量
_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;
/*
如果不需要获取缩略图,就设置为NO,如果需要获取缩略图,则maximumSize为获取的最大尺寸。
以BBC为例,getThumbnail = NO时,打印宽高数据为:1920*1072。
getThumbnail = YES时,maximumSize为100*100。打印宽高数据为:100*55.
注:不乘[UIScreen mainScreen].scale,会发现缩略图在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;
}
// 获取相册详细信息
-(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);
}
}];
}
});
}
// 获取视频详细信息
-(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);
}
}
}];
}
});
}
// 判断相册授权状态
- (void)getAllBigAsset:(void (^)(BOOL success))completion
{
// 清除旧数据
[self resetTagData];
self.bigCompletionHandler = completion;
// 获取当前App的相册授权状态
PHAuthorizationStatus authorizationStatus = [PHPhotoLibrary authorizationStatus];
// 判断授权状态
if (authorizationStatus == PHAuthorizationStatusAuthorized)
{
[self greatAllPhotoAndVideos];
}
// 如果没决定, 弹出指示框, 让用户选择
else if (authorizationStatus == PHAuthorizationStatusNotDetermined)
{
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
// 如果用户选择授权, 则获取图片
if (status == PHAuthorizationStatusAuthorized)
{
[self greatAllPhotoAndVideos];
}
else
{
// 开启权限提示
completion(NO);
}
}];
}
else
{
completion(NO);
}
}
-(void)greatAllPhotoAndVideos
{
self.isPhotoFinish = NO;
self.isVideoFinish = NO;
// 获取相簿中的PHAsset对象
self.allVideoArr = [self greatAllArr:YES];
self.allPhotoArr = [self greatAllArr:NO];
// 处理相册问题
[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];
// 获取相册
PHFetchResult *smartAlbums = [PHAssetCollection fetchAssetCollectionsWithType:PHAssetCollectionTypeSmartAlbum subtype:PHAssetCollectionSubtypeSmartAlbumUserLibrary options:nil];
// 获取所有资源的集合,并按资源的创建时间排序,这样就可以通过和上一张图片判断日期来分组了
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;
// 过滤空相册
if (collection.estimatedAssetCount <= 0){
return;
}
// 获取相册内asset result
PHFetchResult<PHAsset *> *result = [PHAsset fetchAssetsInAssetCollection:collection options:option];
[result enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
//资源图片检测
if (![obj isKindOfClass:[PHAsset class]]) {
return;
}
PHAsset *asset = (PHAsset *) obj;
[resultArray addObject:asset];
}];
}];
return resultArray;
}
@end
相似度的算法使用的是 opencv2 来计算的相似度,但是有一点,照片过多时,内存会疯狂上涨,导致崩溃,原因就是对比这里出现的问题,解决办法如下
1.通过时间,获取照片(视频)的拍摄时间,对比时如果在同一天再去对比,当然你也可以再细致的去判断,比如判断相差时间在几分钟之内也 ok
// 是否为同一天
- (BOOL)isSameDay:(NSDate *)date1 date2:(NSDate *)date2
{
// 有一个日期为空则直接返回
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))
{
// 如果是同一天,那就计算两者的时间差是不是在一个小时之内
NSInteger dateOneSecond = dateComponents1.minute;
NSInteger dateTwoSecond = dateComponents2.minute;
NSInteger Difference = labs(dateTwoSecond - dateOneSecond);
if(Difference < 30)
{
return YES;
}
else
{
return NO;
}
}
return NO;
}
2.修改对比的方法,减少图片缩小后的大小,这里是最有效的,这样会大大减小内存,我减小到了 32 *32,不过量大肯定也会崩溃,我测试在同时对比 1500 张照片,210 个视频下是不会崩溃的
// 是否相似
+ (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;
}
下面是对比的类 和 model ,我这里视频和图片共用了一个 Model
CleanAlbumModel.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface CleanAlbumModel : NSObject
/// 图片资源
@property (nonatomic, strong) PHAsset *asset;
/// 图片
@property (nonatomic, strong) UIImage *exactImage;
/// 图片数据大小
@property (nonatomic, assign) NSUInteger originImageDataLength;
/// 时长
@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 图片相似度对比
#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 {
// 当前匹配结果,越接近于0.0匹配度越高
double dbRst=1.0;
// 匹配结果,-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("匹配结果为:%f\n",dbRst1);
if (dbRst1<=0.01) {
// 匹配成功
tfFound = 1;
break;
}
else if(dbRst==1.0 || dbRst1<dbRst) {
// 本次匹配有进步,更新结果
cvReleaseImage(&cropImage);
dbRst = dbRst1;
}
else if(dbRst1>dbRst) {
cvReleaseImage(&cropImage);
}
}
return dbRst;
}
+ (double)CompareHistWithSmallHeightIpl:(IplImage*)srcIpl withBigHeightIplImg:(IplImage*)srcIpl1 {
// 当前匹配结果,越接近于0.0匹配度越高
double dbRst=1.0;
// 匹配结果,-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("匹配结果为:%f\n",dbRst1);
if (dbRst1<=0.01) {
// 匹配成功
tfFound = 1;
break;
}
else if(dbRst==1.0 || dbRst1<dbRst) {
// 本次匹配有进步,更新结果
cvReleaseImage(&cropImage);
dbRst = dbRst1;
}
else if(dbRst1>dbRst) {
cvReleaseImage(&cropImage);
}
}
return dbRst;
}
+ (double)CompareHistWithSmallIpl:(IplImage*)srcIpl withBigIplImg:(IplImage*)srcIpl1 {
// 当前匹配结果,越接近于0.0匹配度越高
double dbRst=1.0;
// 水平、竖直偏移量
int xSub=0,ySub=0;
// 匹配结果,-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),竖直匹配结果为:%f\n",j,i,dbRst1);
if (dbRst1<=0.0375) {
// 匹配成功
tfFound = 1;
break;
} else if(dbRst==1.0 || dbRst1<dbRst) {
// 本次匹配有进步,更新结果
cvReleaseImage(&cropImage);
dbRst = dbRst1;
} else if(dbRst1>dbRst) {
cvReleaseImage(&cropImage);
// 竖直移动到点了,该水平移动了
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),水平移动匹配结果为:%f\n",k,i,dbRst1);
if (dbRst1<=0.0375) {
// 匹配成功
tfFound = 1;
xSub = k;
break;
} else if(dbRst1<dbRst) {
// 本次匹配有进步,更新结果
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;
}
// 多通道彩色图片的直方图比对
+ (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);
}
// 进行肤色检测
+ (void)SkinDetect:(IplImage*)src withParam:(IplImage*)dst {
// 创建图像头
// 用于存图像的一个中间变量,是用来分通道用的,分成hsv通道
IplImage* hsv = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 3);
// 通道的中间变量,用于肤色检测的中间变量
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个通道
cvSplit(hsv,H,S,V,0);
/*********************肤色检测部分**************/
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类型转换为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类型转换为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;
}
通讯录管理类
这里获取了所有联系人,以及手机号,邮箱,姓名重复的联系人,并进行统计!!
联系人获取
获取联系人的方法
- (CleanPersonManager *)personManager
{
if (!_personManager)
{
_personManager = [CleanPersonManager shareManager];
}
return _personManager;
}
// 获取所有通讯录里的联系人
-(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
{
//没权限
}
});
}];
}
联系人管理类 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;
/// 所有联系人的
@property (nonatomic, strong, readonly) NSDictionary *allPersonInfo;
/// 电话相似信息
@property (nonatomic, strong, readonly) NSDictionary *similarPhoneNumInfo;
/// 姓名相似信息
@property (nonatomic, strong, readonly) NSDictionary *similaNamerInfo;
/// 邮件相似信息
@property (nonatomic, strong, readonly) NSDictionary *similarEmailInfo;
// 获取权限,并请求全部联系人、相似名字、相似电话、相似邮箱
- (void)loadPerson:(void (^)(BOOL isFinish))completion;
// 获取所有名字相同的联系人
- (void)getDuplicateNameCompleteHandle:(void(^)(NSDictionary *duplicateNameDict))complete;
// 获取所有电话相同的联系人
- (void)getDuplicateNumberCompleteHandle:(void(^)(NSDictionary *duplicateNumberDict))complete;
// 获取所有邮箱相同的联系人
- (void)getDuplicateEmailCompleteHandle:(void(^)(NSDictionary *duplicateEmailDict))complete;
// 合并到第几个人下面
- (void)mergeContactWithContacts:(NSArray *)contacts index:(NSInteger)index type:(NSInteger)type;
// 删除对应的联系人
- (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;
/// 所有联系人的
@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;
}
// 判断通讯录授权状态
- (void)loadPerson:(void (^)(BOOL isFinish))completion
{
// 获取当前App的通讯录授权状态
CNAuthorizationStatus status = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];
// 判断授权状态
if (status == CNAuthorizationStatusAuthorized)
{
// 如果已经授权, 获取通讯录信息
[self getAllPerson:^(BOOL isFinish) {
completion(YES);
}];
}
// 如果没决定, 弹出指示框, 让用户选择
else if (status == CNAuthorizationStatusNotDetermined)
{
// 如果用户选择授权, 则获取图片
CNContactStore *store = [[CNContactStore alloc] init];
[store requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (!error) {
// 如果已经授权, 获取通讯录信息
[self getAllPerson:^(BOOL isFinish) {
completion(YES);
}];
}
else
{
// 开启权限提示
completion(NO);
}
}];
}
else
{
// 开启权限提示
completion(NO);
}
}
-(void)getAllPerson:(void(^)(BOOL isFinish))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;
// 匹配电话号码
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);
}
}
// 处理相似名字的联系人
- (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;
}
// 处理相似手机的联系人 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;
}
/// 是否有相同的数据
/// - Parameters:
/// - oneArr: 对比 1 数组
/// - twoArr: 对比 2 数组
/// - type: 1:对比电话 2:对比邮箱
-(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
重复姓名、联系人等,合并的时候要选择合并至哪个人底下,这里选择的意义在于放弃未选中人的所有信息,将需要合并的值进行合并,这里是有优化的点的,可以把两个人的所有信息都同步到一个人的身上,然后相同的过滤掉,我嫌麻烦所以没做
提示选择的 model
、、、
import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface AlertChoosPersonModel : NSObject
// 联系人
@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
// 联系人
@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;
// 电话
@property (nonatomic, copy) NSArray *mobileNumberArr;
// 邮箱
@property (nonatomic, copy) NSArray *emailArr;
@end
NS_ASSUME_NONNULL_END
日历管理类
这就是获取了日历上的事件,方便进行清理
日历获取使用方法
- (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;
// 判断权限
- (void)getLocalCalendarAuthorization:(NSDate *)startDate dataArray:(void (^)(NSArray *dataArray))completion;
// 获取数据
- (NSArray *)getLocalSystemCalendarEventWithStartDate:(NSDate *)startDate endDate:(NSDate *)endDate;
// 删除数据
- (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, // 未进行授权选择
EKAuthorizationStatusRestricted, // 未授权,且用户无法更新,如家长控制情况下
EKAuthorizationStatusDenied, // 用户拒绝App使用
EKAuthorizationStatusAuthorized, // 已授权,可使用
*/
- (void)getLocalCalendarAuthorization:(NSDate *)startDate dataArray:(void (^)(NSArray *dataArray))completion{
EKAuthorizationStatus eventStatus = [EKEventStore authorizationStatusForEntityType:EKEntityTypeEvent];
if (eventStatus == EKAuthorizationStatusNotDetermined) {
// 用户尚未授权,提示用户授权。下边的requestAccessToEntityType:方法可以调出系统授权弹窗
[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) {
// 用户已经允许授权。作相应处理,比如查询日历里今天的所有事件..
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(标题 NSString),
location(位置NSString),
startDate(开始时间 2016/06/07 11:14AM),
endDate(结束时间 2016/06/07 11:14AM),
addAlarm(提醒时间 2016/06/07 11:14AM),
notes(备注类容NSString)
*/
/// 06.07 时间格式
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);
/// 创建事件
EKEvent *event = [EKEvent eventWithEventStore:eventStore];
event.title = [NSString stringWithFormat:@"写入日历事件-%@",dateStr];
event.location = @"北京app";
/// 开始时间(必须传)
event.startDate = [date dateByAddingTimeInterval:60 * 2];
/// 结束时间(必须传)
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];
/// 将日历事件添加到默认的日历源中
[event setCalendar:[eventStore defaultCalendarForNewEvents]];
/// 保存日历事件
NSError *err;
[eventStore saveEvent:event span:EKSpanThisEvent error:&err];
}
/// 删除日历事件(删除单个)
/// @param eventIdentifier 事件ID(标识符)
- (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;
}
}
/// 删除日历事件(可删除一段时间内的事件)
/// @param startDate 开始时间
/// @param endDate 结束时间
- (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];
}
//一次提交所有操作到事件库
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;
// 日历标题
@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
下面再说下多语言的切换,我搜了下,应用内的切换是获取 storyboard,然后进行重新加载的,我只是在界面给了个提示,设置成功后下次启动生效
语言设置管理类
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;
// 获取当前的语言
- (NSString *)getIPhoneLanguage;
// 设置语言
- (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
国际化语言添加的方式说一下
提前创建这个文件,名字不可更改
当然你也可以对 Info.Plist文件中的提示信息进行设置
//比如说日历权限获取就这样写,在切换语言后,权限的提示语也会跟着变
NSCalendarsUsageDescription = "应用程序可以帮助用户分析日历中已过期或未使用的日程信息,以便用户可以查看或删除这些信息";
// 这是我定义的宏
#define CleanLanguage(key,comment) NSLocalizedStringFromTable(key,@"",comment)
// 使用方法如下
self.label.text = CleanLanguage(@"Charging", @"这里只是为了记录是什么");
清理类的就这么多了,由于是公司项目,不能放到网上,有问题自行研究吧。