You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
340 lines
13 KiB
340 lines
13 KiB
![]()
2 years ago
|
//
|
||
|
// KJLoadImageView.m
|
||
|
// iSchool
|
||
|
//
|
||
|
// Created by 杨科军 on 2018/12/22.
|
||
|
// Copyright © 2018 杨科军. All rights reserved.
|
||
|
//
|
||
|
|
||
|
#import "KJLoadImageView.h"
|
||
|
#import <objc/runtime.h>
|
||
|
#import <CommonCrypto/CommonDigest.h>
|
||
|
|
||
|
/* 网络下载相关
|
||
|
* 图片下载器,没有直接使用NSURLSession之类的,因为希望这套库可以支持iOS6
|
||
|
*/
|
||
|
@interface KJImageDownloader : NSObject<NSURLSessionDownloadDelegate>
|
||
|
|
||
|
@property (nonatomic, strong) NSURLSession *session;
|
||
|
@property (nonatomic, strong) NSURLSessionDownloadTask *task;
|
||
|
@property (nonatomic, assign) unsigned long long totalLength;
|
||
|
@property (nonatomic, assign) unsigned long long currentLength;
|
||
|
@property (nonatomic, copy) KJDownloadProgressBlock progressBlock;
|
||
|
@property (nonatomic, copy) KJDownLoadDataCallBack callbackOnFinished;
|
||
|
|
||
|
/// 下载图片
|
||
|
- (void)kj_startDownloadImageWithUrl:(NSString*)url Progress:(KJDownloadProgressBlock)progress Complete:(KJDownLoadDataCallBack)complete;
|
||
|
|
||
|
@end
|
||
|
|
||
|
@implementation KJImageDownloader
|
||
|
|
||
|
- (void)kj_startDownloadImageWithUrl:(NSString*)url Progress:(KJDownloadProgressBlock)progress Complete:(KJDownLoadDataCallBack)complete{
|
||
|
self.progressBlock = progress;
|
||
|
self.callbackOnFinished = complete;
|
||
|
if ([NSURL URLWithString:url] == nil) {
|
||
|
if (complete) {
|
||
|
NSError *error = [NSError errorWithDomain:@"Domain" code:101 userInfo:@{@"message":@"URL不正确"}];
|
||
|
complete(nil, error);
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:60];
|
||
|
[request addValue:@"image/*" forHTTPHeaderField:@"Accept"];
|
||
|
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
|
||
|
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
|
||
|
self.session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:queue];
|
||
|
NSURLSessionDownloadTask *task = [self.session downloadTaskWithRequest:request];
|
||
|
[task resume];
|
||
|
self.task = task;
|
||
|
}
|
||
|
|
||
|
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
|
||
|
NSData *data = [NSData dataWithContentsOfURL:location];
|
||
|
if (self.progressBlock) {
|
||
|
self.progressBlock(self.totalLength, self.currentLength);
|
||
|
}
|
||
|
if (self.callbackOnFinished) {
|
||
|
self.callbackOnFinished(data, nil);
|
||
|
self.callbackOnFinished = nil;// 防止重复调用
|
||
|
}
|
||
|
}
|
||
|
|
||
|
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
|
||
|
self.currentLength = totalBytesWritten;
|
||
|
self.totalLength = totalBytesExpectedToWrite;
|
||
|
if (self.progressBlock) {
|
||
|
self.progressBlock(self.totalLength, self.currentLength);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
|
||
|
if ([error code] != NSURLErrorCancelled) {
|
||
|
if (self.callbackOnFinished) {
|
||
|
self.callbackOnFinished(nil, error);
|
||
|
}
|
||
|
self.callbackOnFinished = nil;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@end
|
||
|
|
||
|
/// 缓存相关
|
||
|
@interface UIApplication (KJCacheImage)
|
||
|
|
||
|
@property (nonatomic,strong,readonly) NSMutableDictionary *kj_cacheFaileDictionary;
|
||
|
|
||
|
- (UIImage *)kj_cacheImageForRequest:(NSURLRequest*)request;
|
||
|
- (void)kj_cacheImage:(UIImage *)image forRequest:(NSURLRequest *)request;
|
||
|
- (void)kj_cacheFailRequest:(NSURLRequest *)request;
|
||
|
/// 获取失败次数
|
||
|
- (NSUInteger)kj_failTimesForRequest:(NSURLRequest *)request;
|
||
|
|
||
|
@end
|
||
|
|
||
|
@implementation UIApplication (KJCacheImage)
|
||
|
|
||
|
- (NSMutableDictionary *)kj_cacheFaileDictionary {
|
||
|
NSMutableDictionary *dict = objc_getAssociatedObject(self, @selector(kj_cacheFaileDictionary));
|
||
|
if (!dict) {
|
||
|
dict = [[NSMutableDictionary alloc] init];
|
||
|
objc_setAssociatedObject(self, @selector(kj_cacheFaileDictionary), dict, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||
|
}
|
||
|
return dict;
|
||
|
}
|
||
|
|
||
|
- (void)kj_clearCache {
|
||
|
[self.kj_cacheFaileDictionary removeAllObjects];
|
||
|
objc_setAssociatedObject(self, @selector(kj_cacheFaileDictionary), nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||
|
}
|
||
|
|
||
|
- (void)kj_clearDiskCaches {
|
||
|
NSString *directoryPath = KJBannerLoadImages;
|
||
|
if ([[NSFileManager defaultManager] fileExistsAtPath:directoryPath isDirectory:nil]) {
|
||
|
NSError *error = nil;
|
||
|
[[NSFileManager defaultManager] removeItemAtPath:directoryPath error:&error];
|
||
|
}
|
||
|
[self kj_clearCache];
|
||
|
}
|
||
|
|
||
|
- (NSUInteger)kj_failTimesForRequest:(NSURLRequest *)request {
|
||
|
NSNumber *faileTimes = [self.kj_cacheFaileDictionary objectForKey:[self kj_md5:[self kj_keyForRequest:request]]];
|
||
|
if (faileTimes && [faileTimes respondsToSelector:@selector(integerValue)]) {
|
||
|
return faileTimes.integerValue;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/// 缓存图片
|
||
|
- (UIImage *)kj_cacheImageForRequest:(NSURLRequest *)request {
|
||
|
if (request) {
|
||
|
NSString *directoryPath = KJBannerLoadImages;
|
||
|
NSString *path = [NSString stringWithFormat:@"%@/%@", directoryPath, [self kj_md5:[self kj_keyForRequest:request]]];
|
||
|
return [UIImage imageWithContentsOfFile:path];
|
||
|
}
|
||
|
return nil;
|
||
|
}
|
||
|
///
|
||
|
- (void)kj_cacheFailRequest:(NSURLRequest *)request {
|
||
|
NSString *key = [self kj_md5:[self kj_keyForRequest:request]];
|
||
|
NSNumber *faileTimes = [self.kj_cacheFaileDictionary objectForKey:key];
|
||
|
NSUInteger times = 0;
|
||
|
if (faileTimes && [faileTimes respondsToSelector:@selector(integerValue)]) {
|
||
|
times = [faileTimes integerValue];
|
||
|
}
|
||
|
times++;
|
||
|
[self.kj_cacheFaileDictionary setObject:@(times) forKey:key];
|
||
|
}
|
||
|
|
||
|
- (void)kj_cacheImage:(UIImage *)image forRequest:(NSURLRequest *)request {
|
||
|
if (image == nil || request == nil) {
|
||
|
return;
|
||
|
}
|
||
|
NSString *directoryPath = KJBannerLoadImages;
|
||
|
if (![[NSFileManager defaultManager] fileExistsAtPath:directoryPath isDirectory:nil]) {
|
||
|
NSError *error = nil;
|
||
|
[[NSFileManager defaultManager] createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:&error];
|
||
|
if (error) return;
|
||
|
}
|
||
|
NSString *path = [NSString stringWithFormat:@"%@/%@",directoryPath,[self kj_md5:[self kj_keyForRequest:request]]];
|
||
|
NSData *data = UIImagePNGRepresentation(image);
|
||
|
if (data) {
|
||
|
[[NSFileManager defaultManager] createFileAtPath:path contents:data attributes:nil];/// 缓存数据
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#pragma mark - 内部方法
|
||
|
/// 拼接路径
|
||
|
- (NSString *)kj_keyForRequest:(NSURLRequest *)request {
|
||
|
BOOL portait = NO;
|
||
|
if (UIDeviceOrientationIsPortrait([[UIDevice currentDevice] orientation])) {
|
||
|
portait = YES;
|
||
|
}
|
||
|
return [NSString stringWithFormat:@"%@%@", request.URL.absoluteString, portait ? @"portait" : @"lanscape"];
|
||
|
}
|
||
|
|
||
|
/// 加密
|
||
|
- (NSString *)kj_md5:(NSString *)string {
|
||
|
if (string == nil || [string length] == 0) return nil;
|
||
|
unsigned char digest[CC_MD5_DIGEST_LENGTH], i;
|
||
|
CC_MD5([string UTF8String], (int)[string lengthOfBytesUsingEncoding:NSUTF8StringEncoding], digest);
|
||
|
NSMutableString *ms = [NSMutableString string];
|
||
|
for (i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {
|
||
|
[ms appendFormat:@"%02x", (int)(digest[i])];
|
||
|
}
|
||
|
return [ms copy];
|
||
|
}
|
||
|
|
||
|
@end
|
||
|
|
||
|
@interface KJLoadImageView (){
|
||
|
__weak KJImageDownloader *_imageDownloader;
|
||
|
}
|
||
|
|
||
|
@end
|
||
|
|
||
|
@implementation KJLoadImageView
|
||
|
|
||
|
- (void)configureLayout {
|
||
|
self.contentMode = UIViewContentModeScaleToFill;
|
||
|
self.kj_failedTimes = 2;
|
||
|
self.kj_isScale = NO;
|
||
|
}
|
||
|
- (instancetype)initWithFrame:(CGRect)frame {
|
||
|
if (self = [super initWithFrame:frame]) {
|
||
|
[self configureLayout];
|
||
|
}
|
||
|
return self;
|
||
|
}
|
||
|
|
||
|
- (void)kj_setImageWithURLString:(NSString*)url Placeholder:(UIImage*)placeholderImage {
|
||
|
return [self kj_setImageWithURLString:url Placeholder:placeholderImage Completion:nil];
|
||
|
}
|
||
|
|
||
|
- (void)kj_setImageWithURLString:(NSString*)url Placeholder:(UIImage*)placeholderImage Completion:(void(^)(UIImage*image))completion {
|
||
|
self.image = placeholderImage;
|
||
|
self.kj_completionBlock = completion;
|
||
|
if (url == nil || [url isKindOfClass:[NSNull class]] || (![url hasPrefix:@"http://"] && ![url hasPrefix:@"https://"])){
|
||
|
if (completion) self.kj_completionBlock(self.image);
|
||
|
return;
|
||
|
}
|
||
|
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
|
||
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||
|
[self kj_downloadWithReqeust:request holder:placeholderImage];
|
||
|
});
|
||
|
}
|
||
|
|
||
|
#pragma mark - 内部方法
|
||
|
/// 下载图片
|
||
|
- (void)kj_downloadWithReqeust:(NSURLRequest*)theRequest holder:(UIImage*)holder {
|
||
|
UIImage *cachedImage = [[UIApplication sharedApplication] kj_cacheImageForRequest:theRequest];
|
||
|
if (cachedImage) {
|
||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||
|
self.image = cachedImage;
|
||
|
});
|
||
|
if (self.kj_completionBlock) self.kj_completionBlock(cachedImage);
|
||
|
return;
|
||
|
}
|
||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||
|
self.image = holder;
|
||
|
});
|
||
|
/// 判断失败次数
|
||
|
if ([[UIApplication sharedApplication] kj_failTimesForRequest:theRequest] >= self.kj_failedTimes) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
[self kj_cancelRequest];
|
||
|
_imageDownloader = nil;
|
||
|
|
||
|
__weak __typeof(self) weakSelf = self;
|
||
|
KJImageDownloader *downloader = [[KJImageDownloader alloc] init];
|
||
|
_imageDownloader = downloader;
|
||
|
[downloader kj_startDownloadImageWithUrl:theRequest.URL.absoluteString Progress:^(unsigned long long total, unsigned long long current) {
|
||
|
!self.kj_progressBlock?:self.kj_progressBlock(total,current);
|
||
|
} Complete:^(NSData *data, NSError *error) {
|
||
|
if (data != nil && error == nil) {// 成功
|
||
|
UIImage *image = [UIImage imageWithData:data];
|
||
|
__block UIImage *finalImage = image;
|
||
|
if (image) {
|
||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||
|
if (weakSelf.kj_isScale) {// 剪裁
|
||
|
if (fabs(weakSelf.frame.size.width - image.size.width) != 0 && fabs(weakSelf.frame.size.height - image.size.height) != 0) {
|
||
|
finalImage = [KJLoadImageView kj_clipImage:image Size:weakSelf.frame.size IsScaleToMax:YES];
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
[[UIApplication sharedApplication] kj_cacheImage:finalImage forRequest:theRequest];
|
||
|
} else {
|
||
|
[[UIApplication sharedApplication] kj_cacheFailRequest:theRequest];
|
||
|
}
|
||
|
if (finalImage) {
|
||
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||
|
weakSelf.image = finalImage;
|
||
|
});
|
||
|
if (weakSelf.kj_completionBlock) {
|
||
|
weakSelf.kj_completionBlock(weakSelf.image);
|
||
|
}
|
||
|
}else {
|
||
|
if (weakSelf.kj_completionBlock) {
|
||
|
weakSelf.kj_completionBlock(weakSelf.image);
|
||
|
}
|
||
|
}
|
||
|
}else {
|
||
|
[[UIApplication sharedApplication] kj_cacheFailRequest:theRequest];
|
||
|
if (weakSelf.kj_completionBlock) {
|
||
|
weakSelf.kj_completionBlock(weakSelf.image);
|
||
|
}
|
||
|
}
|
||
|
}];
|
||
|
}
|
||
|
|
||
|
- (void)kj_cancelRequest {
|
||
|
[_imageDownloader.task cancel];
|
||
|
}
|
||
|
|
||
|
/// 裁剪图片
|
||
|
+ (UIImage*)kj_clipImage:(UIImage*)image Size:(CGSize)size IsScaleToMax:(BOOL)isScaleToMax {
|
||
|
CGFloat scale = [UIScreen mainScreen].scale;
|
||
|
UIGraphicsBeginImageContextWithOptions(size, NO, scale);
|
||
|
CGSize aspectFitSize = CGSizeZero;
|
||
|
if (image.size.width != 0 && image.size.height != 0) {
|
||
|
CGFloat rateWidth = size.width / image.size.width;
|
||
|
CGFloat rateHeight = size.height / image.size.height;
|
||
|
CGFloat rate = isScaleToMax ? MAX(rateHeight, rateWidth) : MIN(rateHeight, rateWidth);
|
||
|
aspectFitSize = CGSizeMake(image.size.width * rate, image.size.height * rate);
|
||
|
}
|
||
|
[image drawInRect:CGRectMake(0, 0, aspectFitSize.width, aspectFitSize.height)];
|
||
|
UIImage *finalImage = UIGraphicsGetImageFromCurrentImageContext();
|
||
|
UIGraphicsEndImageContext();
|
||
|
return finalImage;
|
||
|
}
|
||
|
|
||
|
#pragma mark - 缓存相关方法
|
||
|
+ (void)kj_clearImagesCache {
|
||
|
[[UIApplication sharedApplication] kj_clearDiskCaches];
|
||
|
}
|
||
|
|
||
|
+ (unsigned long long)kj_imagesCacheSize {
|
||
|
NSString *directoryPath = KJBannerLoadImages;
|
||
|
BOOL isDir = NO;
|
||
|
unsigned long long total = 0;
|
||
|
if ([[NSFileManager defaultManager] fileExistsAtPath:directoryPath isDirectory:&isDir]) {
|
||
|
if (isDir) {
|
||
|
NSError *error = nil;
|
||
|
NSArray *array = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:directoryPath error:&error];
|
||
|
if (error == nil) {
|
||
|
for (NSString *subpath in array) {
|
||
|
NSString *path = [directoryPath stringByAppendingPathComponent:subpath];
|
||
|
NSDictionary *dict = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&error];
|
||
|
if (!error) {
|
||
|
total += [dict[NSFileSize] unsignedIntegerValue];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return total;
|
||
|
}
|
||
|
|
||
|
@end
|