├── .gitignore ├── GQImageDownloader ├── GQCategory │ ├── NSData+GQImageDownloader.h │ ├── NSData+GQImageDownloader.m │ ├── UIImage+GQImageDownloader.h │ └── UIImage+GQImageDownloader.m ├── GQImageCache │ ├── GQImageCacheManager.h │ ├── GQImageCacheManager.m │ ├── GQImageGobalPaths.h │ └── GQImageGobalPaths.m ├── GQImageDownloaderOperation │ ├── GQImageDownloaderBaseOperation.h │ ├── GQImageDownloaderBaseOperation.m │ ├── GQImageDownloaderOperationDelegate.h │ ├── GQImageDownloaderOperationManager.h │ ├── GQImageDownloaderOperationManager.m │ ├── GQImageDownloaderSessionManager.h │ ├── GQImageDownloaderSessionManager.m │ ├── GQImageDownloaderSessionOperation.h │ ├── GQImageDownloaderSessionOperation.m │ ├── GQImageDownloaderURLConnectionOperation.h │ └── GQImageDownloaderURLConnectionOperation.m ├── GQImageRequestConfigure │ ├── GQImageDataDownloader.h │ ├── GQImageDataDownloader.m │ ├── GQImageDownloaderBaseURLRequest.h │ ├── GQImageDownloaderBaseURLRequest.m │ └── GQImageDownloaderConst.h ├── GQThirdPart │ └── WebP.framework │ │ ├── Headers │ │ ├── config.h │ │ ├── decode.h │ │ ├── demux.h │ │ ├── encode.h │ │ ├── extras.h │ │ ├── format_constants.h │ │ ├── mux.h │ │ ├── mux_types.h │ │ └── types.h │ │ └── WebP ├── UIImageView+GQImageDownloader.h └── UIImageView+GQImageDownloader.m ├── GQImageVideoViewer.podspec ├── GQImageVideoViewer ├── GQBaseObject │ ├── GQBaseImageVideoModel.h │ ├── GQBaseImageVideoModel.m │ ├── GQBaseImageView.h │ ├── GQBaseImageView.m │ ├── GQBaseTableView.h │ ├── GQBaseTableView.m │ ├── GQBaseVideoView.h │ └── GQBaseVideoView.m ├── GQImageVideoScrollView.h ├── GQImageVideoScrollView.m ├── GQImageVideoTableView.h ├── GQImageVideoTableView.m ├── GQImageVideoViewer.h ├── GQImageVideoViewer.m └── GQImageVideoViewerConst.h ├── GQImageVideoViewerDemo ├── GQImageVideoViewerDemo.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcuserdata │ │ └── gaoqi.xcuserdatad │ │ └── UserInterfaceState.xcuserstate ├── GQImageVideoViewerDemo │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── DemoViewController.h │ ├── DemoViewController.m │ ├── GQDemoVideoView.h │ ├── GQDemoVideoView.m │ ├── Info.plist │ ├── image │ │ ├── 1.jpg │ │ ├── 10.jpg │ │ ├── 2.jpg │ │ ├── 3.jpg │ │ ├── 4.jpg │ │ ├── 5.jpg │ │ ├── 6.jpg │ │ ├── 7.jpg │ │ ├── 8.jpg │ │ └── 9.jpg │ └── main.m ├── GQImageVideoViewerDemoTests │ ├── GQImageVideoViewerDemoTests.m │ └── Info.plist └── GQImageVideoViewerDemoUITests │ ├── GQImageVideoViewerDemoUITests.m │ └── Info.plist ├── LICENSE ├── README.md └── Screenshot └── demo.gif /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | # Mac OS default file 6 | .DS_Store 7 | 8 | 9 | ## Build generated 10 | build/ 11 | DerivedData/ 12 | 13 | ## Various settings 14 | *.pbxuser 15 | !default.pbxuser 16 | *.mode1v3 17 | !default.mode1v3 18 | *.mode2v3 19 | !default.mode2v3 20 | *.perspectivev3 21 | !default.perspectivev3 22 | xcuserdata/ 23 | 24 | ## Other 25 | *.moved-aside 26 | *.xccheckout 27 | *.xcscmblueprint 28 | 29 | ## Obj-C/Swift specific 30 | *.hmap 31 | *.ipa 32 | *.dSYM.zip 33 | *.dSYM 34 | 35 | # CocoaPods 36 | # 37 | # We recommend against adding the Pods directory to your .gitignore. However 38 | # you should judge for yourself, the pros and cons are mentioned at: 39 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 40 | # 41 | # Pods/ 42 | 43 | # Carthage 44 | # 45 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 46 | # Carthage/Checkouts 47 | 48 | Carthage/Build 49 | 50 | # fastlane 51 | # 52 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 53 | # screenshots whenever they are needed. 54 | # For more information about the recommended setup visit: 55 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 56 | 57 | fastlane/report.xml 58 | fastlane/Preview.html 59 | fastlane/screenshots 60 | fastlane/test_output 61 | 62 | # Code Injection 63 | # 64 | # After new code Injection tools there's a generated folder /iOSInjectionProject 65 | # https://github.com/johnno1962/injectionforxcode 66 | 67 | iOSInjectionProject/ 68 | -------------------------------------------------------------------------------- /GQImageDownloader/GQCategory/NSData+GQImageDownloader.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSData+GQImageDownloader.h 3 | // GQImageDownload 4 | // 5 | // Created by 高旗 on 2017/11/23. 6 | // Copyright © 2017年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface NSData (GQImageDownloader) 13 | 14 | /** 15 | 获取图片资源 16 | 17 | return UIImage * 18 | */ 19 | - (UIImage *)gqImageWithData; 20 | 21 | /** 22 | 获取图片种类 23 | 24 | return 种类字符串 25 | */ 26 | - (NSString *)gqTypeForImageData; 27 | 28 | #ifdef GQ_WEBP 29 | 30 | /** 31 | data转换成UIImage 32 | 33 | param imgData 34 | return 35 | */ 36 | + (UIImage *)gq_imageWithWebPData:(NSData *)imgData; 37 | 38 | #endif 39 | @end 40 | -------------------------------------------------------------------------------- /GQImageDownloader/GQCategory/NSData+GQImageDownloader.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSData+GQImageDownloader.m 3 | // GQImageDownload 4 | // 5 | // Created by 高旗 on 2017/11/23. 6 | // Copyright © 2017年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import "NSData+GQImageDownloader.h" 10 | #import 11 | 12 | #ifdef GQ_WEBP 13 | #import "UIImage+GQImageDownloader.h" 14 | #endif 15 | 16 | #ifdef GQ_WEBP 17 | 18 | #import 19 | #import 20 | #import 21 | 22 | static void FreeImageData(void *info, const void *data, size_t size) { 23 | free((void *)data); 24 | } 25 | 26 | #endif 27 | 28 | @implementation NSData (GQImageDownloader) 29 | 30 | - (UIImage *)gqImageWithData 31 | { 32 | UIImage *image; 33 | NSString *imageContentType = [self gqTypeForImageData]; 34 | if ([imageContentType isEqualToString:@"image/gif"]) 35 | { 36 | image = [self animatedGIFWithData]; 37 | } 38 | #ifdef GQ_WEBP 39 | else if ([imageContentType isEqualToString:@"image/webp"]) { 40 | image = [NSData gq_imageWithWebPData:self]; 41 | } 42 | #endif 43 | else { 44 | image = [[UIImage alloc] initWithData:self]; 45 | UIImageOrientation orientation = [self imageOrientationFromImageData]; 46 | if (orientation != UIImageOrientationUp) { 47 | image = [UIImage imageWithCGImage:image.CGImage 48 | scale:image.scale 49 | orientation:orientation]; 50 | } 51 | } 52 | return image; 53 | } 54 | 55 | - (UIImage *)animatedGIFWithData 56 | { 57 | 58 | CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)self, NULL); 59 | 60 | size_t count = CGImageSourceGetCount(source); 61 | 62 | UIImage *animatedImage; 63 | 64 | if (count <= 1) { 65 | animatedImage = [[UIImage alloc] initWithData:self]; 66 | }else { 67 | NSMutableArray *images = [NSMutableArray array]; 68 | 69 | NSTimeInterval duration = 0.0f; 70 | 71 | for (size_t i = 0; i < count; i++) { 72 | CGImageRef image = CGImageSourceCreateImageAtIndex(source, i, NULL); 73 | 74 | duration += [self frameDurationAtIndex:i source:source]; 75 | 76 | [images addObject:[UIImage imageWithCGImage:image scale:[UIScreen mainScreen].scale orientation:UIImageOrientationUp]]; 77 | 78 | CGImageRelease(image); 79 | } 80 | 81 | if (!duration) { 82 | duration = (1.0f / 10.0f) * count; 83 | } 84 | 85 | animatedImage = [UIImage animatedImageWithImages:images duration:duration]; 86 | } 87 | 88 | CFRelease(source); 89 | 90 | return animatedImage; 91 | } 92 | 93 | - (float)frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source 94 | { 95 | 96 | float frameDuration = 0.1f; 97 | 98 | CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil); 99 | 100 | NSDictionary *frameProperties = (__bridge NSDictionary *)cfFrameProperties; 101 | 102 | NSDictionary *gifProperties = frameProperties[(NSString *)kCGImagePropertyGIFDictionary]; 103 | 104 | NSNumber *delayTimeUnclampedProp = gifProperties[(NSString *)kCGImagePropertyGIFUnclampedDelayTime]; 105 | 106 | if (delayTimeUnclampedProp) { 107 | 108 | frameDuration = [delayTimeUnclampedProp floatValue]; 109 | }else { 110 | 111 | NSNumber *delayTimeProp = gifProperties[(NSString *)kCGImagePropertyGIFDelayTime]; 112 | 113 | if (delayTimeProp) { 114 | 115 | frameDuration = [delayTimeProp floatValue]; 116 | } 117 | } 118 | 119 | if (frameDuration < 0.011f) { 120 | frameDuration = 0.100f; 121 | } 122 | 123 | CFRelease(cfFrameProperties); 124 | return frameDuration; 125 | } 126 | 127 | - (UIImageOrientation)imageOrientationFromImageData 128 | { 129 | 130 | UIImageOrientation result = UIImageOrientationUp; 131 | 132 | CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)(NSData *)self, NULL); 133 | 134 | if (imageSource) { 135 | 136 | CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL); 137 | 138 | if (properties) { 139 | CFTypeRef val; 140 | 141 | int exifOrientation; 142 | 143 | val = CFDictionaryGetValue(properties, kCGImagePropertyOrientation); 144 | 145 | if (val) { 146 | CFNumberGetValue(val, kCFNumberIntType, &exifOrientation); 147 | 148 | result = [self exifOrientationToiOSOrientation:exifOrientation]; 149 | } 150 | CFRelease((CFTypeRef) properties); 151 | } 152 | CFRelease(imageSource); 153 | } 154 | return result; 155 | } 156 | 157 | - (UIImageOrientation)exifOrientationToiOSOrientation:(int)exifOrientation 158 | { 159 | UIImageOrientation orientation = UIImageOrientationUp; 160 | switch (exifOrientation) { 161 | case 1: 162 | orientation = UIImageOrientationUp; 163 | break; 164 | 165 | case 2: 166 | orientation = UIImageOrientationUpMirrored; 167 | break; 168 | 169 | case 3: 170 | orientation = UIImageOrientationDown; 171 | break; 172 | 173 | case 4: 174 | orientation = UIImageOrientationDownMirrored; 175 | break; 176 | 177 | case 5: 178 | orientation = UIImageOrientationLeftMirrored; 179 | break; 180 | 181 | case 6: 182 | orientation = UIImageOrientationRight; 183 | break; 184 | 185 | case 7: 186 | orientation = UIImageOrientationRightMirrored; 187 | break; 188 | 189 | case 8: 190 | orientation = UIImageOrientationLeft; 191 | break; 192 | 193 | default: 194 | break; 195 | } 196 | return orientation; 197 | } 198 | 199 | - (NSString *)gqTypeForImageData 200 | { 201 | uint8_t c; 202 | [self getBytes:&c length:1]; 203 | switch (c) { 204 | case 0xFF: 205 | return @"image/jpeg"; 206 | case 0x89: 207 | return @"image/png"; 208 | case 0x47: 209 | return @"image/gif"; 210 | case 0x49: 211 | case 0x4D: 212 | return @"image/tiff"; 213 | case 0x52: 214 | // R as RIFF for WEBP 215 | if ([self length] < 12) { 216 | return nil; 217 | } 218 | 219 | NSString *testString = [[NSString alloc] initWithData:[self subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding]; 220 | if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) { 221 | return @"image/webp"; 222 | } 223 | 224 | return nil; 225 | } 226 | return nil; 227 | } 228 | 229 | #ifdef GQ_WEBP 230 | 231 | + (UIImage *)gq_imageWithWebPData:(NSData *)imgData { 232 | if (!imgData) { 233 | return nil; 234 | } 235 | 236 | WebPData webpData; 237 | WebPDataInit(&webpData); 238 | webpData.bytes = imgData.bytes; 239 | webpData.size = imgData.length; 240 | WebPDemuxer *demuxer = WebPDemux(&webpData); 241 | if (!demuxer) { 242 | return nil; 243 | } 244 | 245 | uint32_t flags = WebPDemuxGetI(demuxer, WEBP_FF_FORMAT_FLAGS); 246 | if (!(flags & ANIMATION_FLAG)) { 247 | // for static single webp image 248 | UIImage *staticImage = [self gq_rawWepImageWithData:webpData]; 249 | WebPDemuxDelete(demuxer); 250 | return staticImage; 251 | } 252 | 253 | WebPIterator iter; 254 | if (!WebPDemuxGetFrame(demuxer, 1, &iter)) { 255 | WebPDemuxReleaseIterator(&iter); 256 | WebPDemuxDelete(demuxer); 257 | return nil; 258 | } 259 | 260 | NSMutableArray *images = [NSMutableArray array]; 261 | NSTimeInterval duration = 0; 262 | 263 | do { 264 | UIImage *image; 265 | if (iter.blend_method == WEBP_MUX_BLEND) { 266 | image = [self gq_blendWebpImageWithOriginImage:[images lastObject] iterator:iter]; 267 | } else { 268 | image = [self gq_rawWepImageWithData:iter.fragment]; 269 | } 270 | 271 | if (!image) { 272 | continue; 273 | } 274 | 275 | [images addObject:image]; 276 | duration += iter.duration / 1000.0f; 277 | 278 | } while (WebPDemuxNextFrame(&iter)); 279 | 280 | WebPDemuxReleaseIterator(&iter); 281 | WebPDemuxDelete(demuxer); 282 | 283 | UIImage *finalImage = nil; 284 | 285 | finalImage = [UIImage animatedImageWithImages:images duration:duration]; 286 | 287 | return finalImage; 288 | } 289 | 290 | + (nullable UIImage *)gq_blendWebpImageWithOriginImage:(nullable UIImage *)originImage iterator:(WebPIterator)iter { 291 | if (!originImage) { 292 | return nil; 293 | } 294 | 295 | CGSize size = originImage.size; 296 | CGFloat tmpX = iter.x_offset; 297 | CGFloat tmpY = size.height - iter.height - iter.y_offset; 298 | CGRect imageRect = CGRectMake(tmpX, tmpY, iter.width, iter.height); 299 | 300 | UIImage *image = [self gq_rawWepImageWithData:iter.fragment]; 301 | if (!image) { 302 | return nil; 303 | } 304 | 305 | CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB(); 306 | uint32_t bitmapInfo = iter.has_alpha ? kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast : 0; 307 | CGContextRef blendCanvas = CGBitmapContextCreate(NULL, size.width, size.height, 8, 0, colorSpaceRef, bitmapInfo); 308 | CGContextDrawImage(blendCanvas, CGRectMake(0, 0, size.width, size.height), originImage.CGImage); 309 | CGContextDrawImage(blendCanvas, imageRect, image.CGImage); 310 | CGImageRef newImageRef = CGBitmapContextCreateImage(blendCanvas); 311 | 312 | image = [UIImage imageWithCGImage:newImageRef]; 313 | 314 | CGImageRelease(newImageRef); 315 | CGContextRelease(blendCanvas); 316 | CGColorSpaceRelease(colorSpaceRef); 317 | 318 | return image; 319 | } 320 | 321 | + (nullable UIImage *)gq_rawWepImageWithData:(WebPData)webpData { 322 | WebPDecoderConfig config; 323 | if (!WebPInitDecoderConfig(&config)) { 324 | return nil; 325 | } 326 | 327 | if (WebPGetFeatures(webpData.bytes, webpData.size, &config.input) != VP8_STATUS_OK) { 328 | return nil; 329 | } 330 | 331 | config.output.colorspace = config.input.has_alpha ? MODE_rgbA : MODE_RGB; 332 | config.options.use_threads = 1; 333 | 334 | // Decode the WebP image data into a RGBA value array. 335 | if (WebPDecode(webpData.bytes, webpData.size, &config) != VP8_STATUS_OK) { 336 | return nil; 337 | } 338 | 339 | int width = config.input.width; 340 | int height = config.input.height; 341 | if (config.options.use_scaling) { 342 | width = config.options.scaled_width; 343 | height = config.options.scaled_height; 344 | } 345 | 346 | // Construct a UIImage from the decoded RGBA value array. 347 | CGDataProviderRef provider = 348 | CGDataProviderCreateWithData(NULL, config.output.u.RGBA.rgba, config.output.u.RGBA.size, FreeImageData); 349 | CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB(); 350 | CGBitmapInfo bitmapInfo = config.input.has_alpha ? kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast : 0; 351 | size_t components = config.input.has_alpha ? 4 : 3; 352 | CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault; 353 | CGImageRef imageRef = CGImageCreate(width, height, 8, components * 8, components * width, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent); 354 | 355 | CGColorSpaceRelease(colorSpaceRef); 356 | CGDataProviderRelease(provider); 357 | 358 | UIImage *image = [[UIImage alloc] initWithCGImage:imageRef]; 359 | CGImageRelease(imageRef); 360 | 361 | return image; 362 | } 363 | 364 | #endif 365 | 366 | @end 367 | -------------------------------------------------------------------------------- /GQImageDownloader/GQCategory/UIImage+GQImageDownloader.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+GQImageDownloader.h 3 | // GQImageDownload 4 | // 5 | // Created by 高旗 on 2017/11/23. 6 | // Copyright © 2017年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UIImage (GQImageDownloader) 12 | 13 | #ifdef GQ_WEBP 14 | 15 | /** 16 | WebP文件转换成UIImage 17 | 18 | param filePath 19 | return 20 | */ 21 | + (UIImage *)gq_imageWithWebPFile:(NSString*)filePath; 22 | 23 | /** 24 | WebP图片转换成UIImage 25 | 26 | param imageName 27 | return 28 | */ 29 | + (UIImage *)gq_imageWithWebPImageName:(NSString *)imageName; 30 | 31 | #endif 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /GQImageDownloader/GQCategory/UIImage+GQImageDownloader.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+GQImageDownloader.m 3 | // GQImageDownload 4 | // 5 | // Created by 高旗 on 2017/11/23. 6 | // Copyright © 2017年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import "UIImage+GQImageDownloader.h" 10 | #import "NSData+GQImageDownloader.h" 11 | 12 | @implementation UIImage (GQImageDownloader) 13 | 14 | #ifdef GQ_WEBP 15 | 16 | + (UIImage *)gq_imageWithWebPImageName:(NSString *)imageName { 17 | NSString *filePath = [[NSBundle mainBundle] pathForResource:imageName ofType:@"webp"]; 18 | return [self gq_imageWithWebPFile:filePath]; 19 | } 20 | 21 | + (UIImage*)gq_imageWithWebPFile:(NSString*)filePath { 22 | if (!filePath||![filePath length] ) { 23 | return nil; 24 | } 25 | NSData *imgData = [NSData dataWithContentsOfFile:filePath]; 26 | return [NSData gq_imageWithWebPData:imgData]; 27 | } 28 | 29 | #endif 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /GQImageDownloader/GQImageCache/GQImageCacheManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // GQImageCacheManager.h 3 | // GQImageVideoViewer 4 | // 5 | // Created by 高旗 on 16/9/8. 6 | // Copyright © 2016年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "GQImageDownloaderConst.h" 12 | 13 | @interface GQImageCacheManager : NSObject 14 | 15 | + (GQImageCacheManager *)sharedManager; 16 | 17 | /** 18 | * 获取指定key的图片文件位置 19 | * 20 | * param url 请求的url或key 21 | * 22 | * @return 文件位置 23 | */ 24 | - (NSString *)createImageFilePathWithUrl:(NSString *)url; 25 | 26 | /** 27 | * 图片保存 28 | * 29 | * param image 图片 30 | * param url 请求的url或key 31 | */ 32 | - (void)saveImage:(UIImage*)image withUrl:(NSString*)url; 33 | - (void)saveImage:(UIImage*)image withKey:(NSString*)key; 34 | 35 | /** 36 | * 获取指定图片 37 | * 38 | * param url 请求的url或key 39 | * 40 | * @return image 41 | */ 42 | - (UIImage *)getImageFromCacheWithUrl:(NSString*)url; 43 | - (UIImage *)getImageFromCacheWithKey:(NSString*)key; 44 | 45 | /** 46 | * 图片是否缓存在内存里 47 | * 48 | * param url 请求的url或key 49 | * 50 | * @return YES or NO 51 | */ 52 | - (BOOL)isImageInMemoryCacheWithUrl:(NSString*)url; 53 | 54 | /** 55 | * 清除内存硬盘中的缓存 56 | */ 57 | - (void)clearMemoryCache; 58 | - (void)clearDiskCache; 59 | 60 | /** 61 | * 删除指定的image缓存 62 | * 63 | * param url 请求的url或key 64 | */ 65 | - (void)removeImageFromCacheWithUrl:(NSString *)url; 66 | - (void)removeImageFromCacheWithKey:(NSString *)key; 67 | 68 | 69 | /** 70 | 获取文件缓存总大小 71 | 72 | @return 文件大小 73 | */ 74 | - (NSUInteger)getSize; 75 | 76 | /** 77 | 获取文件总数 78 | 79 | @return 文件数 80 | */ 81 | - (NSUInteger)getDiskCount; 82 | 83 | /** 84 | 删除disk缓存 85 | */ 86 | - (void)clearDisk; 87 | - (void)clearDiskOnCompletion:(GQImageDownloaderNoParamsBlock)completion; 88 | 89 | @end 90 | -------------------------------------------------------------------------------- /GQImageDownloader/GQImageCache/GQImageCacheManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // GQImageCacheManager.m 3 | // GQImageVideoViewer 4 | // 5 | // Created by 高旗 on 16/9/8. 6 | // Copyright © 2016年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import "GQImageCacheManager.h" 10 | #import "GQImageGobalPaths.h" 11 | #import "NSData+GQImageDownloader.h" 12 | 13 | @interface GQImageCacheManager() 14 | { 15 | NSCache *_memoryCache; 16 | NSFileManager *_fileManager; 17 | } 18 | @property (nonatomic, strong) dispatch_queue_t ioDispatchQueue; 19 | 20 | @property (nonatomic, strong) dispatch_group_t ioDispatchGroup; 21 | 22 | @end 23 | 24 | @implementation GQImageCacheManager 25 | 26 | GQOBJECT_SINGLETON_BOILERPLATE(GQImageCacheManager, sharedManager) 27 | #pragma mark - Object lifecycle 28 | 29 | - (void)dealloc 30 | { 31 | _memoryCache = nil; 32 | } 33 | 34 | - (id)init 35 | { 36 | self = [super init]; 37 | if (self) { 38 | [self restore]; 39 | self.ioDispatchGroup = dispatch_group_create(); 40 | self.ioDispatchQueue = dispatch_queue_create("com.ISS.GQImageCacheManager", DISPATCH_QUEUE_SERIAL); 41 | _fileManager = [NSFileManager defaultManager]; 42 | } 43 | return self; 44 | } 45 | 46 | - (void)restore 47 | { 48 | [self registerMemoryWarningNotification]; 49 | _memoryCache = nil; 50 | _memoryCache = [[NSCache alloc] init]; 51 | NSString *path = [self getImageFolder]; 52 | [self createDirectorysAtPath:path]; 53 | } 54 | 55 | #pragma mark - private methods 56 | 57 | - (void)registerMemoryWarningNotification 58 | { 59 | // Subscribe to app events 60 | [[NSNotificationCenter defaultCenter] addObserver:self 61 | selector:@selector(clearMemoryCache) 62 | name:UIApplicationDidReceiveMemoryWarningNotification 63 | object:nil]; 64 | 65 | #ifdef __IPHONE_4_0 66 | UIDevice *device = [UIDevice currentDevice]; 67 | if ([device respondsToSelector:@selector(isMultitaskingSupported)] && device.multitaskingSupported) 68 | { 69 | // When in background, clean memory in order to have less chance to be killed 70 | [[NSNotificationCenter defaultCenter] addObserver:self 71 | selector:@selector(clearMemoryCache) 72 | name:UIApplicationDidEnterBackgroundNotification 73 | object:nil]; 74 | } 75 | #endif 76 | } 77 | 78 | - (NSString*)encodeURL:(NSString *)string 79 | { 80 | #pragma clang diagnostic push 81 | #pragma clang diagnostic ignored "-Wdeprecated" 82 | NSString *newString = (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, CFSTR(":/?#[]@!$ &'()*+,;=\"<>%{}|\\^~`"), CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding))); 83 | #pragma clang diagnostic pop 84 | if (newString) { 85 | return newString; 86 | } 87 | return @""; 88 | } 89 | 90 | - (BOOL)createDirectorysAtPath:(NSString *)path 91 | { 92 | @synchronized(self){ 93 | if (![_fileManager fileExistsAtPath:path]) { 94 | NSError *error = nil; 95 | if (![_fileManager createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&error]) { 96 | return NO; 97 | } 98 | } 99 | } 100 | return YES; 101 | } 102 | 103 | - (NSString *)getImageFolder 104 | { 105 | return GQPathForCacheResource(@"imageViewerCache"); 106 | } 107 | 108 | - (NSString *)getPathByFileName:(NSString *)fileName 109 | { 110 | return [NSString stringWithFormat:@"%@/%@",[self getImageFolder],fileName]; 111 | } 112 | 113 | - (NSString*)getKeyFromUrl:(NSString*)url 114 | { 115 | return [self encodeURL:url]; 116 | } 117 | 118 | - (NSString *)createImageFilePathWithUrl:(NSString *)url 119 | { 120 | NSString *key = [self getKeyFromUrl:url]; 121 | return [self getPathByFileName:key]; 122 | } 123 | 124 | - (void)saveToMemory:(UIImage*)image forKey:(NSString*)key 125 | { 126 | @synchronized (_memoryCache) { 127 | if (image) { 128 | [_memoryCache setObject:image forKey:key]; 129 | } 130 | } 131 | } 132 | 133 | - (UIImage*)getImageFromMemoryCache:(NSString*)key 134 | { 135 | return [_memoryCache objectForKey:key]; 136 | } 137 | 138 | - (BOOL)isImageInMemoryCacheWithUrl:(NSString*)url 139 | { 140 | return [self isImageInMemoryCache:[self getKeyFromUrl:url]]; 141 | } 142 | 143 | - (BOOL)isImageInMemoryCache:(NSString*)key 144 | { 145 | return (nil != [self getImageFromMemoryCache:key]); 146 | } 147 | 148 | #pragma mark - public methods 149 | - (void)saveImage:(UIImage*)image withUrl:(NSString*)url 150 | { 151 | [self saveImage:image withKey:[self getKeyFromUrl:url]]; 152 | } 153 | 154 | - (void)saveImage:(UIImage*)image withKey:(NSString*)key 155 | { 156 | [self createDirectorysAtPath:[self getImageFolder]]; 157 | dispatch_group_async(self.ioDispatchGroup, self.ioDispatchQueue, ^{ 158 | @try { 159 | NSData* imageData = UIImageJPEGRepresentation(image, 1.0); 160 | NSString *imageFilePath = [self getPathByFileName:key]; 161 | if (![_fileManager fileExistsAtPath:imageFilePath]) { 162 | NSURL *fileURL = [NSURL fileURLWithPath:imageFilePath]; 163 | [_fileManager createFileAtPath:imageFilePath contents:imageData attributes:nil]; 164 | NSError *error = nil; 165 | [fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:&error]; 166 | } 167 | }@catch (NSException *exception) { 168 | 169 | } 170 | }); 171 | [self saveToMemory:image forKey:key]; 172 | } 173 | 174 | - (UIImage*)getImageFromCacheWithUrl:(NSString*)url 175 | { 176 | return [self getImageFromCacheWithKey:[self getKeyFromUrl:url]]; 177 | } 178 | 179 | - (UIImage*)getImageFromCacheWithKey:(NSString*)key 180 | { 181 | if ([self isImageInMemoryCache:key]) { 182 | return [self getImageFromMemoryCache:key]; 183 | }else{ 184 | NSString *imageFilePath = [self getPathByFileName:key]; 185 | NSData *data = [NSData dataWithContentsOfFile:imageFilePath]; 186 | UIImage* image = [data gqImageWithData];; 187 | if (image) { 188 | [self saveToMemory:image forKey:key]; 189 | } 190 | return image; 191 | } 192 | } 193 | 194 | - (void)clearDiskCache 195 | { 196 | NSString *imageFolderPath = [self getImageFolder]; 197 | [_fileManager removeItemAtPath:imageFolderPath error:nil]; 198 | [self createDirectorysAtPath:imageFolderPath]; 199 | } 200 | 201 | - (void)clearMemoryCache 202 | { 203 | [_memoryCache removeAllObjects]; 204 | _memoryCache = nil; 205 | _memoryCache = [[NSCache alloc] init]; 206 | } 207 | 208 | - (void)removeImageFromCacheWithUrl:(NSString *)url 209 | { 210 | [self removeImageFromCacheWithKey:[self getKeyFromUrl:url]]; 211 | } 212 | 213 | - (void)removeImageFromCacheWithKey:(NSString *)key 214 | { 215 | if ([self isImageInMemoryCache:key]) { 216 | [_memoryCache removeObjectForKey:key]; 217 | } 218 | NSString *imageFilePath = [self getPathByFileName:key]; 219 | if ([_fileManager fileExistsAtPath:imageFilePath]) { 220 | [_fileManager removeItemAtPath:imageFilePath error:nil]; 221 | } 222 | } 223 | 224 | - (NSUInteger)getSize { 225 | __block NSUInteger size = 0; 226 | dispatch_sync(self.ioDispatchQueue, ^{ 227 | NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtPath:[self getImageFolder]]; 228 | for (NSString *fileName in fileEnumerator) { 229 | NSString *filePath = [self.getImageFolder stringByAppendingPathComponent:fileName]; 230 | NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil]; 231 | size += [attrs fileSize]; 232 | } 233 | }); 234 | return size; 235 | } 236 | 237 | - (NSUInteger)getDiskCount { 238 | __block NSUInteger count = 0; 239 | dispatch_sync(self.ioDispatchQueue, ^{ 240 | NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtPath:[self getImageFolder]]; 241 | count = [[fileEnumerator allObjects] count]; 242 | }); 243 | return count; 244 | } 245 | 246 | - (void)clearDisk { 247 | [self clearDiskOnCompletion:nil]; 248 | } 249 | 250 | - (void)clearDiskOnCompletion:(GQImageDownloaderNoParamsBlock)completion 251 | { 252 | dispatch_async(self.ioDispatchQueue, ^{ 253 | [_fileManager removeItemAtPath:[self getImageFolder] error:nil]; 254 | [self createDirectorysAtPath:[self getImageFolder]]; 255 | if (completion) { 256 | dispatch_async(dispatch_get_main_queue(), ^{ 257 | completion(); 258 | }); 259 | } 260 | }); 261 | } 262 | 263 | @end 264 | -------------------------------------------------------------------------------- /GQImageDownloader/GQImageCache/GQImageGobalPaths.h: -------------------------------------------------------------------------------- 1 | // 2 | // GQGobalPaths.h 3 | // GQImageVideoViewer 4 | // 5 | // Created by 高旗 on 16/9/8. 6 | // Copyright © 2016年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /** 12 | * 设置全局bundle,默认为main bundle, 如多主题可以使用 13 | */ 14 | void GQSetDefaultBundle(NSBundle* bundle); 15 | 16 | /** 17 | * 返回全局默认bundle 18 | */ 19 | NSBundle *GQGetDefaultBundle(void); 20 | 21 | /** 22 | * 返回bundle资源路径 23 | */ 24 | NSString *GQPathForBundleResource(NSString* relativePath); 25 | 26 | /** 27 | * 返回Documents资源路径 28 | */ 29 | NSString *GQPathForDocumentsResource(NSString* relativePath); 30 | 31 | /** 32 | * 返回Cache资源路径 33 | */ 34 | NSString *GQPathForCacheResource(NSString* relativePath); 35 | -------------------------------------------------------------------------------- /GQImageDownloader/GQImageCache/GQImageGobalPaths.m: -------------------------------------------------------------------------------- 1 | // 2 | // GQGobalPaths.m 3 | // GQImageVideoViewer 4 | // 5 | // Created by 高旗 on 16/9/8. 6 | // Copyright © 2016年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import "GQImageGobalPaths.h" 10 | 11 | static NSBundle* globalBundle = nil; 12 | 13 | /** 14 | * 设置全局bundle,默认为main bundle, 如多主题可以使用 15 | */ 16 | void GQSetDefaultBundle(NSBundle* bundle) 17 | { 18 | globalBundle = bundle; 19 | } 20 | 21 | /** 22 | * 返回全局默认bundle 23 | */ 24 | NSBundle *GQGetDefaultBundle() 25 | { 26 | return (nil != globalBundle) ? globalBundle : [NSBundle mainBundle]; 27 | } 28 | 29 | /** 30 | * 返回bundle资源路径 31 | */ 32 | NSString *GQPathForBundleResource(NSString* relativePath) 33 | { 34 | NSString* resourcePath = [GQGetDefaultBundle() resourcePath]; 35 | return [resourcePath stringByAppendingPathComponent:relativePath]; 36 | } 37 | 38 | /** 39 | * 返回Documents资源路径 40 | */ 41 | NSString *GQPathForDocumentsResource(NSString* relativePath) 42 | { 43 | static NSString* documentsPath = nil; 44 | if (nil == documentsPath) { 45 | NSArray* dirs = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 46 | documentsPath = dirs[0]; 47 | } 48 | return [documentsPath stringByAppendingPathComponent:relativePath]; 49 | } 50 | 51 | /** 52 | * 返回Cache资源路径 53 | */ 54 | NSString *GQPathForCacheResource(NSString* relativePath) 55 | { 56 | static NSString* documentsPath = nil; 57 | if (nil == documentsPath) { 58 | NSArray* dirs = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); 59 | documentsPath = dirs[0]; 60 | } 61 | return [documentsPath stringByAppendingPathComponent:relativePath]; 62 | } 63 | -------------------------------------------------------------------------------- /GQImageDownloader/GQImageDownloaderOperation/GQImageDownloaderBaseOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // GQOperation.h 3 | // GQNetWorkDemo 4 | // 5 | // Created by 高旗 on 16/10/23. 6 | // Copyright © 2016年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "GQImageDownloaderOperationDelegate.h" 12 | 13 | @class GQImageDownloaderBaseOperation; 14 | 15 | typedef void (^GQImageDownloaderChangeHandler) (float progress); 16 | typedef void (^GQImageDownloaderCancelHandler) (void); 17 | typedef void (^GQImageDownloaderCompletionHandler)(GQImageDownloaderBaseOperation *urlOperation,BOOL requestSuccess, NSError *error); 18 | 19 | enum { 20 | GQImageDownloaderStateReady = 0, 21 | GQImageDownloaderStateExecuting, 22 | GQImageDownloaderStateFinished 23 | }; 24 | typedef NSUInteger GQImageDownloaderState; 25 | 26 | @interface GQImageDownloaderBaseOperation : NSOperation 27 | 28 | @property (nonatomic, strong) NSURLRequest *operationRequest; 29 | @property (nonatomic, strong) NSData *responseData; 30 | @property (nonatomic, strong) NSHTTPURLResponse *operationURLResponse; 31 | @property (nonatomic, strong) NSFileHandle *operationFileHandle; 32 | 33 | @property (nonatomic, strong) NSString *operationSavePath; 34 | 35 | @property (nonatomic, strong) NSData *certificateData; 36 | 37 | @property (nonatomic, strong) NSURLSession *operationSession NS_AVAILABLE_IOS(8_0); 38 | 39 | @property (nonatomic, strong) NSMutableData *operationData; 40 | @property (nonatomic, assign) CFRunLoopRef operationRunLoop; 41 | @property (nonatomic, readwrite) UIBackgroundTaskIdentifier backgroundTaskIdentifier; 42 | 43 | @property (nonatomic, readwrite) GQImageDownloaderState state; 44 | @property (nonatomic, readwrite) float expectedContentLength; 45 | @property (nonatomic, readwrite) float receivedContentLength; 46 | 47 | @property (nonatomic, copy) GQImageDownloaderChangeHandler operationProgressBlock; 48 | @property (nonatomic, copy) GQImageDownloaderCancelHandler operationCancelBlock; 49 | @property (nonatomic, copy) GQImageDownloaderCompletionHandler operationCompletionBlock; 50 | 51 | - (GQImageDownloaderBaseOperation *)initWithURLRequest:(NSURLRequest *)urlRequest 52 | progress:(GQImageDownloaderChangeHandler)onProgressBlock 53 | cancel:(GQImageDownloaderCancelHandler)onCancelBlock 54 | completion:(GQImageDownloaderCompletionHandler)onCompletionBlock; 55 | 56 | - (GQImageDownloaderBaseOperation *)initWithURLRequest:(NSURLRequest *)urlRequest 57 | operationSession:(NSURLSession *)operationSession 58 | progress:(GQImageDownloaderChangeHandler)onProgressBlock 59 | cancel:(GQImageDownloaderCancelHandler)onCancelBlock 60 | completion:(GQImageDownloaderCompletionHandler)onCompletionBlock NS_AVAILABLE_IOS(8_0); 61 | 62 | - (void)finish; 63 | - (void)handleResponseData:(NSData *)data; 64 | - (void)callCompletionBlockWithResponse:(NSData *)response 65 | requestSuccess:(BOOL)requestSuccess 66 | error:(NSError *)error; 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /GQImageDownloader/GQImageDownloaderOperation/GQImageDownloaderBaseOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // GQOperation.m 3 | // GQNetWorkDemo 4 | // 5 | // Created by 高旗 on 16/10/23. 6 | // Copyright © 2016年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import "GQImageDownloaderBaseOperation.h" 10 | 11 | @interface GQImageDownloaderBaseOperation() 12 | 13 | @end 14 | 15 | @implementation GQImageDownloaderBaseOperation 16 | 17 | @synthesize state = _state; 18 | 19 | static NSInteger GQHTTPRequestTaskCount = 0; 20 | 21 | - (void)dealloc 22 | { 23 | self.operationRequest = nil; 24 | self.operationURLResponse = nil; 25 | self.operationData = nil; 26 | self.responseData = nil; 27 | } 28 | 29 | - (GQImageDownloaderBaseOperation *)initWithURLRequest:(NSURLRequest *)urlRequest 30 | progress:(GQImageDownloaderChangeHandler)onProgressBlock 31 | cancel:(GQImageDownloaderCancelHandler)onCancelBlock 32 | completion:(GQImageDownloaderCompletionHandler)onCompletionBlock; 33 | 34 | { 35 | self = [super init]; 36 | self.operationData = [[NSMutableData alloc] init]; 37 | self.operationRequest = urlRequest; 38 | 39 | if (onProgressBlock) { 40 | _operationProgressBlock = [onProgressBlock copy]; 41 | } 42 | if (onCancelBlock) { 43 | _operationCancelBlock = [onCancelBlock copy]; 44 | } 45 | if (onCompletionBlock) { 46 | _operationCompletionBlock = [onCompletionBlock copy]; 47 | } 48 | return self; 49 | } 50 | 51 | - (GQImageDownloaderBaseOperation *)initWithURLRequest:(NSURLRequest *)urlRequest 52 | operationSession:(NSURLSession *)operationSession 53 | progress:(GQImageDownloaderChangeHandler)onProgressBlock 54 | cancel:(GQImageDownloaderCancelHandler)onCancelBlock 55 | completion:(GQImageDownloaderCompletionHandler)onCompletionBlock { 56 | self = [super init]; 57 | self.operationData = [[NSMutableData alloc] init]; 58 | self.operationRequest = urlRequest; 59 | self.operationSession = operationSession; 60 | 61 | if (onProgressBlock) { 62 | _operationProgressBlock = [onProgressBlock copy]; 63 | } 64 | if (onCancelBlock) { 65 | _operationCancelBlock = [onCancelBlock copy]; 66 | } 67 | if (onCompletionBlock) { 68 | _operationCompletionBlock = [onCompletionBlock copy]; 69 | } 70 | return self; 71 | } 72 | 73 | - (void)cancel 74 | { 75 | if(![self isFinished]){ 76 | return; 77 | } 78 | [super cancel]; 79 | [self finish]; 80 | } 81 | 82 | - (BOOL)isConcurrent 83 | { 84 | return YES; 85 | } 86 | 87 | - (BOOL)isFinished 88 | { 89 | return self.state == GQImageDownloaderStateFinished; 90 | } 91 | 92 | - (BOOL)isExecuting 93 | { 94 | return self.state == GQImageDownloaderStateExecuting; 95 | } 96 | 97 | - (GQImageDownloaderState)state 98 | { 99 | @synchronized(self) { 100 | return _state; 101 | } 102 | } 103 | 104 | - (void)setState:(GQImageDownloaderState)newState 105 | { 106 | @synchronized(self) { 107 | [self willChangeValueForKey:@"state"]; 108 | _state = newState; 109 | [self didChangeValueForKey:@"state"]; 110 | } 111 | } 112 | 113 | - (void)main 114 | { 115 | dispatch_async(dispatch_get_main_queue(), ^{ 116 | [self increaseSVHTTPRequestTaskCount]; 117 | }); 118 | 119 | [self willChangeValueForKey:@"isExecuting"]; 120 | self.state = GQImageDownloaderStateExecuting; 121 | [self didChangeValueForKey:@"isExecuting"]; 122 | 123 | #if TARGET_OS_IPHONE 124 | // all requests should complete and run completion block unless we explicitely cancel them. 125 | self.backgroundTaskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ 126 | if(self.backgroundTaskIdentifier != UIBackgroundTaskInvalid) { 127 | [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskIdentifier]; 128 | self.backgroundTaskIdentifier = UIBackgroundTaskInvalid; 129 | } 130 | }]; 131 | #endif 132 | if(self.operationSavePath) { 133 | [[NSFileManager defaultManager] createFileAtPath:self.operationSavePath contents:nil attributes:nil]; 134 | self.operationFileHandle = [NSFileHandle fileHandleForWritingAtPath:self.operationSavePath]; 135 | } 136 | 137 | if ([[[UIDevice currentDevice] systemVersion] floatValue] < 8.0) { 138 | NSOperationQueue *currentQueue = [NSOperationQueue currentQueue]; 139 | BOOL inBackgroundAndInOperationQueue = (currentQueue != nil && currentQueue != [NSOperationQueue mainQueue]); 140 | 141 | if(inBackgroundAndInOperationQueue) { 142 | self.operationRunLoop = CFRunLoopGetCurrent(); 143 | BOOL isWaiting = CFRunLoopIsWaiting(self.operationRunLoop); 144 | isWaiting?CFRunLoopWakeUp(self.operationRunLoop):CFRunLoopRun(); 145 | } 146 | } 147 | } 148 | 149 | - (void)finish 150 | { 151 | [self decreaseSVHTTPRequestTaskCount]; 152 | 153 | #if TARGET_OS_IPHONE 154 | if(self.backgroundTaskIdentifier != UIBackgroundTaskInvalid) { 155 | [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskIdentifier]; 156 | self.backgroundTaskIdentifier = UIBackgroundTaskInvalid; 157 | } 158 | #endif 159 | 160 | [self willChangeValueForKey:@"isExecuting"]; 161 | [self willChangeValueForKey:@"isFinished"]; 162 | self.state = GQImageDownloaderStateFinished; 163 | [self didChangeValueForKey:@"isExecuting"]; 164 | [self didChangeValueForKey:@"isFinished"]; 165 | } 166 | 167 | - (void)increaseSVHTTPRequestTaskCount 168 | { 169 | GQHTTPRequestTaskCount++; 170 | [self toggleNetworkActivityIndicator]; 171 | } 172 | 173 | - (void)decreaseSVHTTPRequestTaskCount 174 | { 175 | GQHTTPRequestTaskCount = MAX(0, GQHTTPRequestTaskCount-1); 176 | [self toggleNetworkActivityIndicator]; 177 | } 178 | 179 | - (void)toggleNetworkActivityIndicator 180 | { 181 | #if TARGET_OS_IPHONE 182 | dispatch_async(dispatch_get_main_queue(), ^{ 183 | [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:(GQHTTPRequestTaskCount > 0)]; 184 | }); 185 | #endif 186 | } 187 | 188 | #pragma mark -- HandleResponseData 189 | 190 | - (void)handleResponseData:(NSData *)data{ 191 | [self.operationData appendData:data]; 192 | 193 | if(self.operationProgressBlock) { 194 | //If its -1 that means the header does not have the content size value 195 | if(self.expectedContentLength != -1) { 196 | self.receivedContentLength += data.length; 197 | self.operationProgressBlock(self.receivedContentLength/self.expectedContentLength); 198 | } else { 199 | //we dont know the full size so always return -1 as the progress 200 | self.operationProgressBlock(-1); 201 | } 202 | } 203 | } 204 | 205 | -(void)callCompletionBlockWithResponse:(NSData *)response 206 | requestSuccess:(BOOL)requestSuccess 207 | error:(NSError *)error 208 | { 209 | if ([[[UIDevice currentDevice] systemVersion] floatValue] < 8.0) { 210 | if(self.operationRunLoop){ 211 | CFRunLoopStop(self.operationRunLoop); 212 | } 213 | } 214 | dispatch_async(dispatch_get_main_queue(), ^{ 215 | self.responseData = self.operationData; 216 | NSError *serverError = error; 217 | if(!serverError) { 218 | serverError = [NSError errorWithDomain:NSURLErrorDomain 219 | code:NSURLErrorBadServerResponse 220 | userInfo:nil]; 221 | } 222 | 223 | if(self.operationCompletionBlock && self.state != GQImageDownloaderStateFinished) { 224 | self.operationCompletionBlock(self,requestSuccess,requestSuccess?nil:serverError); 225 | } 226 | [self finish]; 227 | }); 228 | } 229 | 230 | @end 231 | -------------------------------------------------------------------------------- /GQImageDownloader/GQImageDownloaderOperation/GQImageDownloaderOperationDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // GQImageDownloaderOperationDelegate.h 3 | // GQImageDownload 4 | // 5 | // Created by 高旗 on 2017/11/23. 6 | // Copyright © 2017年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol GQImageDownloaderOperationDelegate 12 | 13 | - (void)cancel; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /GQImageDownloader/GQImageDownloaderOperation/GQImageDownloaderOperationManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // GQImageDownloaderOperationManager.h 3 | // GQImageDownload 4 | // 5 | // Created by 高旗 on 2017/11/23. 6 | // Copyright © 2017年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "GQImageDataDownloader.h" 11 | 12 | @interface GQImageDownloaderOperationManager : NSObject 13 | 14 | + (instancetype)sharedManager; 15 | 16 | - (id)loadWithURL:(NSURL *)url 17 | withURLRequestClassName:(NSString *)className 18 | progress:(GQImageDownloaderProgressBlock)progress 19 | complete:(GQImageDownloaderCompleteBlock)complete; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /GQImageDownloader/GQImageDownloaderOperation/GQImageDownloaderOperationManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // GQImageDownloaderOperationManager.m 3 | // GQImageDownload 4 | // 5 | // Created by 高旗 on 2017/11/23. 6 | // Copyright © 2017年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import "GQImageDownloaderOperationManager.h" 10 | #import "GQImageDownloaderConst.h" 11 | #import "GQImageCacheManager.h" 12 | 13 | @interface GQImageDownloaderOperationManager() 14 | #if !OS_OBJECT_USE_OBJC 15 | @property (nonatomic, assign) dispatch_queue_t saveDataDispatchQueue; 16 | @property (nonatomic, assign) dispatch_group_t saveDataDispatchGroup; 17 | #else 18 | @property (nonatomic, strong) dispatch_queue_t saveDataDispatchQueue; 19 | @property (nonatomic, strong) dispatch_group_t saveDataDispatchGroup; 20 | #endif 21 | 22 | @end 23 | 24 | @implementation GQImageDownloaderOperationManager 25 | 26 | GQOBJECT_SINGLETON_BOILERPLATE(GQImageDownloaderOperationManager, sharedManager) 27 | 28 | - (id)loadWithURL:(NSURL *)url 29 | withURLRequestClassName:(NSString *)className 30 | progress:(GQImageDownloaderProgressBlock)progressBlock 31 | complete:(GQImageDownloaderCompleteBlock)completeBlock { 32 | [[GQImageDataDownloader sharedDownloadManager] setURLRequestClass:NSClassFromString(className)]; 33 | __block UIImage *image = nil; 34 | if(![[GQImageCacheManager sharedManager] isImageInMemoryCacheWithUrl:url.absoluteString]){ 35 | id operation = [[GQImageDataDownloader sharedDownloadManager] 36 | downloadWithURL:url 37 | progress:^(CGFloat progress) { 38 | dispatch_main_async_safe(^{ 39 | if (progress) { 40 | progressBlock(progress); 41 | } 42 | }); 43 | } complete:^(UIImage *image, NSURL *url, NSError *error) { 44 | [[GQImageCacheManager sharedManager] saveImage:image withUrl:url.absoluteString]; 45 | dispatch_main_async_safe(^{ 46 | if (completeBlock) { 47 | completeBlock(image, url ,error); 48 | } 49 | }); 50 | }]; 51 | return operation; 52 | }else{ 53 | dispatch_group_async(dispatch_group_create(), dispatch_queue_create("com.ISS.GQImageCacheManager", DISPATCH_QUEUE_SERIAL), ^{ 54 | image = [[GQImageCacheManager sharedManager] getImageFromCacheWithUrl:url.absoluteString]; 55 | dispatch_main_async_safe(^{ 56 | completeBlock(image,url,nil); 57 | }); 58 | }); 59 | return nil; 60 | } 61 | } 62 | 63 | @end 64 | -------------------------------------------------------------------------------- /GQImageDownloader/GQImageDownloaderOperation/GQImageDownloaderSessionManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // GQImageDownloaderSessionManager.h 3 | // GQImageDownloaderDemo 4 | // 5 | // Created by 高旗 on 2017/11/24. 6 | // Copyright © 2017年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_CLASS_AVAILABLE(NSURLSESSION_AVAILABLE, 8_0) 12 | @interface GQImageDownloaderSessionManager : NSObject 13 | 14 | - (instancetype)initWithOperationQueue:(NSOperationQueue *)operationQueue; 15 | 16 | @property (strong, nonatomic) NSURLSession *session; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /GQImageDownloader/GQImageDownloaderOperation/GQImageDownloaderSessionManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // GQImageDownloaderSessionManager.m 3 | // GQImageDownloaderDemo 4 | // 5 | // Created by 高旗 on 2017/11/24. 6 | // Copyright © 2017年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import "GQImageDownloaderSessionManager.h" 10 | 11 | #import "GQImageDownloaderSessionOperation.h" 12 | 13 | @interface GQImageDownloaderSessionManager() 14 | 15 | @property (nonatomic, weak) NSOperationQueue *operationQueue; 16 | 17 | @end 18 | 19 | @implementation GQImageDownloaderSessionManager 20 | 21 | #pragma amrk -- life cycle 22 | 23 | - (instancetype) initWithOperationQueue:(NSOperationQueue *)operationQueue { 24 | self = [super init]; 25 | if (self) { 26 | self.operationQueue = operationQueue; 27 | NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; 28 | self.session = [NSURLSession sessionWithConfiguration:sessionConfig 29 | delegate:self 30 | delegateQueue:nil]; 31 | } 32 | return self; 33 | } 34 | 35 | - (void)dealloc { 36 | [self.session invalidateAndCancel]; 37 | self.session = nil; 38 | } 39 | 40 | #pragma mark Helper methods 41 | 42 | - (GQImageDownloaderSessionOperation *)operationWithTask:(NSURLSessionTask *)task { 43 | GQImageDownloaderSessionOperation *returnOperation = nil; 44 | for (GQImageDownloaderSessionOperation *operation in self.operationQueue.operations) { 45 | if (operation.operationSessionTask.taskIdentifier == task.taskIdentifier) { 46 | returnOperation = operation; 47 | break; 48 | } 49 | } 50 | return returnOperation; 51 | } 52 | 53 | #pragma mark -- NSURLSessionDelegate NSURLSessionTaskDelegate 54 | 55 | - (void)URLSession:(NSURLSession *)session 56 | task:(NSURLSessionTask *)task 57 | willPerformHTTPRedirection:(NSHTTPURLResponse *)response 58 | newRequest:(NSURLRequest *)request 59 | completionHandler:(void (^)(NSURLRequest *))completionHandler { 60 | 61 | GQImageDownloaderSessionOperation *dataOperation = [self operationWithTask:task]; 62 | 63 | [dataOperation URLSession:session task:task willPerformHTTPRedirection:response newRequest:request completionHandler:completionHandler]; 64 | } 65 | 66 | - (void)URLSession:(NSURLSession *)session 67 | dataTask:(NSURLSessionDataTask *)dataTask 68 | didReceiveResponse:(NSURLResponse *)response 69 | completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { 70 | 71 | GQImageDownloaderSessionOperation *dataOperation = [self operationWithTask:dataTask]; 72 | 73 | [dataOperation URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler]; 74 | } 75 | 76 | - (void)URLSession:(NSURLSession *)session 77 | task:(NSURLSessionTask *)task 78 | needNewBodyStream:(void (^)(NSInputStream * bodyStream))completionHandler { 79 | GQImageDownloaderSessionOperation *dataOperation = [self operationWithTask:task]; 80 | 81 | [dataOperation URLSession:session task:task needNewBodyStream:completionHandler]; 82 | } 83 | 84 | - (void)URLSession:(NSURLSession *)session 85 | dataTask:(NSURLSessionDataTask *)dataTask 86 | didReceiveData:(NSData *)data { 87 | 88 | GQImageDownloaderSessionOperation *dataOperation = [self operationWithTask:dataTask]; 89 | 90 | [dataOperation URLSession:session dataTask:dataTask didReceiveData:data]; 91 | } 92 | 93 | - (void)URLSession:(NSURLSession *)session 94 | dataTask:(NSURLSessionDataTask *)dataTask 95 | willCacheResponse:(NSCachedURLResponse *)proposedResponse 96 | completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler { 97 | 98 | GQImageDownloaderSessionOperation *dataOperation = [self operationWithTask:dataTask]; 99 | 100 | [dataOperation URLSession:session dataTask:dataTask willCacheResponse:proposedResponse completionHandler:completionHandler]; 101 | } 102 | 103 | - (void)URLSession:(NSURLSession *)session 104 | task:(NSURLSessionTask *)task 105 | didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge 106 | completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { 107 | 108 | GQImageDownloaderSessionOperation *dataOperation = [self operationWithTask:task]; 109 | 110 | [dataOperation URLSession:session task:task didReceiveChallenge:challenge completionHandler:completionHandler]; 111 | } 112 | 113 | - (void)URLSession:(NSURLSession *)session 114 | task:(NSURLSessionTask *)task 115 | didCompleteWithError:(NSError *)error { 116 | GQImageDownloaderSessionOperation *dataOperation = [self operationWithTask:task]; 117 | 118 | [dataOperation URLSession:session task:task didCompleteWithError:error]; 119 | } 120 | 121 | @end 122 | -------------------------------------------------------------------------------- /GQImageDownloader/GQImageDownloaderOperation/GQImageDownloaderSessionOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // GQSessionOperation.h 3 | // GQNetWorkDemo 4 | // 5 | // Created by 高旗 on 16/10/23. 6 | // Copyright © 2016年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import "GQImageDownloaderBaseOperation.h" 10 | 11 | NS_CLASS_AVAILABLE(NSURLSESSION_AVAILABLE, 8_0) 12 | @interface GQImageDownloaderSessionOperation : GQImageDownloaderBaseOperation 13 | 14 | @property (nonatomic, strong) NSURLSessionDataTask *operationSessionTask; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /GQImageDownloader/GQImageDownloaderOperation/GQImageDownloaderSessionOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // GQSessionOperation.m 3 | // GQNetWorkDemo 4 | // 5 | // Created by 高旗 on 16/10/23. 6 | // Copyright © 2016年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import "GQImageDownloaderSessionOperation.h" 10 | 11 | @implementation GQImageDownloaderSessionOperation 12 | 13 | - (void)dealloc 14 | { 15 | self.operationSessionTask = nil; 16 | } 17 | 18 | - (instancetype)initWithURLRequest:(NSURLRequest *)urlRequest 19 | progress:(GQImageDownloaderChangeHandler)onProgressBlock 20 | cancel:(GQImageDownloaderCancelHandler)onCancelBlock 21 | completion:(GQImageDownloaderCompletionHandler)onCompletionBlock 22 | { 23 | self = [super initWithURLRequest:urlRequest 24 | progress:onProgressBlock 25 | cancel:onCancelBlock 26 | completion:onCompletionBlock]; 27 | return self; 28 | } 29 | 30 | - (void)finish 31 | { 32 | [self.operationSessionTask cancel]; 33 | [super finish]; 34 | } 35 | 36 | - (void)main 37 | { 38 | [super main]; 39 | 40 | self.operationSessionTask = [self.operationSession dataTaskWithRequest:self.operationRequest]; 41 | [self.operationSessionTask resume]; 42 | } 43 | 44 | #pragma mark -- NSURLSessionDelegate NSURLSessionTaskDelegate 45 | 46 | - (void)URLSession:(NSURLSession *)session 47 | task:(NSURLSessionTask *)task 48 | didCompleteWithError:(NSError *)error 49 | { 50 | if (error) { 51 | [self callCompletionBlockWithResponse:nil requestSuccess:NO error:error]; 52 | }else{ 53 | [self callCompletionBlockWithResponse:nil requestSuccess:YES error:nil]; 54 | } 55 | } 56 | 57 | - (void)URLSession:(NSURLSession *)session 58 | dataTask:(NSURLSessionDataTask *)dataTask 59 | didReceiveResponse:(NSURLResponse *)response 60 | completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler 61 | { 62 | self.expectedContentLength = response.expectedContentLength; 63 | 64 | self.receivedContentLength = 0; 65 | 66 | self.operationURLResponse = (NSHTTPURLResponse *)response; 67 | 68 | NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow; 69 | 70 | if (completionHandler) { 71 | completionHandler(disposition); 72 | } 73 | } 74 | 75 | - (void)URLSession:(NSURLSession *)session 76 | task:(NSURLSessionTask *)task 77 | willPerformHTTPRedirection:(NSHTTPURLResponse *)response 78 | newRequest:(NSURLRequest *)request 79 | completionHandler:(void (^)(NSURLRequest *))completionHandler 80 | { 81 | NSURLRequest *redirectionRequest = request; 82 | 83 | if (completionHandler) { 84 | completionHandler(redirectionRequest); 85 | } 86 | } 87 | 88 | - (void)URLSession:(NSURLSession *)session 89 | task:(NSURLSessionTask *)task 90 | didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge 91 | completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * credential))completionHandler 92 | { 93 | NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling; 94 | NSURLCredential *credential = nil; 95 | if (challenge.previousFailureCount > 0) 96 | { 97 | disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge; 98 | } 99 | if (completionHandler) { 100 | completionHandler(disposition, credential); 101 | } 102 | } 103 | 104 | - (void)URLSession:(NSURLSession *)session 105 | task:(NSURLSessionTask *)task 106 | needNewBodyStream:(void (^)(NSInputStream * bodyStream))completionHandler 107 | { 108 | NSInputStream *inputStream = nil; 109 | if (task.originalRequest.HTTPBodyStream && [task.originalRequest.HTTPBodyStream conformsToProtocol:@protocol(NSCopying)]) { 110 | inputStream = [task.originalRequest.HTTPBodyStream copy]; 111 | } 112 | if (completionHandler) { 113 | completionHandler(inputStream); 114 | } 115 | } 116 | 117 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask 118 | didReceiveData:(NSData *)data 119 | { 120 | [self handleResponseData:data]; 121 | } 122 | 123 | - (void)URLSession:(NSURLSession *)session 124 | dataTask:(NSURLSessionDataTask *)dataTask 125 | willCacheResponse:(NSCachedURLResponse *)proposedResponse 126 | completionHandler:(void (^)(NSCachedURLResponse * cachedResponse))completionHandler 127 | { 128 | NSCachedURLResponse *cachedResponse = proposedResponse; 129 | 130 | if (completionHandler) { 131 | completionHandler(cachedResponse); 132 | } 133 | } 134 | 135 | @end 136 | -------------------------------------------------------------------------------- /GQImageDownloader/GQImageDownloaderOperation/GQImageDownloaderURLConnectionOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // GQImageDownloaderURLConnectionOperation.h 3 | // GQNetWorkDemo 4 | // 5 | // Created by 高旗 on 16/5/27. 6 | // Copyright © 2016年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import "GQImageDownloaderBaseOperation.h" 10 | 11 | @interface GQImageDownloaderURLConnectionOperation : GQImageDownloaderBaseOperation 12 | 13 | @property (nonatomic, strong) NSURLConnection *operationConnection; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /GQImageDownloader/GQImageDownloaderOperation/GQImageDownloaderURLConnectionOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // GQImageDownloaderURLConnectionOperation.m 3 | // GQNetWorkDemo 4 | // 5 | // Created by 高旗 on 16/5/27. 6 | // Copyright © 2016年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import "GQImageDownloaderURLConnectionOperation.h" 10 | 11 | @interface GQImageDownloaderURLConnectionOperation() 12 | 13 | @end 14 | 15 | @implementation GQImageDownloaderURLConnectionOperation 16 | 17 | - (void)dealloc 18 | { 19 | self.operationConnection = nil; 20 | } 21 | 22 | - (instancetype)initWithURLRequest:(NSURLRequest *)urlRequest 23 | progress:(GQImageDownloaderChangeHandler)onProgressBlock 24 | cancel:(GQImageDownloaderCancelHandler)onCancelBlock 25 | completion:(GQImageDownloaderCompletionHandler)onCompletionBlock 26 | { 27 | self = [super initWithURLRequest:urlRequest 28 | progress:onProgressBlock 29 | cancel:onCancelBlock 30 | completion:onCompletionBlock]; 31 | return self; 32 | } 33 | 34 | - (void)finish 35 | { 36 | [super finish]; 37 | [self.operationConnection cancel]; 38 | self.operationConnection = nil; 39 | } 40 | 41 | - (void)main 42 | { 43 | [super main]; 44 | NSOperationQueue *currentQueue = [NSOperationQueue currentQueue]; 45 | BOOL inBackgroundAndInOperationQueue = (currentQueue != nil && currentQueue != [NSOperationQueue mainQueue]); 46 | 47 | #pragma clang diagnostic push 48 | #pragma clang diagnostic ignored "-Wdeprecated" 49 | self.operationConnection = [[NSURLConnection alloc] initWithRequest:self.operationRequest delegate:self startImmediately:NO]; 50 | #pragma clang diagnostic pop 51 | NSRunLoop *targetRunLoop = (inBackgroundAndInOperationQueue) ? [NSRunLoop currentRunLoop] : [NSRunLoop mainRunLoop]; 52 | [self.operationConnection scheduleInRunLoop:targetRunLoop forMode:NSDefaultRunLoopMode]; 53 | [self.operationConnection start]; 54 | } 55 | 56 | #pragma mark - NSURLConnectionDelegate 57 | 58 | - (NSURLRequest *)connection:(NSURLConnection *)connection 59 | willSendRequest:(NSURLRequest *)request 60 | redirectResponse:(NSURLResponse *)response 61 | { 62 | NSURLRequest *redirectionRequest = request; 63 | 64 | return redirectionRequest; 65 | } 66 | 67 | - (void)connection:(NSURLConnection *)connection 68 | didReceiveResponse:(NSURLResponse *)response 69 | { 70 | self.expectedContentLength = response.expectedContentLength; 71 | self.receivedContentLength = 0; 72 | self.operationURLResponse = (NSHTTPURLResponse *)response; 73 | } 74 | 75 | - (void)connection:(NSURLConnection *)connection 76 | didReceiveData:(NSData *)data 77 | { 78 | [self handleResponseData:data]; 79 | } 80 | 81 | - (void)connectionDidFinishLoading:(NSURLConnection *)connection 82 | { 83 | [self callCompletionBlockWithResponse:nil requestSuccess:YES error:nil]; 84 | } 85 | 86 | - (void)connection:(NSURLConnection *)connection 87 | didFailWithError:(NSError *)error 88 | { 89 | [self callCompletionBlockWithResponse:nil requestSuccess:NO error:error]; 90 | } 91 | 92 | - (void)connection:(NSURLConnection *)connection 93 | willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge 94 | { 95 | if (challenge.previousFailureCount > 0) 96 | { 97 | [challenge.sender cancelAuthenticationChallenge:challenge]; 98 | }else{ 99 | [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge]; 100 | } 101 | } 102 | 103 | @end 104 | -------------------------------------------------------------------------------- /GQImageDownloader/GQImageRequestConfigure/GQImageDataDownloader.h: -------------------------------------------------------------------------------- 1 | // 2 | // GQImageDataDownloader.h 3 | // GQImageDownload 4 | // 5 | // Created by 高旗 on 2017/11/23. 6 | // Copyright © 2017年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "GQImageDownloaderURLConnectionOperation.h" 11 | #import "GQImageDownloaderSessionOperation.h" 12 | 13 | typedef void(^GQImageDownloaderCompleteBlock)(UIImage* image, NSURL *url, NSError *error); 14 | typedef void(^GQImageDownloaderProgressBlock)(CGFloat progress); 15 | 16 | @interface GQImageDataDownloader : NSObject 17 | 18 | + (instancetype)sharedDownloadManager; 19 | 20 | /** 21 | 设置图片处理请求class 22 | 23 | param requestClass 24 | */ 25 | - (void)setURLRequestClass:(Class)requestClass; 26 | 27 | - (id)downloadWithURL:(NSURL *)url 28 | progress:(GQImageDownloaderProgressBlock)progress 29 | complete:(GQImageDownloaderCompleteBlock)complete; 30 | 31 | - (void)suspendLoading; 32 | 33 | - (void)restoreLoading; 34 | 35 | - (void)cancelAllOpration; 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /GQImageDownloader/GQImageRequestConfigure/GQImageDataDownloader.m: -------------------------------------------------------------------------------- 1 | // 2 | // GQImageDataDownloader.m 3 | // GQImageDownload 4 | // 5 | // Created by 高旗 on 2017/11/23. 6 | // Copyright © 2017年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import "GQImageDataDownloader.h" 10 | #import "GQImageDownloaderConst.h" 11 | #import "GQImageDownloaderURLConnectionOperation.h" 12 | #import "GQImageDownloaderSessionOperation.h" 13 | #import "GQImageDownloaderBaseURLRequest.h" 14 | 15 | #import "NSData+GQImageDownloader.h" 16 | 17 | #import "GQImageDownloaderSessionOperation.h" 18 | 19 | #import "GQImageDownloaderSessionManager.h" 20 | 21 | static NSString *const kProgressCallbackKey = @"progress"; 22 | static NSString *const kCompletedCallbackKey = @"completed"; 23 | 24 | @interface GQImageDataDownloader() { 25 | NSDate *_lastSuspendedTime; 26 | } 27 | 28 | @property (nonatomic,strong) NSOperationQueue *connectionQueue; 29 | 30 | @property (nonatomic, strong) GQImageDownloaderSessionManager *sessionManager NS_AVAILABLE_IOS(8.0); 31 | 32 | @property (nonatomic, strong) NSMutableDictionary *callBackDic; 33 | @property (GQDispatchQueueSetterSementics, nonatomic) dispatch_queue_t barrierQueue; 34 | @property (assign, nonatomic) Class requstClass; 35 | 36 | @end 37 | 38 | @implementation GQImageDataDownloader 39 | 40 | GQOBJECT_SINGLETON_BOILERPLATE(GQImageDataDownloader, sharedDownloadManager) 41 | 42 | - (instancetype)init { 43 | self = [super init]; 44 | if (self) { 45 | _connectionQueue = [[NSOperationQueue alloc] init]; 46 | [NSTimer scheduledTimerWithTimeInterval:10.0f target:self selector:@selector(checkRequestQueueStatus) userInfo:nil repeats:YES]; 47 | _barrierQueue = dispatch_queue_create("com.hackemist.GQWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT); 48 | _requstClass = [GQImageDownloaderBaseURLRequest class]; 49 | 50 | if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) { 51 | #pragma clang diagnostic push 52 | #pragma clang diagnostic ignored "-Wunguarded-availability" 53 | _sessionManager = [[GQImageDownloaderSessionManager alloc] initWithOperationQueue:_connectionQueue]; 54 | #pragma clang diagnostic pop 55 | } 56 | 57 | } 58 | return self; 59 | } 60 | 61 | - (void)dealloc { 62 | [_connectionQueue cancelAllOperations]; 63 | _connectionQueue = nil; 64 | _sessionManager = nil; 65 | _callBackDic = nil; 66 | } 67 | 68 | // make sure queue will not suspended for too long 69 | - (void)checkRequestQueueStatus 70 | { 71 | if(!_lastSuspendedTime || [_connectionQueue operationCount] == 0 ){ 72 | return; 73 | } 74 | if ([[NSDate date] timeIntervalSinceDate:_lastSuspendedTime] > 5.0) { 75 | [self restoreLoading]; 76 | } 77 | } 78 | 79 | - (id)startRequestWithUrl:(NSURL *)url 80 | { 81 | __block UIImage *image = nil; 82 | GQWeakify(self); 83 | id operation; 84 | GQImageDownloaderBaseURLRequest *request = [[_requstClass alloc] initWithURL:url]; 85 | if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) { 86 | #pragma clang diagnostic push 87 | #pragma clang diagnostic ignored "-Wunguarded-availability" 88 | operation = [[GQImageDownloaderSessionOperation alloc] 89 | initWithURLRequest:request 90 | operationSession:self.sessionManager.session 91 | progress:^(float progress) { 92 | __block NSArray *callbacksForURL; 93 | dispatch_barrier_sync(weak_self.barrierQueue, ^{ 94 | callbacksForURL = weak_self.callBackDic[url]; 95 | }); 96 | for (NSDictionary *callbacks in callbacksForURL) { 97 | dispatch_async(dispatch_get_main_queue(), ^{ 98 | GQImageDownloaderProgressBlock callback = callbacks[kProgressCallbackKey]; 99 | if (callback) callback(progress); 100 | }); 101 | } 102 | }cancel:^{ 103 | [weak_self.callBackDic removeObjectForKey:url]; 104 | }completion:^(GQImageDownloaderBaseOperation *urlOperation, BOOL requestSuccess, NSError *error) { 105 | NSData *data = urlOperation.operationData; 106 | image = [data gqImageWithData]; 107 | __block NSArray *callbacksForURL; 108 | dispatch_barrier_sync(weak_self.barrierQueue, ^{ 109 | callbacksForURL = weak_self.callBackDic[url]; 110 | }); 111 | for (NSDictionary *callbacks in callbacksForURL) { 112 | GQImageDownloaderCompleteBlock callback = callbacks[kCompletedCallbackKey]; 113 | if (callback) callback(image,url,error); 114 | } 115 | [weak_self.callBackDic removeObjectForKey:url]; 116 | }]; 117 | #pragma clang diagnostic pop 118 | }else { 119 | operation = [[GQImageDownloaderURLConnectionOperation alloc] 120 | initWithURLRequest:request 121 | progress:^(float progress) { 122 | __block NSArray *callbacksForURL; 123 | dispatch_barrier_sync(weak_self.barrierQueue, ^{ 124 | callbacksForURL = weak_self.callBackDic[url]; 125 | }); 126 | for (NSDictionary *callbacks in callbacksForURL) { 127 | dispatch_async(dispatch_get_main_queue(), ^{ 128 | GQImageDownloaderProgressBlock callback = callbacks[kProgressCallbackKey]; 129 | if (callback) callback(progress); 130 | }); 131 | } 132 | }cancel:^{ 133 | [weak_self.callBackDic removeObjectForKey:url]; 134 | }completion:^(GQImageDownloaderBaseOperation *urlOperation, BOOL requestSuccess, NSError *error) { 135 | NSData *data = urlOperation.operationData; 136 | image = [data gqImageWithData]; 137 | __block NSArray *callbacksForURL; 138 | dispatch_barrier_sync(weak_self.barrierQueue, ^{ 139 | callbacksForURL = weak_self.callBackDic[url]; 140 | }); 141 | for (NSDictionary *callbacks in callbacksForURL) { 142 | GQImageDownloaderCompleteBlock callback = callbacks[kCompletedCallbackKey]; 143 | if (callback) callback(image,url,error); 144 | } 145 | [weak_self.callBackDic removeObjectForKey:url]; 146 | }]; 147 | } 148 | 149 | [self.connectionQueue addOperation:operation]; 150 | return operation; 151 | } 152 | 153 | - (void)addBlockToCallBackDicUrl:(NSURL *)url 154 | progress:(GQImageDownloaderProgressBlock )progressBlock 155 | complete:(GQImageDownloaderCompleteBlock)completeBlock 156 | finishAdd:(GQImageDownloaderNoParamsBlock)callBackBlock { 157 | if (!url) { 158 | if (completeBlock) { 159 | completeBlock(nil,nil,nil); 160 | } 161 | return; 162 | } 163 | dispatch_barrier_sync(self.barrierQueue, ^{ 164 | BOOL first = NO; 165 | if (!self.callBackDic[url]) { 166 | self.callBackDic[url] = [NSMutableArray new]; 167 | first = YES; 168 | } 169 | 170 | NSMutableArray *callbacksForURL = self.callBackDic[url]; 171 | NSMutableDictionary *callbacks = [NSMutableDictionary new]; 172 | if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy]; 173 | if (completeBlock) callbacks[kCompletedCallbackKey] = [completeBlock copy]; 174 | [callbacksForURL addObject:callbacks]; 175 | self.callBackDic[url] = callbacksForURL; 176 | 177 | if (first) { 178 | callBackBlock(); 179 | } 180 | }); 181 | } 182 | 183 | #pragma mark -- publicMethod 184 | 185 | - (id)downloadWithURL:(NSURL *)url 186 | progress:(GQImageDownloaderProgressBlock)progress 187 | complete:(GQImageDownloaderCompleteBlock)complete 188 | { 189 | GQWeakify(self); 190 | __block GQImageDownloaderBaseOperation *operation; 191 | [self addBlockToCallBackDicUrl:(NSURL *)url progress:progress complete:complete finishAdd:^{ 192 | operation = [weak_self startRequestWithUrl:url]; 193 | }]; 194 | return operation; 195 | } 196 | 197 | - (void)cancelAllOpration 198 | { 199 | [_connectionQueue cancelAllOperations]; 200 | _callBackDic = nil; 201 | } 202 | 203 | - (void)suspendLoading 204 | { 205 | if ([_connectionQueue isSuspended]) { 206 | return; 207 | } 208 | [_connectionQueue setSuspended:YES]; 209 | _lastSuspendedTime = nil; 210 | _lastSuspendedTime = [NSDate date]; 211 | } 212 | 213 | - (void)restoreLoading 214 | { 215 | [_connectionQueue setSuspended:NO]; 216 | _lastSuspendedTime = nil; 217 | } 218 | 219 | - (void)setURLRequestClass:(Class)requestClass { 220 | _requstClass = (requestClass && [requestClass isSubclassOfClass:[GQImageDownloaderBaseURLRequest class]]) ? requestClass: [GQImageDownloaderBaseURLRequest class]; 221 | } 222 | 223 | #pragma mark -- lazy load 224 | 225 | - (NSMutableDictionary *)callBackDic { 226 | if (!_callBackDic) { 227 | _callBackDic = [[NSMutableDictionary alloc] init]; 228 | } 229 | return _callBackDic; 230 | } 231 | 232 | @end 233 | -------------------------------------------------------------------------------- /GQImageDownloader/GQImageRequestConfigure/GQImageDownloaderBaseURLRequest.h: -------------------------------------------------------------------------------- 1 | // 2 | // GQImageDownloaderBaseURLRequest.h 3 | // ImageViewer 4 | // 5 | // Created by 高旗 on 16/12/26. 6 | // Copyright © 2016年 tusm. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface GQImageDownloaderBaseURLRequest : NSMutableURLRequest 12 | 13 | /** 14 | 进行参数相关的配置 15 | */ 16 | - (void)configureRequestData; 17 | 18 | /** 19 | User-Agent 20 | 21 | @return User-Agent 22 | */ 23 | - (NSString *)userAgentString; 24 | 25 | /** 26 | Cookie 27 | 28 | @return Cookie 29 | */ 30 | - (NSString *)storageCookies; 31 | 32 | /** 33 | 请求接收类型 34 | 35 | @return 接收类型 36 | */ 37 | - (NSString *)acceptType; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /GQImageDownloader/GQImageRequestConfigure/GQImageDownloaderBaseURLRequest.m: -------------------------------------------------------------------------------- 1 | // 2 | // GQBaseURLRequest.m 3 | // ImageViewer 4 | // 5 | // Created by 高旗 on 16/12/26. 6 | // Copyright © 2016年 tusm. All rights reserved. 7 | // 8 | 9 | #import "GQImageDownloaderBaseURLRequest.h" 10 | #import 11 | 12 | static NSString *defaultUserAgent = nil; 13 | 14 | @implementation GQImageDownloaderBaseURLRequest 15 | 16 | - (instancetype)initWithURL:(NSURL *)URL { 17 | self = [super initWithURL:URL]; 18 | if (self) { 19 | [self defaultConfigure]; 20 | } 21 | return self; 22 | } 23 | 24 | #pragma mark -- private method 25 | - (void)defaultConfigure { 26 | self.timeoutInterval = 30.0f; 27 | [self setValue:[self userAgentString] forHTTPHeaderField:@"User-Agent"]; 28 | [self setValue:[self storageCookies] forHTTPHeaderField:@"Cookie"]; 29 | [self setValue:[self acceptType] forHTTPHeaderField:@"Accept"]; 30 | [self configureRequestData]; 31 | } 32 | 33 | #pragma mark -- public method 34 | - (void)configureRequestData { 35 | 36 | } 37 | 38 | - (NSString *)acceptType { 39 | #ifdef GQ_WEBP 40 | return @"image/webp,image/*;q=0.8"; 41 | #else 42 | return @"image/*;q=0.8"; 43 | #endif 44 | } 45 | 46 | - (NSString *)userAgentString 47 | { 48 | @synchronized (self) { 49 | if (!defaultUserAgent) { 50 | NSBundle *bundle = [NSBundle bundleForClass:[self class]]; 51 | 52 | NSString *appName = [bundle objectForInfoDictionaryKey:@"CFBundleDisplayName"]; 53 | if (!appName) { 54 | appName = [bundle objectForInfoDictionaryKey:@"CFBundleName"]; 55 | } 56 | 57 | NSData *latin1Data = [appName dataUsingEncoding:NSUTF8StringEncoding]; 58 | appName = [[NSString alloc] initWithData:latin1Data encoding:NSISOLatin1StringEncoding]; 59 | 60 | if (!appName) { 61 | return nil; 62 | } 63 | 64 | NSString *appVersion = nil; 65 | NSString *marketingVersionNumber = [bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; 66 | NSString *developmentVersionNumber = [bundle objectForInfoDictionaryKey:@"CFBundleVersion"]; 67 | if (marketingVersionNumber && developmentVersionNumber) { 68 | if ([marketingVersionNumber isEqualToString:developmentVersionNumber]) { 69 | appVersion = marketingVersionNumber; 70 | } else { 71 | appVersion = [NSString stringWithFormat:@"%@ rv:%@",marketingVersionNumber,developmentVersionNumber]; 72 | } 73 | } else { 74 | appVersion = (marketingVersionNumber ? marketingVersionNumber : developmentVersionNumber); 75 | } 76 | 77 | NSString *deviceName; 78 | NSString *OSName; 79 | NSString *OSVersion; 80 | NSString *locale = [[NSLocale currentLocale] localeIdentifier]; 81 | 82 | #if TARGET_OS_IPHONE 83 | UIDevice *device = [UIDevice currentDevice]; 84 | deviceName = [device model]; 85 | OSName = [device systemName]; 86 | OSVersion = [device systemVersion]; 87 | 88 | #else 89 | deviceName = @"Macintosh"; 90 | OSName = @"Mac OS X"; 91 | 92 | OSErr err; 93 | SInt32 versionMajor, versionMinor, versionBugFix; 94 | err = Gestalt(gestaltSystemVersionMajor, &versionMajor); 95 | if (err != noErr) return nil; 96 | err = Gestalt(gestaltSystemVersionMinor, &versionMinor); 97 | if (err != noErr) return nil; 98 | err = Gestalt(gestaltSystemVersionBugFix, &versionBugFix); 99 | if (err != noErr) return nil; 100 | OSVersion = [NSString stringWithFormat:@"%u.%u.%u", versionMajor, versionMinor, versionBugFix]; 101 | #endif 102 | 103 | defaultUserAgent = [NSString stringWithFormat:@"%@ %@ (%@; %@ %@; %@)", appName, appVersion, deviceName, OSName, OSVersion, locale]; 104 | } 105 | } 106 | return defaultUserAgent; 107 | } 108 | 109 | - (NSString *)storageCookies 110 | { 111 | NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:self.URL]; 112 | if ([cookies count] > 0) { 113 | NSHTTPCookie *cookie; 114 | NSString *cookieHeader = nil; 115 | for (cookie in cookies) { 116 | if (!cookieHeader) { 117 | cookieHeader = [NSString stringWithFormat: @"%@=%@",[cookie name],[cookie value]]; 118 | } else { 119 | cookieHeader = [NSString stringWithFormat: @"%@; %@=%@",cookieHeader,[cookie name],[cookie value]]; 120 | } 121 | } 122 | return cookieHeader; 123 | } 124 | return nil; 125 | } 126 | 127 | @end 128 | -------------------------------------------------------------------------------- /GQImageDownloader/GQImageRequestConfigure/GQImageDownloaderConst.h: -------------------------------------------------------------------------------- 1 | // 2 | // GQImageDownloaderConst.h 3 | // GQImageDownload 4 | // 5 | // Created by 高旗 on 2017/11/23. 6 | // Copyright © 2017年 gaoqi. All rights reserved. 7 | // 8 | 9 | #ifndef GQImageDownloaderConst_h 10 | #define GQImageDownloaderConst_h 11 | 12 | #import 13 | 14 | #define GQOBJECT_SINGLETON_BOILERPLATE(_object_name_, _shared_obj_name_) \ 15 | static _object_name_ *z##_shared_obj_name_ = nil; \ 16 | + (_object_name_ *)_shared_obj_name_ { \ 17 | @synchronized(self) { \ 18 | if (z##_shared_obj_name_ == nil) { \ 19 | static dispatch_once_t done; \ 20 | dispatch_once(&done, ^{ \ 21 | z##_shared_obj_name_ = [[super allocWithZone:nil] init]; }); \ 22 | } \ 23 | } \ 24 | return z##_shared_obj_name_; \ 25 | } \ 26 | + (id)allocWithZone:(NSZone *)zone { \ 27 | @synchronized(self) { \ 28 | if (z##_shared_obj_name_ == nil) { \ 29 | z##_shared_obj_name_ = [super allocWithZone:NULL]; \ 30 | return z##_shared_obj_name_; \ 31 | } \ 32 | } \ 33 | return nil; \ 34 | } \ 35 | - (id)copyWithZone:(NSZone*)zone \ 36 | { \ 37 | return z##_shared_obj_name_; \ 38 | } 39 | 40 | #if OS_OBJECT_USE_OBJC 41 | #undef GQDispatchQueueRelease 42 | #undef GQDispatchQueueSetterSementics 43 | #define GQDispatchQueueRelease(q) 44 | #define GQDispatchQueueSetterSementics strong 45 | #else 46 | #undef GQDispatchQueueRelease 47 | #undef GQDispatchQueueSetterSementics 48 | #define GQDispatchQueueRelease(q) (dispatch_release(q)) 49 | #define GQDispatchQueueSetterSementics assign 50 | #endif 51 | 52 | #define dispatch_main_async_safe(block)\ 53 | if ([NSThread isMainThread]) {\ 54 | block();\ 55 | } else {\ 56 | dispatch_async(dispatch_get_main_queue(), block);\ 57 | } 58 | 59 | //强弱引用 60 | #ifndef GQWeakify 61 | #define GQWeakify(object) __weak __typeof__(object) weak##_##object = object 62 | #endif 63 | 64 | #ifndef GQStrongify 65 | #define GQStrongify(object) __typeof__(object) object = weak##_##object 66 | #endif 67 | 68 | #pragma mark - 动态添加属性 69 | //动态添加属性 70 | #define GQ_DYNAMIC_PROPERTY_OBJECT(_getter_, _setter_, _association_, _type_) \ 71 | - (void)_setter_ : (_type_)object { \ 72 | [self willChangeValueForKey:@#_getter_]; \ 73 | objc_setAssociatedObject(self, _cmd, object, _association_); \ 74 | [self didChangeValueForKey:@#_getter_]; \ 75 | } \ 76 | - (_type_)_getter_ { \ 77 | return objc_getAssociatedObject(self, @selector(_setter_:)); \ 78 | } 79 | 80 | //动态添加BOOL属性 81 | #define GQ_DYNAMIC_PROPERTY_BOOL(_getter_, _setter_)\ 82 | - (void)_setter_:(BOOL)object {\ 83 | [self willChangeValueForKey:@#_getter_]; \ 84 | objc_setAssociatedObject(self, _cmd, @(object), OBJC_ASSOCIATION_ASSIGN); \ 85 | [self didChangeValueForKey:@#_getter_]; \ 86 | }\ 87 | - (BOOL)_getter_ {\ 88 | return [objc_getAssociatedObject(self, @selector(_setter_:)) boolValue];\ 89 | }\ 90 | 91 | typedef void(^GQImageDownloaderNoParamsBlock)(void); 92 | 93 | #endif /* GQImageDownloaderConst_h */ 94 | -------------------------------------------------------------------------------- /GQImageDownloader/GQThirdPart/WebP.framework/Headers/config.h: -------------------------------------------------------------------------------- 1 | /* src/webp/config.h. Generated from config.h.in by configure. */ 2 | /* src/webp/config.h.in. Generated from configure.ac by autoheader. */ 3 | 4 | /* Define if building universal (internal helper macro) */ 5 | /* #undef AC_APPLE_UNIVERSAL_BUILD */ 6 | 7 | /* Set to 1 if __builtin_bswap16 is available */ 8 | #define HAVE_BUILTIN_BSWAP16 1 9 | 10 | /* Set to 1 if __builtin_bswap32 is available */ 11 | #define HAVE_BUILTIN_BSWAP32 1 12 | 13 | /* Set to 1 if __builtin_bswap64 is available */ 14 | #define HAVE_BUILTIN_BSWAP64 1 15 | 16 | /* Define to 1 if you have the header file. */ 17 | #define HAVE_DLFCN_H 1 18 | 19 | /* Define to 1 if you have the header file. */ 20 | /* #undef HAVE_GLUT_GLUT_H */ 21 | 22 | /* Define to 1 if you have the header file. */ 23 | /* #undef HAVE_GL_GLUT_H */ 24 | 25 | /* Define to 1 if you have the header file. */ 26 | #define HAVE_INTTYPES_H 1 27 | 28 | /* Define to 1 if you have the header file. */ 29 | #define HAVE_MEMORY_H 1 30 | 31 | /* Define to 1 if you have the header file. */ 32 | /* #undef HAVE_OPENGL_GLUT_H */ 33 | 34 | /* Have PTHREAD_PRIO_INHERIT. */ 35 | #define HAVE_PTHREAD_PRIO_INHERIT 1 36 | 37 | /* Define to 1 if you have the header file. */ 38 | /* #undef HAVE_SHLWAPI_H */ 39 | 40 | /* Define to 1 if you have the header file. */ 41 | #define HAVE_STDINT_H 1 42 | 43 | /* Define to 1 if you have the header file. */ 44 | #define HAVE_STDLIB_H 1 45 | 46 | /* Define to 1 if you have the header file. */ 47 | #define HAVE_STRINGS_H 1 48 | 49 | /* Define to 1 if you have the header file. */ 50 | #define HAVE_STRING_H 1 51 | 52 | /* Define to 1 if you have the header file. */ 53 | #define HAVE_SYS_STAT_H 1 54 | 55 | /* Define to 1 if you have the header file. */ 56 | #define HAVE_SYS_TYPES_H 1 57 | 58 | /* Define to 1 if you have the header file. */ 59 | #define HAVE_UNISTD_H 1 60 | 61 | /* Define to 1 if you have the header file. */ 62 | /* #undef HAVE_WINCODEC_H */ 63 | 64 | /* Define to 1 if you have the header file. */ 65 | /* #undef HAVE_WINDOWS_H */ 66 | 67 | /* Define to the sub-directory in which libtool stores uninstalled libraries. 68 | */ 69 | #define LT_OBJDIR ".libs/" 70 | 71 | /* Name of package */ 72 | #define PACKAGE "libwebp" 73 | 74 | /* Define to the address where bug reports for this package should be sent. */ 75 | #define PACKAGE_BUGREPORT "https://bugs.chromium.org/p/webp" 76 | 77 | /* Define to the full name of this package. */ 78 | #define PACKAGE_NAME "libwebp" 79 | 80 | /* Define to the full name and version of this package. */ 81 | #define PACKAGE_STRING "libwebp 0.5.0" 82 | 83 | /* Define to the one symbol short name of this package. */ 84 | #define PACKAGE_TARNAME "libwebp" 85 | 86 | /* Define to the home page for this package. */ 87 | #define PACKAGE_URL "http://developers.google.com/speed/webp" 88 | 89 | /* Define to the version of this package. */ 90 | #define PACKAGE_VERSION "0.5.0" 91 | 92 | /* Define to necessary symbol if this constant uses a non-standard name on 93 | your system. */ 94 | /* #undef PTHREAD_CREATE_JOINABLE */ 95 | 96 | /* Define to 1 if you have the ANSI C header files. */ 97 | #define STDC_HEADERS 1 98 | 99 | /* Version number of package */ 100 | #define VERSION "0.5.0" 101 | 102 | /* Enable experimental code */ 103 | /* #undef WEBP_EXPERIMENTAL_FEATURES */ 104 | 105 | /* Define to 1 to force aligned memory operations */ 106 | /* #undef WEBP_FORCE_ALIGNED */ 107 | 108 | /* Set to 1 if AVX2 is supported */ 109 | /* #undef WEBP_HAVE_AVX2 */ 110 | 111 | /* Set to 1 if GIF library is installed */ 112 | /* #undef WEBP_HAVE_GIF */ 113 | 114 | /* Set to 1 if OpenGL is supported */ 115 | /* #undef WEBP_HAVE_GL */ 116 | 117 | /* Set to 1 if JPEG library is installed */ 118 | /* #undef WEBP_HAVE_JPEG */ 119 | 120 | /* Set to 1 if PNG library is installed */ 121 | /* #undef WEBP_HAVE_PNG */ 122 | 123 | /* Set to 1 if SSE2 is supported */ 124 | /* #undef WEBP_HAVE_SSE2 */ 125 | 126 | /* Set to 1 if SSE4.1 is supported */ 127 | /* #undef WEBP_HAVE_SSE41 */ 128 | 129 | /* Set to 1 if TIFF library is installed */ 130 | /* #undef WEBP_HAVE_TIFF */ 131 | 132 | /* Undefine this to disable thread support. */ 133 | #define WEBP_USE_THREAD 1 134 | 135 | /* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most 136 | significant byte first (like Motorola and SPARC, unlike Intel). */ 137 | #if defined AC_APPLE_UNIVERSAL_BUILD 138 | # if defined __BIG_ENDIAN__ 139 | # define WORDS_BIGENDIAN 1 140 | # endif 141 | #else 142 | # ifndef WORDS_BIGENDIAN 143 | /* # undef WORDS_BIGENDIAN */ 144 | # endif 145 | #endif 146 | -------------------------------------------------------------------------------- /GQImageDownloader/GQThirdPart/WebP.framework/Headers/demux.h: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Google Inc. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by a BSD-style license 4 | // that can be found in the COPYING file in the root of the source 5 | // tree. An additional intellectual property rights grant can be found 6 | // in the file PATENTS. All contributing project authors may 7 | // be found in the AUTHORS file in the root of the source tree. 8 | // ----------------------------------------------------------------------------- 9 | // 10 | // Demux API. 11 | // Enables extraction of image and extended format data from WebP files. 12 | 13 | // Code Example: Demuxing WebP data to extract all the frames, ICC profile 14 | // and EXIF/XMP metadata. 15 | /* 16 | WebPDemuxer* demux = WebPDemux(&webp_data); 17 | 18 | uint32_t width = WebPDemuxGetI(demux, WEBP_FF_CANVAS_WIDTH); 19 | uint32_t height = WebPDemuxGetI(demux, WEBP_FF_CANVAS_HEIGHT); 20 | // ... (Get information about the features present in the WebP file). 21 | uint32_t flags = WebPDemuxGetI(demux, WEBP_FF_FORMAT_FLAGS); 22 | 23 | // ... (Iterate over all frames). 24 | WebPIterator iter; 25 | if (WebPDemuxGetFrame(demux, 1, &iter)) { 26 | do { 27 | // ... (Consume 'iter'; e.g. Decode 'iter.fragment' with WebPDecode(), 28 | // ... and get other frame properties like width, height, offsets etc. 29 | // ... see 'struct WebPIterator' below for more info). 30 | } while (WebPDemuxNextFrame(&iter)); 31 | WebPDemuxReleaseIterator(&iter); 32 | } 33 | 34 | // ... (Extract metadata). 35 | WebPChunkIterator chunk_iter; 36 | if (flags & ICCP_FLAG) WebPDemuxGetChunk(demux, "ICCP", 1, &chunk_iter); 37 | // ... (Consume the ICC profile in 'chunk_iter.chunk'). 38 | WebPDemuxReleaseChunkIterator(&chunk_iter); 39 | if (flags & EXIF_FLAG) WebPDemuxGetChunk(demux, "EXIF", 1, &chunk_iter); 40 | // ... (Consume the EXIF metadata in 'chunk_iter.chunk'). 41 | WebPDemuxReleaseChunkIterator(&chunk_iter); 42 | if (flags & XMP_FLAG) WebPDemuxGetChunk(demux, "XMP ", 1, &chunk_iter); 43 | // ... (Consume the XMP metadata in 'chunk_iter.chunk'). 44 | WebPDemuxReleaseChunkIterator(&chunk_iter); 45 | WebPDemuxDelete(demux); 46 | */ 47 | 48 | #ifndef WEBP_WEBP_DEMUX_H_ 49 | #define WEBP_WEBP_DEMUX_H_ 50 | 51 | #include "./decode.h" // for WEBP_CSP_MODE 52 | #include "./mux_types.h" 53 | 54 | #ifdef __cplusplus 55 | extern "C" { 56 | #endif 57 | 58 | #define WEBP_DEMUX_ABI_VERSION 0x0107 // MAJOR(8b) + MINOR(8b) 59 | 60 | // Note: forward declaring enumerations is not allowed in (strict) C and C++, 61 | // the types are left here for reference. 62 | // typedef enum WebPDemuxState WebPDemuxState; 63 | // typedef enum WebPFormatFeature WebPFormatFeature; 64 | typedef struct WebPDemuxer WebPDemuxer; 65 | typedef struct WebPIterator WebPIterator; 66 | typedef struct WebPChunkIterator WebPChunkIterator; 67 | typedef struct WebPAnimInfo WebPAnimInfo; 68 | typedef struct WebPAnimDecoderOptions WebPAnimDecoderOptions; 69 | 70 | //------------------------------------------------------------------------------ 71 | 72 | // Returns the version number of the demux library, packed in hexadecimal using 73 | // 8bits for each of major/minor/revision. E.g: v2.5.7 is 0x020507. 74 | WEBP_EXTERN(int) WebPGetDemuxVersion(void); 75 | 76 | //------------------------------------------------------------------------------ 77 | // Life of a Demux object 78 | 79 | typedef enum WebPDemuxState { 80 | WEBP_DEMUX_PARSE_ERROR = -1, // An error occurred while parsing. 81 | WEBP_DEMUX_PARSING_HEADER = 0, // Not enough data to parse full header. 82 | WEBP_DEMUX_PARSED_HEADER = 1, // Header parsing complete, 83 | // data may be available. 84 | WEBP_DEMUX_DONE = 2 // Entire file has been parsed. 85 | } WebPDemuxState; 86 | 87 | // Internal, version-checked, entry point 88 | WEBP_EXTERN(WebPDemuxer*) WebPDemuxInternal( 89 | const WebPData*, int, WebPDemuxState*, int); 90 | 91 | // Parses the full WebP file given by 'data'. For single images the WebP file 92 | // header alone or the file header and the chunk header may be absent. 93 | // Returns a WebPDemuxer object on successful parse, NULL otherwise. 94 | static WEBP_INLINE WebPDemuxer* WebPDemux(const WebPData* data) { 95 | return WebPDemuxInternal(data, 0, NULL, WEBP_DEMUX_ABI_VERSION); 96 | } 97 | 98 | // Parses the possibly incomplete WebP file given by 'data'. 99 | // If 'state' is non-NULL it will be set to indicate the status of the demuxer. 100 | // Returns NULL in case of error or if there isn't enough data to start parsing; 101 | // and a WebPDemuxer object on successful parse. 102 | // Note that WebPDemuxer keeps internal pointers to 'data' memory segment. 103 | // If this data is volatile, the demuxer object should be deleted (by calling 104 | // WebPDemuxDelete()) and WebPDemuxPartial() called again on the new data. 105 | // This is usually an inexpensive operation. 106 | static WEBP_INLINE WebPDemuxer* WebPDemuxPartial( 107 | const WebPData* data, WebPDemuxState* state) { 108 | return WebPDemuxInternal(data, 1, state, WEBP_DEMUX_ABI_VERSION); 109 | } 110 | 111 | // Frees memory associated with 'dmux'. 112 | WEBP_EXTERN(void) WebPDemuxDelete(WebPDemuxer* dmux); 113 | 114 | //------------------------------------------------------------------------------ 115 | // Data/information extraction. 116 | 117 | typedef enum WebPFormatFeature { 118 | WEBP_FF_FORMAT_FLAGS, // Extended format flags present in the 'VP8X' chunk. 119 | WEBP_FF_CANVAS_WIDTH, 120 | WEBP_FF_CANVAS_HEIGHT, 121 | WEBP_FF_LOOP_COUNT, 122 | WEBP_FF_BACKGROUND_COLOR, 123 | WEBP_FF_FRAME_COUNT // Number of frames present in the demux object. 124 | // In case of a partial demux, this is the number of 125 | // frames seen so far, with the last frame possibly 126 | // being partial. 127 | } WebPFormatFeature; 128 | 129 | // Get the 'feature' value from the 'dmux'. 130 | // NOTE: values are only valid if WebPDemux() was used or WebPDemuxPartial() 131 | // returned a state > WEBP_DEMUX_PARSING_HEADER. 132 | WEBP_EXTERN(uint32_t) WebPDemuxGetI( 133 | const WebPDemuxer* dmux, WebPFormatFeature feature); 134 | 135 | //------------------------------------------------------------------------------ 136 | // Frame iteration. 137 | 138 | struct WebPIterator { 139 | int frame_num; 140 | int num_frames; // equivalent to WEBP_FF_FRAME_COUNT. 141 | int x_offset, y_offset; // offset relative to the canvas. 142 | int width, height; // dimensions of this frame. 143 | int duration; // display duration in milliseconds. 144 | WebPMuxAnimDispose dispose_method; // dispose method for the frame. 145 | int complete; // true if 'fragment' contains a full frame. partial images 146 | // may still be decoded with the WebP incremental decoder. 147 | WebPData fragment; // The frame given by 'frame_num'. Note for historical 148 | // reasons this is called a fragment. 149 | int has_alpha; // True if the frame contains transparency. 150 | WebPMuxAnimBlend blend_method; // Blend operation for the frame. 151 | 152 | uint32_t pad[2]; // padding for later use. 153 | void* private_; // for internal use only. 154 | }; 155 | 156 | // Retrieves frame 'frame_number' from 'dmux'. 157 | // 'iter->fragment' points to the frame on return from this function. 158 | // Setting 'frame_number' equal to 0 will return the last frame of the image. 159 | // Returns false if 'dmux' is NULL or frame 'frame_number' is not present. 160 | // Call WebPDemuxReleaseIterator() when use of the iterator is complete. 161 | // NOTE: 'dmux' must persist for the lifetime of 'iter'. 162 | WEBP_EXTERN(int) WebPDemuxGetFrame( 163 | const WebPDemuxer* dmux, int frame_number, WebPIterator* iter); 164 | 165 | // Sets 'iter->fragment' to point to the next ('iter->frame_num' + 1) or 166 | // previous ('iter->frame_num' - 1) frame. These functions do not loop. 167 | // Returns true on success, false otherwise. 168 | WEBP_EXTERN(int) WebPDemuxNextFrame(WebPIterator* iter); 169 | WEBP_EXTERN(int) WebPDemuxPrevFrame(WebPIterator* iter); 170 | 171 | // Releases any memory associated with 'iter'. 172 | // Must be called before any subsequent calls to WebPDemuxGetChunk() on the same 173 | // iter. Also, must be called before destroying the associated WebPDemuxer with 174 | // WebPDemuxDelete(). 175 | WEBP_EXTERN(void) WebPDemuxReleaseIterator(WebPIterator* iter); 176 | 177 | //------------------------------------------------------------------------------ 178 | // Chunk iteration. 179 | 180 | struct WebPChunkIterator { 181 | // The current and total number of chunks with the fourcc given to 182 | // WebPDemuxGetChunk(). 183 | int chunk_num; 184 | int num_chunks; 185 | WebPData chunk; // The payload of the chunk. 186 | 187 | uint32_t pad[6]; // padding for later use 188 | void* private_; 189 | }; 190 | 191 | // Retrieves the 'chunk_number' instance of the chunk with id 'fourcc' from 192 | // 'dmux'. 193 | // 'fourcc' is a character array containing the fourcc of the chunk to return, 194 | // e.g., "ICCP", "XMP ", "EXIF", etc. 195 | // Setting 'chunk_number' equal to 0 will return the last chunk in a set. 196 | // Returns true if the chunk is found, false otherwise. Image related chunk 197 | // payloads are accessed through WebPDemuxGetFrame() and related functions. 198 | // Call WebPDemuxReleaseChunkIterator() when use of the iterator is complete. 199 | // NOTE: 'dmux' must persist for the lifetime of the iterator. 200 | WEBP_EXTERN(int) WebPDemuxGetChunk(const WebPDemuxer* dmux, 201 | const char fourcc[4], int chunk_number, 202 | WebPChunkIterator* iter); 203 | 204 | // Sets 'iter->chunk' to point to the next ('iter->chunk_num' + 1) or previous 205 | // ('iter->chunk_num' - 1) chunk. These functions do not loop. 206 | // Returns true on success, false otherwise. 207 | WEBP_EXTERN(int) WebPDemuxNextChunk(WebPChunkIterator* iter); 208 | WEBP_EXTERN(int) WebPDemuxPrevChunk(WebPChunkIterator* iter); 209 | 210 | // Releases any memory associated with 'iter'. 211 | // Must be called before destroying the associated WebPDemuxer with 212 | // WebPDemuxDelete(). 213 | WEBP_EXTERN(void) WebPDemuxReleaseChunkIterator(WebPChunkIterator* iter); 214 | 215 | //------------------------------------------------------------------------------ 216 | // WebPAnimDecoder API 217 | // 218 | // This API allows decoding (possibly) animated WebP images. 219 | // 220 | // Code Example: 221 | /* 222 | WebPAnimDecoderOptions dec_options; 223 | WebPAnimDecoderOptionsInit(&dec_options); 224 | // Tune 'dec_options' as needed. 225 | WebPAnimDecoder* dec = WebPAnimDecoderNew(webp_data, &dec_options); 226 | WebPAnimInfo anim_info; 227 | WebPAnimDecoderGetInfo(dec, &anim_info); 228 | for (uint32_t i = 0; i < anim_info.loop_count; ++i) { 229 | while (WebPAnimDecoderHasMoreFrames(dec)) { 230 | uint8_t* buf; 231 | int timestamp; 232 | WebPAnimDecoderGetNext(dec, &buf, ×tamp); 233 | // ... (Render 'buf' based on 'timestamp'). 234 | // ... (Do NOT free 'buf', as it is owned by 'dec'). 235 | } 236 | WebPAnimDecoderReset(dec); 237 | } 238 | const WebPDemuxer* demuxer = WebPAnimDecoderGetDemuxer(dec); 239 | // ... (Do something using 'demuxer'; e.g. get EXIF/XMP/ICC data). 240 | WebPAnimDecoderDelete(dec); 241 | */ 242 | 243 | typedef struct WebPAnimDecoder WebPAnimDecoder; // Main opaque object. 244 | 245 | // Global options. 246 | struct WebPAnimDecoderOptions { 247 | // Output colorspace. Only the following modes are supported: 248 | // MODE_RGBA, MODE_BGRA, MODE_rgbA and MODE_bgrA. 249 | WEBP_CSP_MODE color_mode; 250 | int use_threads; // If true, use multi-threaded decoding. 251 | uint32_t padding[7]; // Padding for later use. 252 | }; 253 | 254 | // Internal, version-checked, entry point. 255 | WEBP_EXTERN(int) WebPAnimDecoderOptionsInitInternal( 256 | WebPAnimDecoderOptions*, int); 257 | 258 | // Should always be called, to initialize a fresh WebPAnimDecoderOptions 259 | // structure before modification. Returns false in case of version mismatch. 260 | // WebPAnimDecoderOptionsInit() must have succeeded before using the 261 | // 'dec_options' object. 262 | static WEBP_INLINE int WebPAnimDecoderOptionsInit( 263 | WebPAnimDecoderOptions* dec_options) { 264 | return WebPAnimDecoderOptionsInitInternal(dec_options, 265 | WEBP_DEMUX_ABI_VERSION); 266 | } 267 | 268 | // Internal, version-checked, entry point. 269 | WEBP_EXTERN(WebPAnimDecoder*) WebPAnimDecoderNewInternal( 270 | const WebPData*, const WebPAnimDecoderOptions*, int); 271 | 272 | // Creates and initializes a WebPAnimDecoder object. 273 | // Parameters: 274 | // webp_data - (in) WebP bitstream. This should remain unchanged during the 275 | // lifetime of the output WebPAnimDecoder object. 276 | // dec_options - (in) decoding options. Can be passed NULL to choose 277 | // reasonable defaults (in particular, color mode MODE_RGBA 278 | // will be picked). 279 | // Returns: 280 | // A pointer to the newly created WebPAnimDecoder object, or NULL in case of 281 | // parsing error, invalid option or memory error. 282 | static WEBP_INLINE WebPAnimDecoder* WebPAnimDecoderNew( 283 | const WebPData* webp_data, const WebPAnimDecoderOptions* dec_options) { 284 | return WebPAnimDecoderNewInternal(webp_data, dec_options, 285 | WEBP_DEMUX_ABI_VERSION); 286 | } 287 | 288 | // Global information about the animation.. 289 | struct WebPAnimInfo { 290 | uint32_t canvas_width; 291 | uint32_t canvas_height; 292 | uint32_t loop_count; 293 | uint32_t bgcolor; 294 | uint32_t frame_count; 295 | uint32_t pad[4]; // padding for later use 296 | }; 297 | 298 | // Get global information about the animation. 299 | // Parameters: 300 | // dec - (in) decoder instance to get information from. 301 | // info - (out) global information fetched from the animation. 302 | // Returns: 303 | // True on success. 304 | WEBP_EXTERN(int) WebPAnimDecoderGetInfo(const WebPAnimDecoder* dec, 305 | WebPAnimInfo* info); 306 | 307 | // Fetch the next frame from 'dec' based on options supplied to 308 | // WebPAnimDecoderNew(). This will be a fully reconstructed canvas of size 309 | // 'canvas_width * 4 * canvas_height', and not just the frame sub-rectangle. The 310 | // returned buffer 'buf' is valid only until the next call to 311 | // WebPAnimDecoderGetNext(), WebPAnimDecoderReset() or WebPAnimDecoderDelete(). 312 | // Parameters: 313 | // dec - (in/out) decoder instance from which the next frame is to be fetched. 314 | // buf - (out) decoded frame. 315 | // timestamp - (out) timestamp of the frame in milliseconds. 316 | // Returns: 317 | // False if any of the arguments are NULL, or if there is a parsing or 318 | // decoding error, or if there are no more frames. Otherwise, returns true. 319 | WEBP_EXTERN(int) WebPAnimDecoderGetNext(WebPAnimDecoder* dec, 320 | uint8_t** buf, int* timestamp); 321 | 322 | // Check if there are more frames left to decode. 323 | // Parameters: 324 | // dec - (in) decoder instance to be checked. 325 | // Returns: 326 | // True if 'dec' is not NULL and some frames are yet to be decoded. 327 | // Otherwise, returns false. 328 | WEBP_EXTERN(int) WebPAnimDecoderHasMoreFrames(const WebPAnimDecoder* dec); 329 | 330 | // Resets the WebPAnimDecoder object, so that next call to 331 | // WebPAnimDecoderGetNext() will restart decoding from 1st frame. This would be 332 | // helpful when all frames need to be decoded multiple times (e.g. 333 | // info.loop_count times) without destroying and recreating the 'dec' object. 334 | // Parameters: 335 | // dec - (in/out) decoder instance to be reset 336 | WEBP_EXTERN(void) WebPAnimDecoderReset(WebPAnimDecoder* dec); 337 | 338 | // Grab the internal demuxer object. 339 | // Getting the demuxer object can be useful if one wants to use operations only 340 | // available through demuxer; e.g. to get XMP/EXIF/ICC metadata. The returned 341 | // demuxer object is owned by 'dec' and is valid only until the next call to 342 | // WebPAnimDecoderDelete(). 343 | // 344 | // Parameters: 345 | // dec - (in) decoder instance from which the demuxer object is to be fetched. 346 | WEBP_EXTERN(const WebPDemuxer*) WebPAnimDecoderGetDemuxer( 347 | const WebPAnimDecoder* dec); 348 | 349 | // Deletes the WebPAnimDecoder object. 350 | // Parameters: 351 | // dec - (in/out) decoder instance to be deleted 352 | WEBP_EXTERN(void) WebPAnimDecoderDelete(WebPAnimDecoder* dec); 353 | 354 | #ifdef __cplusplus 355 | } // extern "C" 356 | #endif 357 | 358 | #endif /* WEBP_WEBP_DEMUX_H_ */ 359 | -------------------------------------------------------------------------------- /GQImageDownloader/GQThirdPart/WebP.framework/Headers/extras.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google Inc. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by a BSD-style license 4 | // that can be found in the COPYING file in the root of the source 5 | // tree. An additional intellectual property rights grant can be found 6 | // in the file PATENTS. All contributing project authors may 7 | // be found in the AUTHORS file in the root of the source tree. 8 | // ----------------------------------------------------------------------------- 9 | // 10 | 11 | #ifndef WEBP_WEBP_EXTRAS_H_ 12 | #define WEBP_WEBP_EXTRAS_H_ 13 | 14 | #include "./types.h" 15 | 16 | #ifdef __cplusplus 17 | extern "C" { 18 | #endif 19 | 20 | #include "./encode.h" 21 | 22 | #define WEBP_EXTRAS_ABI_VERSION 0x0000 // MAJOR(8b) + MINOR(8b) 23 | 24 | //------------------------------------------------------------------------------ 25 | 26 | // Returns the version number of the extras library, packed in hexadecimal using 27 | // 8bits for each of major/minor/revision. E.g: v2.5.7 is 0x020507. 28 | WEBP_EXTERN(int) WebPGetExtrasVersion(void); 29 | 30 | //------------------------------------------------------------------------------ 31 | // Ad-hoc colorspace importers. 32 | 33 | // Import luma sample (gray scale image) into 'picture'. The 'picture' 34 | // width and height must be set prior to calling this function. 35 | WEBP_EXTERN(int) WebPImportGray(const uint8_t* gray, WebPPicture* picture); 36 | 37 | // Import rgb sample in RGB565 packed format into 'picture'. The 'picture' 38 | // width and height must be set prior to calling this function. 39 | WEBP_EXTERN(int) WebPImportRGB565(const uint8_t* rgb565, WebPPicture* pic); 40 | 41 | // Import rgb sample in RGB4444 packed format into 'picture'. The 'picture' 42 | // width and height must be set prior to calling this function. 43 | WEBP_EXTERN(int) WebPImportRGB4444(const uint8_t* rgb4444, WebPPicture* pic); 44 | 45 | //------------------------------------------------------------------------------ 46 | 47 | #ifdef __cplusplus 48 | } // extern "C" 49 | #endif 50 | 51 | #endif /* WEBP_WEBP_EXTRAS_H_ */ 52 | -------------------------------------------------------------------------------- /GQImageDownloader/GQThirdPart/WebP.framework/Headers/format_constants.h: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Google Inc. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by a BSD-style license 4 | // that can be found in the COPYING file in the root of the source 5 | // tree. An additional intellectual property rights grant can be found 6 | // in the file PATENTS. All contributing project authors may 7 | // be found in the AUTHORS file in the root of the source tree. 8 | // ----------------------------------------------------------------------------- 9 | // 10 | // Internal header for constants related to WebP file format. 11 | // 12 | // Author: Urvang (urvang@google.com) 13 | 14 | #ifndef WEBP_WEBP_FORMAT_CONSTANTS_H_ 15 | #define WEBP_WEBP_FORMAT_CONSTANTS_H_ 16 | 17 | // Create fourcc of the chunk from the chunk tag characters. 18 | #define MKFOURCC(a, b, c, d) ((a) | (b) << 8 | (c) << 16 | (uint32_t)(d) << 24) 19 | 20 | // VP8 related constants. 21 | #define VP8_SIGNATURE 0x9d012a // Signature in VP8 data. 22 | #define VP8_MAX_PARTITION0_SIZE (1 << 19) // max size of mode partition 23 | #define VP8_MAX_PARTITION_SIZE (1 << 24) // max size for token partition 24 | #define VP8_FRAME_HEADER_SIZE 10 // Size of the frame header within VP8 data. 25 | 26 | // VP8L related constants. 27 | #define VP8L_SIGNATURE_SIZE 1 // VP8L signature size. 28 | #define VP8L_MAGIC_BYTE 0x2f // VP8L signature byte. 29 | #define VP8L_IMAGE_SIZE_BITS 14 // Number of bits used to store 30 | // width and height. 31 | #define VP8L_VERSION_BITS 3 // 3 bits reserved for version. 32 | #define VP8L_VERSION 0 // version 0 33 | #define VP8L_FRAME_HEADER_SIZE 5 // Size of the VP8L frame header. 34 | 35 | #define MAX_PALETTE_SIZE 256 36 | #define MAX_CACHE_BITS 11 37 | #define HUFFMAN_CODES_PER_META_CODE 5 38 | #define ARGB_BLACK 0xff000000 39 | 40 | #define DEFAULT_CODE_LENGTH 8 41 | #define MAX_ALLOWED_CODE_LENGTH 15 42 | 43 | #define NUM_LITERAL_CODES 256 44 | #define NUM_LENGTH_CODES 24 45 | #define NUM_DISTANCE_CODES 40 46 | #define CODE_LENGTH_CODES 19 47 | 48 | #define MIN_HUFFMAN_BITS 2 // min number of Huffman bits 49 | #define MAX_HUFFMAN_BITS 9 // max number of Huffman bits 50 | 51 | #define TRANSFORM_PRESENT 1 // The bit to be written when next data 52 | // to be read is a transform. 53 | #define NUM_TRANSFORMS 4 // Maximum number of allowed transform 54 | // in a bitstream. 55 | typedef enum { 56 | PREDICTOR_TRANSFORM = 0, 57 | CROSS_COLOR_TRANSFORM = 1, 58 | SUBTRACT_GREEN = 2, 59 | COLOR_INDEXING_TRANSFORM = 3 60 | } VP8LImageTransformType; 61 | 62 | // Alpha related constants. 63 | #define ALPHA_HEADER_LEN 1 64 | #define ALPHA_NO_COMPRESSION 0 65 | #define ALPHA_LOSSLESS_COMPRESSION 1 66 | #define ALPHA_PREPROCESSED_LEVELS 1 67 | 68 | // Mux related constants. 69 | #define TAG_SIZE 4 // Size of a chunk tag (e.g. "VP8L"). 70 | #define CHUNK_SIZE_BYTES 4 // Size needed to store chunk's size. 71 | #define CHUNK_HEADER_SIZE 8 // Size of a chunk header. 72 | #define RIFF_HEADER_SIZE 12 // Size of the RIFF header ("RIFFnnnnWEBP"). 73 | #define ANMF_CHUNK_SIZE 16 // Size of an ANMF chunk. 74 | #define ANIM_CHUNK_SIZE 6 // Size of an ANIM chunk. 75 | #define FRGM_CHUNK_SIZE 6 // Size of a FRGM chunk. 76 | #define VP8X_CHUNK_SIZE 10 // Size of a VP8X chunk. 77 | 78 | #define MAX_CANVAS_SIZE (1 << 24) // 24-bit max for VP8X width/height. 79 | #define MAX_IMAGE_AREA (1ULL << 32) // 32-bit max for width x height. 80 | #define MAX_LOOP_COUNT (1 << 16) // maximum value for loop-count 81 | #define MAX_DURATION (1 << 24) // maximum duration 82 | #define MAX_POSITION_OFFSET (1 << 24) // maximum frame/fragment x/y offset 83 | 84 | // Maximum chunk payload is such that adding the header and padding won't 85 | // overflow a uint32_t. 86 | #define MAX_CHUNK_PAYLOAD (~0U - CHUNK_HEADER_SIZE - 1) 87 | 88 | #endif /* WEBP_WEBP_FORMAT_CONSTANTS_H_ */ 89 | -------------------------------------------------------------------------------- /GQImageDownloader/GQThirdPart/WebP.framework/Headers/mux_types.h: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Google Inc. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by a BSD-style license 4 | // that can be found in the COPYING file in the root of the source 5 | // tree. An additional intellectual property rights grant can be found 6 | // in the file PATENTS. All contributing project authors may 7 | // be found in the AUTHORS file in the root of the source tree. 8 | // ----------------------------------------------------------------------------- 9 | // 10 | // Data-types common to the mux and demux libraries. 11 | // 12 | // Author: Urvang (urvang@google.com) 13 | 14 | #ifndef WEBP_WEBP_MUX_TYPES_H_ 15 | #define WEBP_WEBP_MUX_TYPES_H_ 16 | 17 | #include // free() 18 | #include // memset() 19 | #include "./types.h" 20 | 21 | #ifdef __cplusplus 22 | extern "C" { 23 | #endif 24 | 25 | // Note: forward declaring enumerations is not allowed in (strict) C and C++, 26 | // the types are left here for reference. 27 | // typedef enum WebPFeatureFlags WebPFeatureFlags; 28 | // typedef enum WebPMuxAnimDispose WebPMuxAnimDispose; 29 | // typedef enum WebPMuxAnimBlend WebPMuxAnimBlend; 30 | typedef struct WebPData WebPData; 31 | 32 | // VP8X Feature Flags. 33 | typedef enum WebPFeatureFlags { 34 | FRAGMENTS_FLAG = 0x00000001, 35 | ANIMATION_FLAG = 0x00000002, 36 | XMP_FLAG = 0x00000004, 37 | EXIF_FLAG = 0x00000008, 38 | ALPHA_FLAG = 0x00000010, 39 | ICCP_FLAG = 0x00000020 40 | } WebPFeatureFlags; 41 | 42 | // Dispose method (animation only). Indicates how the area used by the current 43 | // frame is to be treated before rendering the next frame on the canvas. 44 | typedef enum WebPMuxAnimDispose { 45 | WEBP_MUX_DISPOSE_NONE, // Do not dispose. 46 | WEBP_MUX_DISPOSE_BACKGROUND // Dispose to background color. 47 | } WebPMuxAnimDispose; 48 | 49 | // Blend operation (animation only). Indicates how transparent pixels of the 50 | // current frame are blended with those of the previous canvas. 51 | typedef enum WebPMuxAnimBlend { 52 | WEBP_MUX_BLEND, // Blend. 53 | WEBP_MUX_NO_BLEND // Do not blend. 54 | } WebPMuxAnimBlend; 55 | 56 | // Data type used to describe 'raw' data, e.g., chunk data 57 | // (ICC profile, metadata) and WebP compressed image data. 58 | struct WebPData { 59 | const uint8_t* bytes; 60 | size_t size; 61 | }; 62 | 63 | // Initializes the contents of the 'webp_data' object with default values. 64 | static WEBP_INLINE void WebPDataInit(WebPData* webp_data) { 65 | if (webp_data != NULL) { 66 | memset(webp_data, 0, sizeof(*webp_data)); 67 | } 68 | } 69 | 70 | // Clears the contents of the 'webp_data' object by calling free(). Does not 71 | // deallocate the object itself. 72 | static WEBP_INLINE void WebPDataClear(WebPData* webp_data) { 73 | if (webp_data != NULL) { 74 | free((void*)webp_data->bytes); 75 | WebPDataInit(webp_data); 76 | } 77 | } 78 | 79 | // Allocates necessary storage for 'dst' and copies the contents of 'src'. 80 | // Returns true on success. 81 | static WEBP_INLINE int WebPDataCopy(const WebPData* src, WebPData* dst) { 82 | if (src == NULL || dst == NULL) return 0; 83 | WebPDataInit(dst); 84 | if (src->bytes != NULL && src->size != 0) { 85 | dst->bytes = (uint8_t*)malloc(src->size); 86 | if (dst->bytes == NULL) return 0; 87 | memcpy((void*)dst->bytes, src->bytes, src->size); 88 | dst->size = src->size; 89 | } 90 | return 1; 91 | } 92 | 93 | #ifdef __cplusplus 94 | } // extern "C" 95 | #endif 96 | 97 | #endif /* WEBP_WEBP_MUX_TYPES_H_ */ 98 | -------------------------------------------------------------------------------- /GQImageDownloader/GQThirdPart/WebP.framework/Headers/types.h: -------------------------------------------------------------------------------- 1 | // Copyright 2010 Google Inc. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by a BSD-style license 4 | // that can be found in the COPYING file in the root of the source 5 | // tree. An additional intellectual property rights grant can be found 6 | // in the file PATENTS. All contributing project authors may 7 | // be found in the AUTHORS file in the root of the source tree. 8 | // ----------------------------------------------------------------------------- 9 | // 10 | // Common types 11 | // 12 | // Author: Skal (pascal.massimino@gmail.com) 13 | 14 | #ifndef WEBP_WEBP_TYPES_H_ 15 | #define WEBP_WEBP_TYPES_H_ 16 | 17 | #include // for size_t 18 | 19 | #ifndef _MSC_VER 20 | #include 21 | #if defined(__cplusplus) || !defined(__STRICT_ANSI__) || \ 22 | (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) 23 | #define WEBP_INLINE inline 24 | #else 25 | #define WEBP_INLINE 26 | #endif 27 | #else 28 | typedef signed char int8_t; 29 | typedef unsigned char uint8_t; 30 | typedef signed short int16_t; 31 | typedef unsigned short uint16_t; 32 | typedef signed int int32_t; 33 | typedef unsigned int uint32_t; 34 | typedef unsigned long long int uint64_t; 35 | typedef long long int int64_t; 36 | #define WEBP_INLINE __forceinline 37 | #endif /* _MSC_VER */ 38 | 39 | #ifndef WEBP_EXTERN 40 | // This explicitly marks library functions and allows for changing the 41 | // signature for e.g., Windows DLL builds. 42 | # if defined(__GNUC__) && __GNUC__ >= 4 43 | # define WEBP_EXTERN(type) extern __attribute__ ((visibility ("default"))) type 44 | # else 45 | # define WEBP_EXTERN(type) extern type 46 | # endif /* __GNUC__ >= 4 */ 47 | #endif /* WEBP_EXTERN */ 48 | 49 | // Macro to check ABI compatibility (same major revision number) 50 | #define WEBP_ABI_IS_INCOMPATIBLE(a, b) (((a) >> 8) != ((b) >> 8)) 51 | 52 | #endif /* WEBP_WEBP_TYPES_H_ */ 53 | -------------------------------------------------------------------------------- /GQImageDownloader/GQThirdPart/WebP.framework/WebP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g763007297/GQImageVideoViewer/c73793e879e789928be8e9b201946a5dad569032/GQImageDownloader/GQThirdPart/WebP.framework/WebP -------------------------------------------------------------------------------- /GQImageDownloader/UIImageView+GQImageDownloader.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImageView+GQImageDownloader.h 3 | // GQImageDownload 4 | // 5 | // Created by 高旗 on 2017/11/23. 6 | // Copyright © 2017年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef void(^GQImageCompletionBlock)(UIImage *image, NSError *error, NSURL *imageUrl); 12 | typedef void(^GQImageProgressBlock) (CGFloat progress); 13 | 14 | @interface UIImageView (GQImageDownloader) 15 | 16 | - (void)loadImage:(NSURL*)url 17 | progress:(GQImageProgressBlock)progress 18 | complete:(GQImageCompletionBlock)complete; 19 | 20 | - (void)loadImage:(NSURL*)url 21 | placeHolder:(UIImage *)placeHolderImage 22 | progress:(GQImageProgressBlock)progress 23 | complete:(GQImageCompletionBlock)complete; 24 | 25 | - (void)loadImage:(NSURL*)url 26 | requestClassName:(NSString *)className 27 | placeHolder:(UIImage *)placeHolderImage 28 | progress:(GQImageProgressBlock)progress 29 | complete:(GQImageCompletionBlock)complete; 30 | 31 | - (void)cancelCurrentImageRequest; //caller must call this method in its dealloc method 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /GQImageDownloader/UIImageView+GQImageDownloader.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIImageView+GQImageDownloader.m 3 | // GQImageDownload 4 | // 5 | // Created by 高旗 on 2017/11/23. 6 | // Copyright © 2017年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import "UIImageView+GQImageDownloader.h" 10 | #import "GQImageDownloaderOperationManager.h" 11 | #import "GQImageDownloaderConst.h" 12 | #import 13 | 14 | @implementation UIImageView (GQImageDownloader) 15 | 16 | GQ_DYNAMIC_PROPERTY_OBJECT(imageUrl, setImageUrl, OBJC_ASSOCIATION_RETAIN_NONATOMIC, NSURL*); 17 | GQ_DYNAMIC_PROPERTY_OBJECT(progress, setProgress, OBJC_ASSOCIATION_COPY_NONATOMIC, GQImageDownloaderProgressBlock); 18 | GQ_DYNAMIC_PROPERTY_OBJECT(complete, setComplete, OBJC_ASSOCIATION_COPY_NONATOMIC, GQImageDownloaderCompleteBlock); 19 | GQ_DYNAMIC_PROPERTY_OBJECT(downloadOperation, setDownloadOperation, OBJC_ASSOCIATION_RETAIN_NONATOMIC, id); 20 | 21 | - (void)dealloc 22 | { 23 | [self cancelCurrentImageRequest]; 24 | } 25 | 26 | - (void)cancelCurrentImageRequest 27 | { 28 | [[self downloadOperation] cancel]; 29 | [self setDownloadOperation:nil]; 30 | } 31 | 32 | - (void)loadImage:(NSURL*)url 33 | progress:(GQImageProgressBlock)progress 34 | complete:(GQImageCompletionBlock)complete 35 | { 36 | [self loadImage:url 37 | placeHolder:nil 38 | progress:progress 39 | complete:complete]; 40 | } 41 | 42 | - (void)loadImage:(NSURL*)url 43 | placeHolder:(UIImage *)placeHolderImage 44 | progress:(GQImageProgressBlock)progress 45 | complete:(GQImageCompletionBlock)complete 46 | { 47 | [self loadImage:url 48 | requestClassName:nil 49 | placeHolder:placeHolderImage 50 | progress:progress 51 | complete:complete]; 52 | } 53 | 54 | - (void)loadImage:(NSURL*)url 55 | requestClassName:(NSString *)className 56 | placeHolder:(UIImage *)placeHolderImage 57 | progress:(GQImageProgressBlock)progress 58 | complete:(GQImageCompletionBlock)complete { 59 | 60 | if(nil == url || [@"" isEqualToString:url.absoluteString] ) { 61 | return; 62 | } 63 | self.complete = [complete copy]; 64 | self.progress = [progress copy]; 65 | self.imageUrl = url; 66 | [self cancelCurrentImageRequest]; 67 | 68 | self.image = placeHolderImage; 69 | GQWeakify(self); 70 | __strong id _downloadOperation = [[GQImageDownloaderOperationManager sharedManager] 71 | loadWithURL:self.imageUrl 72 | withURLRequestClassName:className 73 | progress:^(CGFloat progress) { 74 | GQStrongify(self); 75 | if (self.progress) { 76 | self.progress(progress); 77 | } 78 | }complete:^(UIImage *image, NSURL *url, NSError *error) { 79 | GQStrongify(self); 80 | if (image) { 81 | self.image = image; 82 | } 83 | if (self.complete) { 84 | self.complete(image,url,error); 85 | } 86 | }]; 87 | [self setDownloadOperation:_downloadOperation]; 88 | } 89 | 90 | 91 | @end 92 | -------------------------------------------------------------------------------- /GQImageVideoViewer.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | s.name = "GQImageVideoViewer" 4 | s.version = "0.0.5" 5 | s.summary = "一款仿微信多图片及视频浏览器,图片和视频原尺寸显示,不会变形,双击图片放大缩小,单击消失,支持多张本地和网络图片以及网络视频混合查看,支持链式调用。" 6 | 7 | s.homepage = "https://github.com/g763007297/GQImageVideoViewer" 8 | # s.screenshots = "https://github.com/g763007297/GQImageVideoViewer/blob/master/Screenshot/demo.gif" 9 | 10 | s.license = "MIT (example)" 11 | s.license = { :type => "MIT", :file => "LICENSE" } 12 | 13 | s.author = { "developer_高" => "763007297@qq.com" } 14 | 15 | s.platform = :ios, "6.0" 16 | 17 | s.source = { :git => "https://github.com/g763007297/GQImageVideoViewer.git", :tag => s.version.to_s } 18 | 19 | s.requires_arc = true 20 | 21 | s.source_files = "GQImageVideoViewer/**/*.{h,m}" 22 | 23 | s.dependency "GQImageDownloader" 24 | 25 | end 26 | -------------------------------------------------------------------------------- /GQImageVideoViewer/GQBaseObject/GQBaseImageVideoModel.h: -------------------------------------------------------------------------------- 1 | // 2 | // GQBaseModel.h 3 | // GQImageVideoViewerDemo 4 | // 5 | // Created by 高旗 on 16/9/13. 6 | // Copyright © 2016年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | extern NSString *const GQURLString; 13 | extern NSString *const GQIsImageURL; 14 | extern NSString *const GQIsRepeat; 15 | extern NSString *const GQVideoViewClassName; 16 | extern NSString *const GQImageViewClassName; 17 | extern NSString *const GQNilClassName; 18 | 19 | @interface GQBaseImageVideoModel : NSObject 20 | 21 | /** 22 | * 可以传NSString,NSUrl,UIImage,UIImageView类型 23 | */ 24 | @property (nonatomic, copy) id GQURLString; 25 | 26 | /** 27 | * 是否为图片,如果是图片地址就传YES,如果是视频地址就传NO 28 | */ 29 | @property (nonatomic, assign) BOOL GQIsImageURL; 30 | 31 | /** 32 | 视频是否循环播放,只对视频有效,可以不传,默认为YES 33 | */ 34 | @property (assign, nonatomic) BOOL GQIsRepeat; 35 | 36 | /** 37 | 自定义视频播放界面class名称 必须继承GQBaseVideoView 38 | */ 39 | @property (nonatomic, strong) NSString *GQVideoViewClassName; 40 | 41 | /** 42 | 自定义图片浏览界面class名称 必须继承GQBaseImageView 43 | */ 44 | @property (nonatomic, strong) NSString *GQImageViewClassName; 45 | 46 | - (instancetype)initWithDataDic:(NSDictionary*)data; 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /GQImageVideoViewer/GQBaseObject/GQBaseImageVideoModel.m: -------------------------------------------------------------------------------- 1 | // 2 | // GQBaseModel.m 3 | // GQImageVideoViewerDemo 4 | // 5 | // Created by 高旗 on 16/9/13. 6 | // Copyright © 2016年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import "GQBaseImageVideoModel.h" 10 | #import 11 | 12 | NSString *const GQURLString = @"GQURLString"; 13 | NSString *const GQIsImageURL = @"GQIsImageURL"; 14 | NSString *const GQIsRepeat = @"GQIsRepeat"; 15 | NSString *const GQVideoViewClassName = @"GQVideoViewClassName"; 16 | NSString *const GQImageViewClassName = @"GQImageViewClassName"; 17 | NSString *const GQNilClassName = @"nil"; 18 | /** 19 | * Given a scalar or struct value, wraps it in NSValue 20 | * Based on EXPObjectify: https://github.com/specta/expecta 21 | */ 22 | static inline id _GQBoxValue(const char *type, ...) { 23 | va_list v; 24 | va_start(v, type); 25 | id obj = nil; 26 | if (strcmp(type, @encode(id)) == 0) { 27 | id actual = va_arg(v, id); 28 | obj = actual; 29 | } else if (strcmp(type, @encode(CGPoint)) == 0) { 30 | CGPoint actual = (CGPoint)va_arg(v, CGPoint); 31 | obj = [NSValue value:&actual withObjCType:type]; 32 | } else if (strcmp(type, @encode(CGSize)) == 0) { 33 | CGSize actual = (CGSize)va_arg(v, CGSize); 34 | obj = [NSValue value:&actual withObjCType:type]; 35 | } else if (strcmp(type, @encode(UIEdgeInsets)) == 0) { 36 | UIEdgeInsets actual = (UIEdgeInsets)va_arg(v, UIEdgeInsets); 37 | obj = [NSValue value:&actual withObjCType:type]; 38 | } else if (strcmp(type, @encode(double)) == 0) { 39 | double actual = (double)va_arg(v, double); 40 | obj = [NSNumber numberWithDouble:actual]; 41 | } else if (strcmp(type, @encode(float)) == 0) { 42 | float actual = (float)va_arg(v, double); 43 | obj = [NSNumber numberWithFloat:actual]; 44 | } else if (strcmp(type, @encode(int)) == 0) { 45 | int actual = (int)va_arg(v, int); 46 | obj = [NSNumber numberWithInt:actual]; 47 | } else if (strcmp(type, @encode(long)) == 0) { 48 | long actual = (long)va_arg(v, long); 49 | obj = [NSNumber numberWithLong:actual]; 50 | } else if (strcmp(type, @encode(long long)) == 0) { 51 | long long actual = (long long)va_arg(v, long long); 52 | obj = [NSNumber numberWithLongLong:actual]; 53 | } else if (strcmp(type, @encode(short)) == 0) { 54 | short actual = (short)va_arg(v, int); 55 | obj = [NSNumber numberWithShort:actual]; 56 | } else if (strcmp(type, @encode(char)) == 0) { 57 | char actual = (char)va_arg(v, int); 58 | obj = [NSNumber numberWithChar:actual]; 59 | } else if (strcmp(type, @encode(bool)) == 0) { 60 | bool actual = (bool)va_arg(v, int); 61 | obj = [NSNumber numberWithBool:actual]; 62 | } else if (strcmp(type, @encode(unsigned char)) == 0) { 63 | unsigned char actual = (unsigned char)va_arg(v, unsigned int); 64 | obj = [NSNumber numberWithUnsignedChar:actual]; 65 | } else if (strcmp(type, @encode(unsigned int)) == 0) { 66 | unsigned int actual = (unsigned int)va_arg(v, unsigned int); 67 | obj = [NSNumber numberWithUnsignedInt:actual]; 68 | } else if (strcmp(type, @encode(unsigned long)) == 0) { 69 | unsigned long actual = (unsigned long)va_arg(v, unsigned long); 70 | obj = [NSNumber numberWithUnsignedLong:actual]; 71 | } else if (strcmp(type, @encode(unsigned long long)) == 0) { 72 | unsigned long long actual = (unsigned long long)va_arg(v, unsigned long long); 73 | obj = [NSNumber numberWithUnsignedLongLong:actual]; 74 | } else if (strcmp(type, @encode(unsigned short)) == 0) { 75 | unsigned short actual = (unsigned short)va_arg(v, unsigned int); 76 | obj = [NSNumber numberWithUnsignedShort:actual]; 77 | } 78 | va_end(v); 79 | return obj; 80 | } 81 | 82 | #define GQBoxValue(value) _GQBoxValue(@encode(__typeof__((value))), (value)) 83 | 84 | typedef void(^GQEnumerateSuper)(Class c , BOOL *stop); 85 | 86 | @interface GQBaseImageVideoModel() 87 | 88 | - (void)setAttributes:(NSDictionary*)dataDic; 89 | 90 | @end 91 | 92 | @implementation GQBaseImageVideoModel 93 | 94 | + (NSDictionary *)attributeMapDictionary{ 95 | return [[[[self class] alloc] init] propertiesAttributeMapDictionary]; 96 | } 97 | 98 | - (NSString *)customDescription 99 | { 100 | return nil; 101 | } 102 | 103 | - (NSData*)getArchivedData 104 | { 105 | return [NSKeyedArchiver archivedDataWithRootObject:self]; 106 | } 107 | 108 | - (NSString *)description 109 | { 110 | NSMutableString *attrsDesc = [NSMutableString stringWithCapacity:100]; 111 | NSDictionary *attrMapDic = [[self class] attributeMapDictionary]; 112 | NSEnumerator *keyEnum = [attrMapDic keyEnumerator]; 113 | id attributeName; 114 | while ((attributeName = [keyEnum nextObject])) { 115 | id valueObj = [self getValue:attributeName]; 116 | if (valueObj) { 117 | [attrsDesc appendFormat:@" [%@=%@] ",attributeName,valueObj]; 118 | }else { 119 | [attrsDesc appendFormat:@" [%@=nil] ",attributeName]; 120 | } 121 | } 122 | NSString *customDesc = [self customDescription]; 123 | NSString *desc; 124 | if (customDesc && [customDesc length] > 0 ) { 125 | desc = [NSString stringWithFormat:@"%@:{%@,%@}", [self class], attrsDesc, customDesc]; 126 | } 127 | else { 128 | desc = [NSString stringWithFormat:@"%@:{%@}", [self class], attrsDesc]; 129 | } 130 | return desc; 131 | } 132 | 133 | 134 | 135 | - (instancetype)initWithDataDic:(NSDictionary*)data 136 | { 137 | if (self = [super init]) { 138 | [self setAttributes:data]; 139 | } 140 | return self; 141 | } 142 | 143 | - (instancetype)copyWithZone:(NSZone *)zone 144 | { 145 | id object = [[self class] allocWithZone:zone]; 146 | NSDictionary *attrMapDic = [[self class] attributeMapDictionary]; 147 | NSEnumerator *keyEnum = [attrMapDic keyEnumerator]; 148 | id attributeName; 149 | while ((attributeName = [keyEnum nextObject])) { 150 | SEL getSel = NSSelectorFromString(attributeName); 151 | SEL sel = [object getSetterSelWithAttibuteName:attributeName]; 152 | if ([self respondsToSelector:sel] && 153 | [self respondsToSelector:getSel]) { 154 | id valueObj = [self getValue:attributeName]; 155 | if (valueObj) { 156 | [object setValue:valueObj forKey:attributeName]; 157 | } 158 | } 159 | } 160 | return object; 161 | } 162 | 163 | - (instancetype)initWithCoder:(NSCoder *)decoder 164 | { 165 | if( self = [super init] ){ 166 | NSDictionary *attrMapDic = [[self class] attributeMapDictionary]; 167 | if (attrMapDic == nil) { 168 | return self; 169 | } 170 | NSEnumerator *keyEnum = [attrMapDic keyEnumerator]; 171 | id attributeName; 172 | while ((attributeName = [keyEnum nextObject])) { 173 | SEL sel = [self getSetterSelWithAttibuteName:attributeName]; 174 | if ([self respondsToSelector:sel]) { 175 | id obj = [decoder decodeObjectForKey:attributeName]; 176 | if (obj) { 177 | [self setValue:obj forKey:attributeName]; 178 | } 179 | } 180 | } 181 | } 182 | return self; 183 | } 184 | 185 | - (void)encodeWithCoder:(NSCoder *)encoder 186 | { 187 | NSDictionary *attrMapDic = [[self class] attributeMapDictionary]; 188 | if (attrMapDic == nil) { 189 | return; 190 | } 191 | NSEnumerator *keyEnum = [attrMapDic keyEnumerator]; 192 | id attributeName; 193 | while ((attributeName = [keyEnum nextObject])) { 194 | id valueObj = [self getValue:attributeName]; 195 | if (valueObj) { 196 | [encoder encodeObject:valueObj forKey:attributeName]; 197 | } 198 | } 199 | } 200 | 201 | #pragma mark - private methods 202 | -(SEL)getSetterSelWithAttibuteName:(NSString*)attributeName 203 | { 204 | NSString *capital = [[attributeName substringToIndex:1] uppercaseString]; 205 | NSString *setterSelStr = [NSString stringWithFormat:@"set%@%@:",capital,[attributeName substringFromIndex:1]]; 206 | return NSSelectorFromString(setterSelStr); 207 | } 208 | 209 | -(void)setAttributes:(NSDictionary*)dataDic 210 | { 211 | NSDictionary *attrMapDic = [[self class] attributeMapDictionary]; 212 | if (attrMapDic == nil) { 213 | return; 214 | } 215 | NSEnumerator *keyEnum = [attrMapDic keyEnumerator]; 216 | id attributeName; 217 | while ((attributeName = [keyEnum nextObject])) { 218 | SEL sel = [self getSetterSelWithAttibuteName:attributeName]; 219 | if ([self respondsToSelector:sel]) { 220 | NSString *dataDicKey = attrMapDic[attributeName]; 221 | NSString *value = nil; 222 | if([[dataDic objectForKey:dataDicKey] isKindOfClass:[NSNull class]]){ 223 | value = nil; 224 | }else{ 225 | value = [dataDic objectForKey:dataDicKey]; 226 | } 227 | [self setValue:value forKey:dataDicKey]; 228 | } 229 | } 230 | } 231 | 232 | /*! 233 | * get property names of object 234 | */ 235 | - (NSArray*)propertyNames 236 | { 237 | NSMutableArray *propertyNames = [[NSMutableArray alloc] init]; 238 | [[self class] enumerateCustomClass:^(__unsafe_unretained Class c, BOOL *stop) { 239 | unsigned int propertyCount = 0; 240 | objc_property_t *properties = class_copyPropertyList(c, &propertyCount); 241 | for (unsigned int i = 0; i < propertyCount; ++i) { 242 | objc_property_t property = properties[i]; 243 | const char * name = property_getName(property); 244 | [propertyNames addObject:[NSString stringWithUTF8String:name]]; 245 | } 246 | free(properties); 247 | }]; 248 | return propertyNames; 249 | } 250 | 251 | + (void)enumerateCustomClass:(GQEnumerateSuper)block 252 | { 253 | if (block == nil) { 254 | return; 255 | } 256 | BOOL stop = NO; 257 | 258 | Class c = self; 259 | 260 | while (c &&!stop) { 261 | block(c, &stop); 262 | 263 | c = class_getSuperclass(c); 264 | 265 | if (![c isSubclassOfClass:[GQBaseImageVideoModel class]]) break; 266 | } 267 | } 268 | 269 | - (NSArray *)versionChangeProperties 270 | { 271 | return nil; 272 | } 273 | 274 | /*! 275 | * \returns a dictionary Key-Value pair by property and corresponding value. 276 | */ 277 | - (NSDictionary*)propertiesAndValuesDictionary 278 | { 279 | NSMutableDictionary *propertiesValuesDic = [NSMutableDictionary dictionary]; 280 | NSArray *properties = [self propertyNames]; 281 | for (NSString *property in properties) { 282 | id object = [self getValue:property]?[self getValue:property]:@""; 283 | propertiesValuesDic[property] = object; 284 | } 285 | return propertiesValuesDic; 286 | } 287 | 288 | - (id)getValue:(NSString *)property 289 | { 290 | SEL getSel = NSSelectorFromString(property); 291 | id valueObj = nil; 292 | if ([self respondsToSelector:getSel]) { 293 | valueObj =[self valueForKey:property]; 294 | } 295 | return GQBoxValue(valueObj); 296 | } 297 | 298 | // default AttributeMapDictionary 299 | - (NSDictionary*)propertiesAttributeMapDictionary 300 | { 301 | NSMutableDictionary *attributeMapDictionary = [NSMutableDictionary dictionary]; 302 | NSArray *properties = [self propertyNames]; 303 | for (NSString *property in properties) { 304 | SEL getSel = NSSelectorFromString(property); 305 | if ([self respondsToSelector:getSel]) { 306 | attributeMapDictionary[property] = property; 307 | } 308 | } 309 | return attributeMapDictionary; 310 | } 311 | 312 | @end 313 | -------------------------------------------------------------------------------- /GQImageVideoViewer/GQBaseObject/GQBaseImageView.h: -------------------------------------------------------------------------------- 1 | // 2 | // GQImageView.h 3 | // GQImageVideoViewerDemo 4 | // 5 | // Created by 高旗 on 16/9/12. 6 | // Copyright © 2016年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface GQBaseImageView : UIImageView 12 | 13 | @property (nonatomic,assign) BOOL showLoadingView; 14 | 15 | /** 16 | 配置图片显示界面 17 | */ 18 | - (void)configureImageView; 19 | 20 | -(void)showLoading; 21 | 22 | -(void)hideLoading; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /GQImageVideoViewer/GQBaseObject/GQBaseImageView.m: -------------------------------------------------------------------------------- 1 | // 2 | // GQImageView.m 3 | // GQImageVideoViewerDemo 4 | // 5 | // Created by 高旗 on 16/9/12. 6 | // Copyright © 2016年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import "GQBaseImageView.h" 10 | #import "GQImageVideoViewerConst.h" 11 | 12 | @interface GQBaseImageView() 13 | 14 | @property (nonatomic, strong) UIActivityIndicatorView *indicator; 15 | 16 | @end 17 | 18 | @implementation GQBaseImageView 19 | 20 | #pragma mark -- life cycle 21 | 22 | - (void)awakeFromNib 23 | { 24 | [super awakeFromNib]; 25 | [self setupInit]; 26 | } 27 | 28 | - (instancetype)initWithFrame:(CGRect)frame 29 | { 30 | self = [super initWithFrame:frame]; 31 | if (self) { 32 | [self setupInit]; 33 | } 34 | return self; 35 | } 36 | 37 | - (void)setupInit { 38 | [self configureImageView]; 39 | self.showLoadingView = YES; 40 | self.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; 41 | } 42 | 43 | #pragma mark -- public method 44 | 45 | - (void)configureImageView { 46 | 47 | } 48 | 49 | -(void)showLoading 50 | { 51 | if (!self.showLoadingView) { 52 | return; 53 | } 54 | if (!_indicator) { 55 | _indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; 56 | _indicator.center = CGPointMake(self.bounds.origin.x+(self.bounds.size.width/2), self.bounds.origin.y+(self.bounds.size.height/2)); 57 | [_indicator setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleRightMargin|UIViewAutoresizingFlexibleBottomMargin|UIViewAutoresizingFlexibleTopMargin]; 58 | } 59 | if (!_indicator.isAnimating||_indicator.hidden) { 60 | _indicator.hidden = NO; 61 | if(!_indicator.superview){ 62 | [self addSubview:_indicator]; 63 | } 64 | [_indicator startAnimating]; 65 | } 66 | } 67 | 68 | -(void)hideLoading 69 | { 70 | if (!self.showLoadingView) { 71 | return; 72 | } 73 | if (_indicator) { 74 | [_indicator stopAnimating]; 75 | _indicator.hidden = YES; 76 | } 77 | } 78 | 79 | @end 80 | -------------------------------------------------------------------------------- /GQImageVideoViewer/GQBaseObject/GQBaseTableView.h: -------------------------------------------------------------------------------- 1 | // 2 | // GQBaseTableView.h 3 | // GQImageVideoViewerDemo 4 | // 5 | // Created by 高旗 on 16/9/12. 6 | // Copyright © 2016年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface GQBaseTableView : UICollectionView 12 | 13 | @property (nonatomic, strong) NSArray *dataArray; 14 | 15 | @property (nonatomic, copy) void (^block)(NSInteger index); 16 | 17 | //当前选中的单元格IndexPath 18 | @property(nonatomic,copy) NSIndexPath *selectedInexPath; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /GQImageVideoViewer/GQBaseObject/GQBaseTableView.m: -------------------------------------------------------------------------------- 1 | // 2 | // GQBaseTableView.m 3 | // GQImageVideoViewerDemo 4 | // 5 | // Created by 高旗 on 16/9/12. 6 | // Copyright © 2016年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import "GQBaseTableView.h" 10 | 11 | 12 | @interface GQReuseTabViewFlowLayout : UICollectionViewFlowLayout 13 | 14 | @end 15 | 16 | @implementation GQReuseTabViewFlowLayout 17 | - (void)prepareLayout 18 | { 19 | [super prepareLayout]; 20 | self.minimumInteritemSpacing = 0; 21 | self.minimumLineSpacing = 0; 22 | if (self.collectionView.bounds.size.height) { 23 | self.itemSize = self.collectionView.bounds.size; 24 | } 25 | self.scrollDirection = UICollectionViewScrollDirectionHorizontal; 26 | 27 | } 28 | 29 | @end 30 | 31 | @interface GQBaseTableView() { 32 | GQReuseTabViewFlowLayout *layouts; 33 | } 34 | 35 | @end 36 | 37 | @implementation GQBaseTableView 38 | 39 | - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout { 40 | 41 | layouts = [[GQReuseTabViewFlowLayout alloc] init]; 42 | 43 | self = [super initWithFrame:frame collectionViewLayout:layouts]; 44 | if (self) { 45 | [self _initViews:frame]; 46 | } 47 | return self; 48 | } 49 | 50 | - (void)awakeFromNib 51 | { 52 | [super awakeFromNib]; 53 | [self _initViews:self.frame]; 54 | } 55 | 56 | - (void)_initViews:(CGRect)frame 57 | { 58 | self.backgroundColor = [UIColor clearColor]; 59 | 60 | //去掉垂直方向的滚动条 61 | self.showsHorizontalScrollIndicator = NO; 62 | 63 | self.delegate = self; 64 | self.dataSource = self; 65 | 66 | //设置减速的方式, UIScrollViewDecelerationRateFast 为快速减速 67 | self.decelerationRate = UIScrollViewDecelerationRateFast; 68 | 69 | self.contentInset = UIEdgeInsetsMake(0,0,0,0); 70 | 71 | self.selectedInexPath = [NSIndexPath indexPathForRow:0 inSection:0]; 72 | } 73 | 74 | - (void)layoutSubviews { 75 | [super layoutSubviews]; 76 | [layouts prepareLayout]; 77 | } 78 | 79 | - (void)setDataArray:(NSArray *)dataArray 80 | { 81 | _dataArray = [dataArray copy]; 82 | [layouts prepareLayout]; 83 | } 84 | 85 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 86 | return _dataArray.count; 87 | } 88 | 89 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 90 | static NSString *identify = @"GQBaseCellIdentifier"; 91 | UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identify forIndexPath:indexPath]; 92 | return cell; 93 | } 94 | 95 | #pragma mark - UIScrollView delegate 96 | 97 | //手指离开屏幕时调用的协议方法 98 | - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate 99 | { 100 | //判断手指离开屏幕时,视图是否正在减速,如果是减速说明视图还在滚动中,如果不是则说明视图停止了 101 | if(!decelerate) { 102 | [self scrollCellToCenter]; 103 | } 104 | } 105 | 106 | //已经减速停止后调用的协议方法 107 | - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView 108 | { 109 | [self scrollCellToCenter]; 110 | } 111 | 112 | - (void) scrollViewDidScroll:(UIScrollView *)sender { 113 | [self getPageIndex]; 114 | } 115 | 116 | //将单元格滚动至中间位置 117 | - (void)scrollCellToCenter 118 | { 119 | CGFloat edge = self.contentInset.right; 120 | 121 | float y = self.contentOffset.x + edge + self.frame.size.width/2; 122 | int row = y/self.frame.size.width; 123 | 124 | if (row >= _dataArray.count || row < 0) { 125 | return; 126 | } 127 | 128 | NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:0]; 129 | 130 | [self scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionNone animated:NO]; 131 | } 132 | 133 | - (void)getPageIndex 134 | { 135 | CGFloat edge = self.contentInset.right; 136 | 137 | float y = self.contentOffset.x + edge + self.frame.size.width/2; 138 | int row = y/self.frame.size.width; 139 | 140 | if (row >= _dataArray.count || row < 0) { 141 | return; 142 | } 143 | NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:0]; 144 | 145 | if (indexPath.row != self.selectedInexPath.row) { 146 | if (self.block) { 147 | self.block(row); 148 | } 149 | //记录选中的单元格IndexPath 150 | self.selectedInexPath = indexPath; 151 | } 152 | } 153 | 154 | @end 155 | -------------------------------------------------------------------------------- /GQImageVideoViewer/GQBaseObject/GQBaseVideoView.h: -------------------------------------------------------------------------------- 1 | // 2 | // GQBaseVideoView.h 3 | // GQImageVideoViewerDemo 4 | // 5 | // Created by 高旗 on 16/9/12. 6 | // Copyright © 2016年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | typedef enum : NSUInteger { 13 | GQBaseVideoViewStateConfigure,//配置状态 14 | GQBaseVideoViewStateBuffer,//缓冲状态 15 | GQBaseVideoViewStateReadyToPlay,//准备播放状态 16 | GQBaseVideoViewStatePlaying,//播放状态 17 | GQBaseVideoViewStateStop,//停止状态 18 | GQBaseVideoViewStatePuase,//暂停状态 19 | GQBaseVideoViewStateFail,//播放失败 20 | } GQBaseVideoViewState; 21 | 22 | @interface GQBaseVideoView : UIView 23 | 24 | @property (nonatomic, strong) NSURL *item; 25 | 26 | /** 27 | 是否能循环播放,默认是YES,可以循环播放 28 | */ 29 | @property (assign, nonatomic) BOOL isRepeat; 30 | 31 | @property (nonatomic, strong, readonly) AVPlayerItem *playerItem; 32 | 33 | @property (nonatomic, strong, readonly) AVPlayer *player;//播放器 34 | 35 | /** 36 | 视频播放状态 37 | */ 38 | @property (nonatomic, assign) GQBaseVideoViewState state; 39 | 40 | /** 41 | 自定义配置播放面板 42 | */ 43 | - (void)configureVideoView; 44 | 45 | /** 46 | * 暂停 47 | */ 48 | - (void)puase; 49 | 50 | /** 51 | 切换 52 | */ 53 | - (void)replace; 54 | 55 | /** 56 | 播放 57 | */ 58 | - (void)play; 59 | 60 | /** 61 | * 停止播放 62 | */ 63 | - (void)stop; 64 | 65 | @end 66 | -------------------------------------------------------------------------------- /GQImageVideoViewer/GQBaseObject/GQBaseVideoView.m: -------------------------------------------------------------------------------- 1 | // 2 | // GQBaseVideoView.m 3 | // GQImageVideoViewerDemo 4 | // 5 | // Created by 高旗 on 16/9/12. 6 | // Copyright © 2016年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import "GQBaseVideoView.h" 10 | #import "GQImageVideoViewerConst.h" 11 | 12 | @interface GQBaseVideoView() 13 | 14 | @property (nonatomic, strong) UIActivityIndicatorView *indicator;//加载指示器 15 | 16 | @property (nonatomic, strong) AVPlayerLayer *playerLayer;//播放视图载体 17 | 18 | @end 19 | 20 | @implementation GQBaseVideoView 21 | 22 | GQ_DYNAMIC_PROPERTY_BOOL(isExitObserver, setIsExitObserver); 23 | 24 | #pragma mark -- privateMethod 25 | 26 | - (instancetype)initWithFrame:(CGRect)frame 27 | { 28 | self = [super initWithFrame:frame]; 29 | if (self) { 30 | self.backgroundColor = [UIColor blackColor]; 31 | [self configureVideoView]; 32 | _state = GQBaseVideoViewStateConfigure; 33 | _isRepeat = YES; 34 | } 35 | return self; 36 | } 37 | 38 | - (void)configureVideoView { 39 | 40 | } 41 | 42 | -(void)showLoading 43 | { 44 | if (!_indicator) { 45 | _indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge]; 46 | _indicator.center = CGPointMake(self.bounds.origin.x+(self.bounds.size.width/2), self.bounds.origin.y+(self.bounds.size.height/2)); 47 | [_indicator setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleRightMargin|UIViewAutoresizingFlexibleBottomMargin|UIViewAutoresizingFlexibleTopMargin]; 48 | } 49 | if (!_indicator.isAnimating||_indicator.hidden) { 50 | _indicator.hidden = NO; 51 | if(!_indicator.superview){ 52 | [self addSubview:_indicator]; 53 | } 54 | [_indicator startAnimating]; 55 | } 56 | } 57 | 58 | -(void)hideLoading 59 | { 60 | if (_indicator) { 61 | [_indicator stopAnimating]; 62 | _indicator.hidden = YES; 63 | } 64 | } 65 | 66 | - (void)addObserver 67 | { 68 | //判断是否已经添加KVO和存在父视图 69 | if (![self isExitObserver]&&self.superview) 70 | { 71 | //KVO 72 | [self setIsExitObserver:YES]; 73 | [_playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld|NSKeyValueObservingOptionInitial context:nil]; 74 | [_player addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld|NSKeyValueObservingOptionInitial context:nil]; 75 | [[NSNotificationCenter defaultCenter] addObserverForName:AVPlayerItemDidPlayToEndTimeNotification object:_playerItem queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * note) { 76 | // [_player seekToTime:kCMTimeZero]; 77 | // [self play]; 78 | // 重复播放 79 | [self repeatPlay]; 80 | }]; 81 | } 82 | } 83 | 84 | - (void)removeObserver 85 | { 86 | if ([self isExitObserver]) 87 | { 88 | [self setIsExitObserver:NO]; 89 | [_playerItem removeObserver:self forKeyPath:@"status"]; 90 | [_player removeObserver:self forKeyPath:@"status"]; 91 | [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:_playerItem]; 92 | _playerItem = nil; 93 | } 94 | } 95 | 96 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 97 | { 98 | if ([keyPath isEqualToString:@"status"]&&object == _playerItem) 99 | { 100 | AVPlayerItemStatus status = _playerItem.status; 101 | switch (status) { 102 | case AVPlayerItemStatusReadyToPlay: 103 | { 104 | //判断_player的status是否是准备播放 105 | if (_player.status == AVPlayerStatusReadyToPlay) { 106 | _state = GQBaseVideoViewStateReadyToPlay; 107 | [self hideLoading]; 108 | [self play]; 109 | } 110 | break; 111 | } 112 | case AVPlayerItemStatusFailed: 113 | { 114 | _state = GQBaseVideoViewStateFail; 115 | [self hideLoading]; 116 | break; 117 | } 118 | default: 119 | _state = GQBaseVideoViewStateBuffer; 120 | break; 121 | } 122 | }else if ([keyPath isEqualToString:@"status"]&&object == _player) 123 | { 124 | AVPlayerStatus stadus = _player.status; 125 | switch (stadus) { 126 | //判断_playerItem的status是否为准备播放 127 | case AVPlayerStatusReadyToPlay:{ 128 | if (_playerItem.status == AVPlayerItemStatusReadyToPlay) 129 | { 130 | _state = GQBaseVideoViewStateReadyToPlay; 131 | [self hideLoading]; 132 | [self play]; 133 | } 134 | break; 135 | } 136 | case AVPlayerStatusFailed:{ 137 | _state = GQBaseVideoViewStateFail; 138 | [self hideLoading]; 139 | break; 140 | } 141 | default: 142 | _state = GQBaseVideoViewStateBuffer; 143 | break; 144 | } 145 | } 146 | } 147 | 148 | 149 | /** 150 | 释放播放器 151 | */ 152 | - (void)releasePlayer { 153 | 154 | [self.playerLayer removeFromSuperlayer]; 155 | [self.player.currentItem cancelPendingSeeks]; 156 | [self.player.currentItem.asset cancelLoading]; 157 | [self.player replaceCurrentItemWithPlayerItem:nil]; 158 | _player = nil; 159 | self.playerLayer = nil; 160 | } 161 | 162 | /** 163 | 重复播放 164 | */ 165 | - (void)repeatPlay { 166 | 167 | if (_isRepeat) { 168 | [_player seekToTime:kCMTimeZero]; 169 | [self play]; 170 | } 171 | } 172 | 173 | #pragma mark -- Object lifecycle 174 | 175 | - (void)layoutSubviews { 176 | self.frame = self.bounds; 177 | self.playerLayer.frame = self.layer.bounds; 178 | } 179 | 180 | - (void)removeFromSuperview 181 | { 182 | [self removeObserver]; 183 | [super removeFromSuperview]; 184 | } 185 | 186 | - (void)dealloc 187 | { 188 | 189 | [self hideLoading]; 190 | 191 | // 释放播放器 192 | [self releasePlayer]; 193 | 194 | // [self.playerLayer removeFromSuperlayer]; 195 | // [self.player.currentItem cancelPendingSeeks]; 196 | // [self.player.currentItem.asset cancelLoading]; 197 | // [self.player replaceCurrentItemWithPlayerItem:nil]; 198 | // _player = nil; 199 | // self.playerLayer = nil; 200 | } 201 | 202 | #pragma mark -- publicMethod 203 | 204 | - (void)puase 205 | { 206 | [self hideLoading]; 207 | _state = GQBaseVideoViewStatePuase; 208 | [self.player pause]; 209 | } 210 | 211 | - (void)play 212 | { 213 | if (_player.status ==AVPlayerStatusReadyToPlay && _playerItem.status == AVPlayerItemStatusReadyToPlay) { 214 | if (_player) { 215 | [_player play]; 216 | _state = GQBaseVideoViewStatePlaying; 217 | return; 218 | } 219 | } 220 | _state = GQBaseVideoViewStateBuffer; 221 | } 222 | 223 | - (void)replace 224 | { 225 | [self.player.currentItem cancelPendingSeeks]; 226 | [self.player.currentItem.asset cancelLoading]; 227 | [self.player seekToTime:kCMTimeZero]; 228 | [self.player pause]; 229 | _state = GQBaseVideoViewStateStop; 230 | } 231 | 232 | - (void)stop 233 | { 234 | [self hideLoading]; 235 | [self.player replaceCurrentItemWithPlayerItem:nil]; 236 | _state = GQBaseVideoViewStateStop; 237 | } 238 | 239 | #pragma mark -- set get method 240 | 241 | - (void)setItem:(NSURL *)item 242 | { 243 | _state = GQBaseVideoViewStateConfigure; 244 | //判断之前播放的url地址是否一致 245 | if ([_item.absoluteString isEqualToString:item.absoluteString]&&_player.currentItem) 246 | { 247 | // //如果一致并且_playerItem的status是准备播放时就直接播放 248 | // if (_playerItem.status == AVPlayerItemStatusReadyToPlay&&_player.status == AVPlayerStatusReadyToPlay) 249 | // { 250 | // [self play]; 251 | // return; 252 | // } 253 | 254 | // 如果item一致就直接跳出,可以直接播放 255 | return; 256 | } 257 | 258 | // 移除观察者 259 | [self removeObserver]; 260 | // 释放播放器,用来解决重用导致的上次视频画面残留 261 | [self releasePlayer]; 262 | 263 | // 设置新的播放器和playerItem 264 | _item = [item copy]; 265 | 266 | [self showLoading]; 267 | 268 | //重置_playerItem 269 | _playerItem = [[AVPlayerItem alloc] initWithURL:_item]; 270 | 271 | _player = [AVPlayer playerWithPlayerItem:_playerItem]; 272 | self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player]; 273 | self.playerLayer.videoGravity = AVLayerVideoGravityResizeAspect; 274 | self.playerLayer.frame = self.layer.bounds; 275 | [self.layer insertSublayer:_playerLayer atIndex:0]; 276 | 277 | //添加KVO 278 | [self addObserver]; 279 | } 280 | 281 | @end 282 | -------------------------------------------------------------------------------- /GQImageVideoViewer/GQImageVideoScrollView.h: -------------------------------------------------------------------------------- 1 | // 2 | // GQImageVideoScrollView.h 3 | // GQImageVideoViewerDemo 4 | // 5 | // Created by 高旗 on 16/9/12. 6 | // Copyright © 2016年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "GQBaseImageVideoModel.h" 11 | 12 | @interface GQImageVideoScrollView : UIScrollView 13 | 14 | @property (nonatomic, copy) GQBaseImageVideoModel *data; 15 | 16 | @property(nonatomic,assign) NSInteger row; 17 | 18 | @property (nonatomic, copy) UIImage *placeholderImage; 19 | 20 | //暂停展示视频画面 21 | - (void)stopDisplay; 22 | 23 | //开始展示视频画面 24 | - (void)beginDisplay; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /GQImageVideoViewer/GQImageVideoScrollView.m: -------------------------------------------------------------------------------- 1 | // 2 | // GQImageVideoScrollView.m 3 | // GQImageVideoViewerDemo 4 | // 5 | // Created by 高旗 on 16/9/12. 6 | // Copyright © 2016年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import "GQImageVideoScrollView.h" 10 | #import "GQImageVideoViewer.h" 11 | #import "GQBaseImageView.h" 12 | #import "GQBaseVideoView.h" 13 | 14 | #import "UIImageView+GQImageDownloader.h" 15 | 16 | #import "GQImageCacheManager.h" 17 | 18 | #import "GQImageVideoViewerConst.h" 19 | 20 | @interface GQImageVideoScrollView(){ 21 | NSURL *_currentUrl; 22 | BOOL _isAddSubView; 23 | } 24 | 25 | @property (nonatomic, strong) GQBaseImageView *imageView; 26 | @property (nonatomic, strong) GQBaseVideoView *videoView; 27 | 28 | @end 29 | 30 | @implementation GQImageVideoScrollView 31 | 32 | - (id)initWithFrame:(CGRect)frame 33 | { 34 | self = [super initWithFrame:frame]; 35 | if (self) 36 | { 37 | self.backgroundColor = [UIColor clearColor]; 38 | 39 | _isAddSubView = NO; 40 | 41 | //设置最大放大倍数 42 | self.maximumZoomScale = 3.0; 43 | self.minimumZoomScale = 1.0; 44 | 45 | //隐藏滚动条 46 | self.showsHorizontalScrollIndicator = NO; 47 | self.showsVerticalScrollIndicator = NO; 48 | 49 | self.delegate = self; 50 | 51 | //单击手势 52 | UITapGestureRecognizer *tap1 = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapAction:)]; 53 | [self addGestureRecognizer:tap1]; 54 | 55 | //双击放大缩小手势 56 | UITapGestureRecognizer *tap2 = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapAction:)]; 57 | //双击 58 | tap2.numberOfTapsRequired = 2; 59 | //手指的数量 60 | tap2.numberOfTouchesRequired = 1; 61 | [self addGestureRecognizer:tap2]; 62 | 63 | //tap1、tap2两个手势同时响应时,则取消tap1手势 64 | [tap1 requireGestureRecognizerToFail:tap2]; 65 | } 66 | return self; 67 | } 68 | 69 | - (void)layoutSubviews { 70 | [super layoutSubviews]; 71 | if (self.zoomScale == 1) { 72 | _videoView.frame = self.bounds; 73 | _imageView.frame = self.bounds; 74 | } 75 | } 76 | 77 | - (void)stopDisplay 78 | { 79 | [_videoView replace]; 80 | } 81 | 82 | /** 83 | 开始播放 84 | */ 85 | - (void)beginDisplay 86 | { 87 | if (_currentUrl&&!_data.GQIsImageURL) 88 | { 89 | // [_videoView setItem:_currentUrl]; 90 | [_videoView play]; 91 | } 92 | } 93 | 94 | 95 | /** 96 | 准备视频展示的数据 97 | */ 98 | - (void)prepareVideoData { 99 | if(_videoView) { 100 | 101 | // 设置需要展示的数据 102 | [_videoView setItem:_currentUrl]; 103 | // 设置是否需要循环播放 104 | [_videoView setIsRepeat:_data.GQIsRepeat]; 105 | } 106 | 107 | } 108 | 109 | - (void)setData:(GQBaseImageVideoModel *)data 110 | { 111 | _data = [data copy]; 112 | if (!_isAddSubView) { 113 | [self addSubview:self.videoView]; 114 | [self addSubview:self.imageView]; 115 | _isAddSubView = YES; 116 | } 117 | id urlString = _data.GQURLString; 118 | UIImage *image = nil; 119 | _currentUrl = nil; 120 | if ([urlString isKindOfClass:[UIImage class]]) 121 | { 122 | image = urlString; 123 | }else if ([urlString isKindOfClass:[NSString class]]||[urlString isKindOfClass:[NSURL class]]) 124 | { 125 | _currentUrl = urlString; 126 | if ([urlString isKindOfClass:[NSString class]]) { 127 | _currentUrl = [NSURL URLWithString:urlString]; 128 | } 129 | }else if ([urlString isKindOfClass:[UIImageView class]]) 130 | { 131 | UIImageView *imageView = (UIImageView *)urlString; 132 | image = imageView.image; 133 | } 134 | if (_data.GQIsImageURL) { 135 | [_videoView setHidden:YES]; 136 | if (_currentUrl) { 137 | [_imageView showLoading]; 138 | GQWeakify(self); 139 | [_imageView loadImage:_currentUrl 140 | requestClassName:nil 141 | placeHolder:_placeholderImage 142 | progress:nil 143 | complete:^(UIImage *image, NSError *error, NSURL *imageUrl) { 144 | GQStrongify(self); 145 | [self.imageView hideLoading]; 146 | [self layoutSubviews]; 147 | }]; 148 | }else if(image){ 149 | _imageView.image = image; 150 | }else{ 151 | _imageView.image = nil; 152 | } 153 | }else{ 154 | _imageView.image = nil; 155 | [_videoView setHidden:NO]; 156 | 157 | [self prepareVideoData]; // 准备视频数据 158 | } 159 | } 160 | 161 | #pragma mark - UIScrollView delegate 162 | //返回需要缩放的子视图 163 | - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView 164 | { 165 | return self.data.GQIsImageURL ? _imageView : nil; 166 | } 167 | 168 | - (void)tapAction:(UITapGestureRecognizer *)tap 169 | { 170 | if (tap.numberOfTapsRequired == 1) 171 | { 172 | [_imageView cancelCurrentImageRequest]; 173 | [[GQImageVideoViewer sharedInstance] dissMiss]; 174 | } 175 | else if(tap.numberOfTapsRequired == 2 && self.data.GQIsImageURL) 176 | { 177 | if (self.zoomScale > 1) 178 | { 179 | [self setZoomScale:1 animated:YES]; 180 | } else 181 | { 182 | [self setZoomScale:3 animated:YES]; 183 | } 184 | } 185 | } 186 | 187 | - (void)dealloc 188 | { 189 | [[GQImageCacheManager sharedManager] clearMemoryCache]; 190 | [_videoView stop]; 191 | [_imageView cancelCurrentImageRequest]; 192 | _imageView = nil; 193 | _videoView = nil; 194 | } 195 | 196 | - (GQBaseImageView *)imageView { 197 | if (!_imageView) { 198 | _imageView = [[NSClassFromString(_data.GQImageViewClassName) alloc] initWithFrame:self.bounds]; 199 | //让图片等比例适应图片视图的尺寸 200 | _imageView.contentMode = UIViewContentModeScaleAspectFit; 201 | } 202 | return _imageView; 203 | } 204 | 205 | - (GQBaseVideoView *)videoView { 206 | if (!_videoView) { 207 | _videoView = [[NSClassFromString(_data.GQVideoViewClassName) alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)]; 208 | } 209 | return _videoView; 210 | } 211 | 212 | @end 213 | -------------------------------------------------------------------------------- /GQImageVideoViewer/GQImageVideoTableView.h: -------------------------------------------------------------------------------- 1 | // 2 | // GQImageVideoTableView.h 3 | // GQImageVideoViewerDemo 4 | // 5 | // Created by 高旗 on 16/9/12. 6 | // Copyright © 2016年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import "GQBaseTableView.h" 10 | 11 | @interface GQImageVideoTableView : GQBaseTableView 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /GQImageVideoViewer/GQImageVideoTableView.m: -------------------------------------------------------------------------------- 1 | // 2 | // GQImageVideoTableView.m 3 | // GQImageVideoViewerDemo 4 | // 5 | // Created by 高旗 on 16/9/12. 6 | // Copyright © 2016年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import "GQImageVideoTableView.h" 10 | #import "GQImageVideoScrollView.h" 11 | 12 | #import "GQImageVideoViewerConst.h" 13 | 14 | @interface GQImageVideoCollectionViewCell : UICollectionViewCell 15 | 16 | @end 17 | 18 | @implementation GQImageVideoCollectionViewCell 19 | 20 | - (instancetype)initWithFrame:(CGRect)frame { 21 | self = [super initWithFrame:frame]; 22 | if (self) { 23 | [self initView]; 24 | } 25 | return self; 26 | } 27 | 28 | - (void)initView { 29 | GQImageVideoScrollView *photoSV = [[GQImageVideoScrollView alloc] init]; 30 | self.backgroundColor = [UIColor clearColor]; 31 | photoSV.tag = 100; 32 | [self.contentView addSubview:photoSV]; 33 | } 34 | 35 | - (void)layoutSubviews { 36 | GQImageVideoScrollView *photoSV = (GQImageVideoScrollView *)[self.contentView viewWithTag:100]; 37 | photoSV.frame = self.bounds; 38 | } 39 | 40 | @end 41 | 42 | @interface GQImageVideoTableView() 43 | { 44 | NSIndexPath *currentIndexPath; 45 | BOOL isFirstLayout; 46 | } 47 | 48 | @end 49 | 50 | @implementation GQImageVideoTableView 51 | 52 | - (id)initWithFrame:(CGRect)frame collectionViewLayout:(nonnull UICollectionViewLayout *)layout 53 | { 54 | self = [super initWithFrame:frame collectionViewLayout:layout]; 55 | if (self) { 56 | [self registerClass:[GQImageVideoCollectionViewCell class] forCellWithReuseIdentifier:@"GQImageVideoScrollViewIdentify"]; 57 | self.pagingEnabled = YES; 58 | } 59 | return self; 60 | } 61 | 62 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath 63 | { 64 | static NSString *identify = @"GQImageVideoScrollViewIdentify"; 65 | 66 | GQImageVideoCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identify forIndexPath:indexPath]; 67 | 68 | GQImageVideoScrollView *photoSV = (GQImageVideoScrollView *)[cell.contentView viewWithTag:100]; 69 | 70 | photoSV.data = self.dataArray[indexPath.row]; 71 | 72 | photoSV.row = indexPath.row; 73 | 74 | if (isFirstLayout) { 75 | isFirstLayout = NO; 76 | [photoSV beginDisplay]; 77 | } 78 | 79 | return cell; 80 | } 81 | 82 | #pragma mark - UITableViewDelegate 83 | - (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(nonnull UICollectionViewCell *)cell forItemAtIndexPath:(nonnull NSIndexPath *)indexPath { 84 | GQImageVideoScrollView *photoSV = (GQImageVideoScrollView *)[cell.contentView viewWithTag:100]; 85 | 86 | [photoSV setZoomScale:1.0 animated:YES]; 87 | 88 | [photoSV stopDisplay]; 89 | } 90 | 91 | - (void)scrollToItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UICollectionViewScrollPosition)scrollPosition animated:(BOOL)animated { 92 | [super scrollToItemAtIndexPath:indexPath atScrollPosition:scrollPosition animated:animated]; 93 | //如果不存在currentIndexPath 94 | if (!currentIndexPath) 95 | { 96 | currentIndexPath = indexPath; 97 | }else if (currentIndexPath != indexPath)//如果currentIndexPath不等于indexPath则停止currentIndexPath的播放 98 | { 99 | UICollectionViewCell *cell = [self cellForItemAtIndexPath:currentIndexPath]; 100 | GQImageVideoScrollView *photoSV = (GQImageVideoScrollView *)[cell.contentView viewWithTag:100]; 101 | [photoSV stopDisplay]; 102 | currentIndexPath = indexPath; 103 | } 104 | //播放当前indexPath 105 | UICollectionViewCell *cell = [self cellForItemAtIndexPath:indexPath]; 106 | //如果指定的cell为空则说明是第一次展示,记录第一次 107 | if (!cell) { 108 | isFirstLayout = YES; 109 | }else { 110 | GQImageVideoScrollView *photoSV = (GQImageVideoScrollView *)[cell.contentView viewWithTag:100]; 111 | [photoSV beginDisplay]; 112 | } 113 | } 114 | 115 | @end 116 | 117 | -------------------------------------------------------------------------------- /GQImageVideoViewer/GQImageVideoViewer.h: -------------------------------------------------------------------------------- 1 | // 2 | // GQImageVideoViewer.h 3 | // GQImageVideoViewerDemo 4 | // 5 | // Created by 高旗 on 16/9/12. 6 | // Copyright © 2016年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "GQBaseImageVideoModel.h" 11 | 12 | typedef enum { 13 | GQLaunchDirectionBottom = 1,//从下往上推出 14 | GQLaunchDirectionTop, //从上往下推出 15 | GQLaunchDirectionLeft, //从左往右推出 16 | GQLaunchDirectionRight //从右往左推出 17 | }GQLaunchDirection; 18 | 19 | typedef enum { 20 | GQShowIndexTypeNone = 1, // 不显示下标 21 | GQShowIndexTypePageControl, // 以pageControl的形式显示 22 | GQShowIndexTypeLabel // 以文字样式显示 23 | 24 | }GQShowIndexType; 25 | 26 | typedef void (^GQAchieveIndexBlock)(NSInteger selectIndex);//获取当前图片的index的block 27 | typedef void (^GQDissMissAtIndexBlock)(NSInteger dissMissIndex);// 移除浏览器时的block 28 | 29 | @class GQImageVideoViewer; 30 | 31 | //链式调用block 32 | typedef GQImageVideoViewer * (^GQShowIndexTypeChain)(GQShowIndexType showIndexType); 33 | typedef GQImageVideoViewer * (^GQStringClassChain) (NSString *className); 34 | typedef GQImageVideoViewer * (^GQDataArrayChain)(NSArray *dataArray); 35 | typedef GQImageVideoViewer * (^GQSelectIndexChain)(NSInteger selectIndex); 36 | typedef GQImageVideoViewer * (^GQLaunchDirectionChain)(GQLaunchDirection launchDirection); 37 | typedef GQImageVideoViewer * (^GQAchieveIndexChain)(GQAchieveIndexBlock selectIndexBlock); 38 | typedef GQImageVideoViewer * (^GQDissMissAtIndexChain)(GQDissMissAtIndexBlock dissMissAtIndexBlock); 39 | typedef void (^GQShowViewChain)(UIView *showView); 40 | 41 | @interface GQImageVideoViewer : UIView 42 | 43 | #pragma mark -- 链式调用 44 | /** 45 | 自定义视频class名称 type : NSSting 46 | */ 47 | @property (nonatomic, copy, readonly) GQStringClassChain videoViewClassNameChain; 48 | 49 | /** 50 | 自定义图片浏览界面class名称 type : NSSting 51 | */ 52 | @property (nonatomic, copy, readonly) GQStringClassChain imageViewClassNameChain; 53 | 54 | /** 55 | * 显示PageControl传yes type : BOOL 56 | */ 57 | @property (nonatomic, copy, readonly) GQShowIndexTypeChain showIndexTypeChain; 58 | 59 | /** 60 | * 图片数组 type : NSArray * 61 | */ 62 | @property (nonatomic, copy, readonly) GQDataArrayChain dataArrayChain; 63 | 64 | /** 65 | * 选中的图片索引 type : NSInteger 66 | */ 67 | @property (nonatomic, copy, readonly) GQSelectIndexChain selectIndexChain; 68 | 69 | /** 70 | * 推出方向 type : GQLaunchDirection 71 | */ 72 | @property (nonatomic, copy, readonly) GQLaunchDirectionChain launchDirectionChain; 73 | 74 | /** 75 | * 显示GQImageViewer到指定view上 type: UIView * 76 | */ 77 | @property (nonatomic, copy, readonly) GQShowViewChain showViewChain; 78 | 79 | /** 80 | * 获取当前选中的图片index type: GQAchieveIndexBlock 81 | */ 82 | @property (nonatomic, copy, readonly) GQAchieveIndexChain achieveSelectIndexChain; 83 | 84 | /** 85 | * 移除浏览器时和移除时的index type: GQDissMissWithIndexBlock 86 | */ 87 | @property (nonatomic, copy, readonly) GQDissMissAtIndexChain dissMissAtIndexChain; 88 | #pragma mark -- 普通调用 89 | 90 | /* 91 | * 显示PageControl传yes 默认 : yes 92 | * 显示label就传no 93 | */ 94 | @property (nonatomic, assign) GQShowIndexType showIndexType; 95 | 96 | /** 97 | 自定义视频class名称 必须继承GQBaseVideoView 98 | */ 99 | @property (nonatomic, strong) NSString *videoViewClassName; 100 | 101 | /** 102 | 自定义图片浏览界面class名称 必须继承GQBaseImageView 103 | */ 104 | @property (nonatomic, strong) NSString *imageViewClassName; 105 | 106 | /** 107 | * 如果有网络图片则设置默认图片 108 | */ 109 | @property (nonatomic, copy) UIImage *placeholderImage; 110 | 111 | /** 112 | * 资源数组 113 | */ 114 | @property (nonatomic, copy) NSArray *dataArray;//资源数组 115 | 116 | /** 117 | * 获取当前选中的图片index 118 | */ 119 | @property (nonatomic, copy) GQAchieveIndexBlock achieveSelectIndex; 120 | 121 | /** 122 | * 移除浏览器时的index 123 | */ 124 | @property (copy, nonatomic) GQDissMissAtIndexBlock dissMissAtIndex; 125 | 126 | /** 127 | * 选中的图片索引 128 | */ 129 | @property(nonatomic,assign) NSInteger selectIndex; 130 | 131 | /** 132 | * 推出方向 默认GQLaunchDirectionBottom 133 | */ 134 | @property (nonatomic) GQLaunchDirection laucnDirection; 135 | 136 | /** 137 | * 显示GQImageViewer到指定view上 138 | * 139 | * @param showView view 140 | */ 141 | - (void)showInView:(UIView *)showView; 142 | 143 | /** 144 | * 单例方法 145 | * 146 | * @return GQImageViewer 147 | */ 148 | + (GQImageVideoViewer *)sharedInstance; 149 | 150 | /** 151 | * 取消显示 152 | */ 153 | - (void)dissMiss; 154 | 155 | @end 156 | -------------------------------------------------------------------------------- /GQImageVideoViewer/GQImageVideoViewer.m: -------------------------------------------------------------------------------- 1 | // 2 | // GQImageVideoViewer.m 3 | // GQImageVideoViewerDemo 4 | // 5 | // Created by 高旗 on 16/9/12. 6 | // Copyright © 2016年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import "GQImageVideoViewer.h" 10 | 11 | #import "GQImageVideoViewer.h" 12 | #import "GQImageVideoTableView.h" 13 | #import "GQImageVideoViewerConst.h" 14 | 15 | static NSInteger pageNumberTag = 10086; 16 | 17 | @interface GQImageVideoViewer() 18 | { 19 | GQImageVideoTableView *_tableView;//tableview 20 | UIPageControl *_pageControl;//页码显示control 21 | UILabel *_pageLabel;//页码显示label 22 | CGRect _superViewRect;//superview的rect 23 | CGRect _initialRect;//初始化rect 24 | } 25 | 26 | @property (nonatomic, assign) BOOL isVisible;//是否正在显示 27 | 28 | @end 29 | 30 | @implementation GQImageVideoViewer 31 | 32 | __strong static GQImageVideoViewer *imageVideoViewerManager; 33 | + (GQImageVideoViewer *)sharedInstance 34 | { 35 | static dispatch_once_t onceToken = 0; 36 | 37 | dispatch_once(&onceToken, ^{ 38 | imageVideoViewerManager = [[super allocWithZone:nil] init]; 39 | }); 40 | return imageVideoViewerManager; 41 | } 42 | 43 | + (id)allocWithZone:(NSZone *)zone 44 | { 45 | return [self sharedInstance]; 46 | } 47 | 48 | - (id)copyWithZone:(NSZone*)zone 49 | { 50 | return self; 51 | } 52 | 53 | //初始化,不可重复调用 54 | - (instancetype)initWithFrame:(CGRect)frame 55 | { 56 | NSAssert(!imageVideoViewerManager, @"init method can't call"); 57 | self = [super initWithFrame:frame]; 58 | if (self) { 59 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(statusBarOrientationChange:) name:UIDeviceOrientationDidChangeNotification object:nil]; 60 | self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.3]; 61 | [self setClipsToBounds:YES]; 62 | self.laucnDirection = GQLaunchDirectionBottom; 63 | self.showIndexType = GQShowIndexTypeLabel; 64 | } 65 | return self; 66 | } 67 | 68 | @synthesize videoViewClassNameChain = _videoViewClassNameChain; 69 | @synthesize imageViewClassNameChain = _imageViewClassNameChain; 70 | @synthesize showIndexTypeChain = _showIndexTypeChain; 71 | @synthesize dataArrayChain = _dataArrayChain; 72 | @synthesize selectIndexChain = _selectIndexChain; 73 | @synthesize showViewChain = _showViewChain; 74 | @synthesize launchDirectionChain = _launchDirectionChain; 75 | @synthesize achieveSelectIndexChain = _achieveSelectIndexChain; 76 | @synthesize dissMissAtIndexChain = _dissMissAtIndexChain; 77 | 78 | GQChainObjectDefine(videoViewClassNameChain, VideoViewClassName, NSString *, GQStringClassChain); 79 | GQChainObjectDefine(imageViewClassNameChain, ImageViewClassName, NSString *, GQStringClassChain); 80 | GQChainObjectDefine(showIndexTypeChain, ShowIndexType, GQShowIndexType, GQShowIndexTypeChain); 81 | GQChainObjectDefine(dataArrayChain, DataArray, NSArray *, GQDataArrayChain); 82 | GQChainObjectDefine(selectIndexChain, SelectIndex, NSInteger, GQSelectIndexChain); 83 | GQChainObjectDefine(launchDirectionChain, LaucnDirection, GQLaunchDirection, GQLaunchDirectionChain); 84 | GQChainObjectDefine(achieveSelectIndexChain, AchieveSelectIndex, GQAchieveIndexBlock, GQAchieveIndexChain); 85 | GQChainObjectDefine(dissMissAtIndexChain, DissMissAtIndex, GQDissMissAtIndexBlock, GQDissMissAtIndexChain); 86 | 87 | - (GQShowViewChain)showViewChain 88 | { 89 | if (!_showViewChain) { 90 | GQWeakify(self); 91 | _showViewChain = ^(UIView *showView){ 92 | GQStrongify(self); 93 | [self showInView:showView]; 94 | }; 95 | } 96 | return _showViewChain; 97 | } 98 | 99 | #pragma mark -- set method 100 | 101 | - (void)setImageViewClassName:(NSString *)imageViewClassName { 102 | NSAssert(_dataArray == nil, @"_imageViewClassName must be set earlier than _dataArray"); 103 | 104 | NSAssert(!_isVisible, @"GQImageVideoViewer can not be set _imageViewClassName in the display"); 105 | 106 | if (_imageViewClassName) { 107 | _imageViewClassName = nil; 108 | } 109 | _imageViewClassName = [imageViewClassName copy]; 110 | } 111 | 112 | - (void)setVideoViewClassName:(NSString *)videoViewClassName { 113 | NSAssert(_dataArray == nil, @"_imageViewClassName must be set earlier than _dataArray"); 114 | 115 | NSAssert(!_isVisible, @"GQImageVideoViewer can not be set _imageViewClassName in the display"); 116 | 117 | if (_videoViewClassName) { 118 | _videoViewClassName = nil; 119 | } 120 | _videoViewClassName = [videoViewClassName copy]; 121 | } 122 | 123 | - (void)setShowIndexType:(GQShowIndexType)showIndexType { 124 | _showIndexType = showIndexType; 125 | [self updateNumberView]; 126 | } 127 | 128 | - (void)setDataArray:(NSArray *)dataArray 129 | { 130 | _dataArray = [[self handleImageUrlArray:dataArray] copy]; 131 | if (!_isVisible) { 132 | return; 133 | } 134 | 135 | NSAssert([_dataArray count] > 0, @"imageArray count must be greater than zero"); 136 | 137 | if (_selectIndex>[_dataArray count]-1&&[_dataArray count]>0){ 138 | _selectIndex = [_dataArray count]-1; 139 | 140 | [self updatePageNumber]; 141 | [self scrollToSettingIndex]; 142 | } 143 | _tableView.dataArray = [_dataArray copy]; 144 | } 145 | 146 | - (void)setSelectIndex:(NSInteger)selectIndex 147 | { 148 | if (_selectIndex == selectIndex) { 149 | return; 150 | } 151 | _selectIndex = selectIndex; 152 | if (!_isVisible) { 153 | return; 154 | } 155 | 156 | NSAssert(selectIndex>=0, @"_selectIndex must be greater than zero"); 157 | NSAssert([_dataArray count] > 0, @"imageArray count must be greater than zero"); 158 | 159 | if (selectIndex>[_dataArray count]-1){ 160 | _selectIndex = [_dataArray count]-1; 161 | }else if (selectIndex < 0){ 162 | _selectIndex = 0; 163 | } 164 | 165 | [self updatePageNumber]; 166 | [self scrollToSettingIndex]; 167 | } 168 | 169 | - (void)showInView:(UIView *)showView 170 | { 171 | if ([_dataArray count]==0) { 172 | return; 173 | } 174 | 175 | if (_isVisible) { 176 | [self dissMiss]; 177 | return; 178 | }else{ 179 | _isVisible = YES; 180 | } 181 | 182 | //设置superview的rect 183 | _superViewRect = showView.bounds; 184 | 185 | //初始化子view 186 | [self initSubViews]; 187 | 188 | //更新初始化rect 189 | [self updateInitialRect]; 190 | 191 | //设置初始值 192 | self.alpha = 0; 193 | self.frame = _initialRect; 194 | 195 | [showView addSubview:self]; 196 | 197 | [UIView animateWithDuration:0.3 198 | animations:^{ 199 | self.alpha = 1; 200 | self.frame = _superViewRect; 201 | }]; 202 | } 203 | 204 | //view消失 205 | - (void)dissMiss 206 | { 207 | // view消失的回调 208 | if (self.dissMissAtIndex) { 209 | self.dissMissAtIndex(_selectIndex); 210 | } 211 | 212 | [UIView animateWithDuration:0.3 213 | animations:^{ 214 | self.alpha = 0; 215 | self.frame = _initialRect; 216 | } completion:^(BOOL finished) { 217 | [self.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)]; 218 | [self removeFromSuperview]; 219 | _tableView = nil; 220 | _dataArray = nil; 221 | 222 | _videoViewClassName = nil; 223 | _imageViewClassName = nil; 224 | _isVisible = NO; 225 | }]; 226 | } 227 | 228 | #pragma mark -- privateMethod 229 | //屏幕旋转通知 230 | - (void)statusBarOrientationChange:(NSNotification *)noti { 231 | if (_isVisible) { 232 | _superViewRect = self.superview.bounds; 233 | [self orientationChange]; 234 | } 235 | } 236 | 237 | //屏幕旋转调整frame 238 | - (void)orientationChange { 239 | self.frame = _superViewRect; 240 | _tableView.frame = _superViewRect; 241 | [self updateInitialRect]; 242 | } 243 | 244 | //初始化子view 245 | - (void)initSubViews { 246 | [self updateNumberView]; 247 | if (!_tableView) { 248 | _tableView = [[GQImageVideoTableView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(_superViewRect) ,CGRectGetHeight(_superViewRect)) collectionViewLayout:[UICollectionViewLayout new]]; 249 | GQWeakify(self); 250 | _tableView.block = ^(NSInteger index){ 251 | GQStrongify(self); 252 | self->_selectIndex = index; 253 | [self updatePageNumber]; 254 | }; 255 | } 256 | [self insertSubview:_tableView atIndex:0]; 257 | 258 | //将所有的图片url赋给tableView显示 259 | _tableView.dataArray = [_dataArray copy]; 260 | 261 | [self scrollToSettingIndex]; 262 | } 263 | 264 | //更新初始化rect 265 | - (void)updateInitialRect{ 266 | switch (_laucnDirection) { 267 | case GQLaunchDirectionBottom:{ 268 | _initialRect = CGRectMake(0, CGRectGetHeight(_superViewRect), CGRectGetWidth(_superViewRect), 0); 269 | break; 270 | } 271 | case GQLaunchDirectionTop:{ 272 | _initialRect = CGRectMake(0, 0, CGRectGetWidth(_superViewRect), 0); 273 | break; 274 | } 275 | case GQLaunchDirectionLeft:{ 276 | _initialRect = CGRectMake(0, 0, 0, CGRectGetHeight(_superViewRect)); 277 | break; 278 | } 279 | case GQLaunchDirectionRight:{ 280 | _initialRect = CGRectMake(CGRectGetWidth(_superViewRect), 0, 0, CGRectGetHeight(_superViewRect)); 281 | break; 282 | } 283 | default: 284 | break; 285 | } 286 | } 287 | 288 | //更新页面显示view 289 | - (void)updateNumberView 290 | { 291 | [[self viewWithTag:pageNumberTag] removeFromSuperview]; 292 | 293 | switch (_showIndexType) { 294 | case GQShowIndexTypeNone: 295 | { 296 | // 不显示下标 297 | } 298 | break; 299 | case GQShowIndexTypePageControl: 300 | { 301 | _pageControl = [[UIPageControl alloc] initWithFrame:CGRectMake(0, CGRectGetHeight(_superViewRect)- (10 + GQTabSafeHeight), 0, 10)]; 302 | _pageControl.numberOfPages = _dataArray.count; 303 | _pageControl.tag = pageNumberTag; 304 | _pageControl.currentPage = _selectIndex; 305 | [self insertSubview:_pageControl atIndex:1]; 306 | } 307 | break; 308 | case GQShowIndexTypeLabel: 309 | default:{ 310 | _pageLabel = [[UILabel alloc] initWithFrame:CGRectMake(CGRectGetWidth(_superViewRect)/2 - 30, CGRectGetHeight(_superViewRect) - (20 + GQTabSafeHeight), 60, 15)]; 311 | _pageLabel.textAlignment = NSTextAlignmentCenter; 312 | _pageLabel.tag = pageNumberTag; 313 | _pageLabel.text = [NSString stringWithFormat:@"%zd/%zd",(_selectIndex+1),_dataArray.count]; 314 | _pageLabel.textColor = [UIColor whiteColor]; 315 | [self insertSubview:_pageLabel atIndex:1]; 316 | } 317 | break; 318 | } 319 | 320 | [self updatePageNumber]; 321 | } 322 | 323 | //更新页码 324 | - (void)updatePageNumber 325 | { 326 | if (self.achieveSelectIndex) { 327 | self.achieveSelectIndex(_selectIndex); 328 | } 329 | switch (_showIndexType) { 330 | case GQShowIndexTypeNone: 331 | { 332 | // 不设置下标 333 | } 334 | break; 335 | case GQShowIndexTypePageControl: 336 | { 337 | _pageControl.currentPage = self.selectIndex; 338 | 339 | } 340 | break; 341 | case GQShowIndexTypeLabel: 342 | default:{ 343 | _pageLabel.text = [NSString stringWithFormat:@"%zd/%zd",(_selectIndex+1),_dataArray.count]; 344 | 345 | } 346 | break; 347 | } 348 | } 349 | 350 | - (void)scrollToSettingIndex 351 | { 352 | //滚动到指定的单元格 353 | if (_tableView) { 354 | NSIndexPath *indexPath = [NSIndexPath indexPathForRow:_selectIndex inSection:0]; 355 | [_tableView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionNone animated:NO]; 356 | } 357 | } 358 | 359 | //图片处理 360 | - (NSArray *)handleImageUrlArray:(NSArray *)imageURlArray{ 361 | NSMutableArray *handleImages = [[NSMutableArray alloc] initWithCapacity:[imageURlArray count]]; 362 | for (int i = 0 ; i < [imageURlArray count]; i++) { 363 | id imageObject = imageURlArray[i]; 364 | //如果不是GQBaseImageVideoModel类和NSDictionary类,就默认为图片资源; 365 | if (![imageObject isKindOfClass:[GQBaseImageVideoModel class]]&&![imageObject isKindOfClass:[NSDictionary class]]) { 366 | imageObject = [@{GQURLString:imageObject,GQIsImageURL:@(YES),GQIsRepeat:@(YES),GQVideoViewClassName:_videoViewClassName?:@"GQBaseVideoView",GQImageViewClassName:_imageViewClassName?:@"GQBaseImageView"} copy]; 367 | }else if ([imageObject isKindOfClass:[NSDictionary class]]) { 368 | NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] initWithDictionary:imageObject]; 369 | [dictionary addEntriesFromDictionary:@{GQVideoViewClassName:_videoViewClassName?:@"GQBaseVideoView",GQImageViewClassName:_imageViewClassName?:@"GQBaseImageView"}]; 370 | // 补充视频是否需要重复播放参数,默认YES 371 | if (![[dictionary allKeys] containsObject:GQIsRepeat]) { 372 | 373 | [dictionary addEntriesFromDictionary:@{GQIsRepeat:@(YES)}]; 374 | } 375 | 376 | imageObject = dictionary; 377 | } 378 | //如果为NSDictionary类,则改装成GQBaseImageVideoModel类 379 | if ([imageObject isKindOfClass:[NSDictionary class]]) { 380 | imageObject = [[GQBaseImageVideoModel alloc] initWithDataDic:imageObject]; 381 | } 382 | [handleImages addObject:imageObject]; 383 | } 384 | return handleImages; 385 | } 386 | 387 | //清除通知,防止崩溃 388 | - (void)dealloc{ 389 | [[NSNotificationCenter defaultCenter] removeObserver:self name:UIDeviceOrientationDidChangeNotification object:nil]; 390 | } 391 | 392 | @end 393 | -------------------------------------------------------------------------------- /GQImageVideoViewer/GQImageVideoViewerConst.h: -------------------------------------------------------------------------------- 1 | // 2 | // GQImageVideoViewerConst.h 3 | // GQImageVideoViewerDemo 4 | // 5 | // Created by 高旗 on 16/9/12. 6 | // Copyright © 2016年 gaoqi. All rights reserved. 7 | // 8 | 9 | #ifndef GQImageVideoViewerConst_h 10 | #define GQImageVideoViewerConst_h 11 | 12 | #import 13 | 14 | #define GQOBJECT_SINGLETON_BOILERPLATE(_object_name_, _shared_obj_name_) \ 15 | static _object_name_ *z##_shared_obj_name_ = nil; \ 16 | + (_object_name_ *)_shared_obj_name_ { \ 17 | @synchronized(self) { \ 18 | if (z##_shared_obj_name_ == nil) { \ 19 | static dispatch_once_t done; \ 20 | dispatch_once(&done, ^{ z##_shared_obj_name_ \ 21 | = [[super allocWithZone:nil] init]; }); \ 22 | } \ 23 | } \ 24 | return z##_shared_obj_name_; \ 25 | } \ 26 | + (id)allocWithZone:(NSZone *)zone { \ 27 | @synchronized(self) { \ 28 | if (z##_shared_obj_name_ == nil) { \ 29 | z##_shared_obj_name_ = [super allocWithZone:NULL]; \ 30 | return z##_shared_obj_name_; \ 31 | } \ 32 | } \ 33 | return nil; \ 34 | } \ 35 | - (id)copyWithZone:(NSZone*)zone \ 36 | { \ 37 | return z##_shared_obj_name_; \ 38 | } 39 | 40 | 41 | #define GQChainObjectDefine(_key_name_,_Chain_, _type_ , _block_type_) \ 42 | - (_block_type_)_key_name_ \ 43 | { \ 44 | __weak typeof(self) weakSelf = self; \ 45 | if (!_##_key_name_) { \ 46 | _##_key_name_ = ^(_type_ value){ \ 47 | __strong typeof(weakSelf) strongSelf = weakSelf; \ 48 | [strongSelf set##_Chain_:value]; \ 49 | return strongSelf; \ 50 | }; \ 51 | } \ 52 | return _##_key_name_; \ 53 | } 54 | 55 | 56 | #define GQ_DYNAMIC_PROPERTY_BOOL(_getter_, _setter_) \ 57 | - (void)_setter_:(BOOL)object{ \ 58 | [self willChangeValueForKey:@#_getter_]; \ 59 | objc_setAssociatedObject(self, _cmd, @(object), OBJC_ASSOCIATION_ASSIGN); \ 60 | [self didChangeValueForKey:@#_getter_]; \ 61 | } \ 62 | -(BOOL)_getter_{ \ 63 | return [objc_getAssociatedObject(self, @selector(_setter_:)) boolValue]; \ 64 | } \ 65 | 66 | //强弱引用 67 | #ifndef GQWeakify 68 | #define GQWeakify(object) __weak __typeof__(object) weak##_##object = object 69 | #endif 70 | 71 | #ifndef GQStrongify 72 | #define GQStrongify(object) __typeof__(object) object = weak##_##object 73 | #endif 74 | 75 | // iPhone X 适配 76 | #define GQIsIPhoneX ([UIScreen mainScreen].bounds.size.height == 812) 77 | 78 | #define GQTabSafeHeight ((GQIsIPhoneX) ? (34.0) : (0.0)) 79 | 80 | #endif /* GQImageVideoViewerConst_h */ 81 | -------------------------------------------------------------------------------- /GQImageVideoViewerDemo/GQImageVideoViewerDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /GQImageVideoViewerDemo/GQImageVideoViewerDemo.xcodeproj/project.xcworkspace/xcuserdata/gaoqi.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g763007297/GQImageVideoViewer/c73793e879e789928be8e9b201946a5dad569032/GQImageVideoViewerDemo/GQImageVideoViewerDemo.xcodeproj/project.xcworkspace/xcuserdata/gaoqi.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /GQImageVideoViewerDemo/GQImageVideoViewerDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // GQImageVideoViewerDemo 4 | // 5 | // Created by 高旗 on 16/9/12. 6 | // Copyright © 2016年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /GQImageVideoViewerDemo/GQImageVideoViewerDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // GQImageVideoViewerDemo 4 | // 5 | // Created by 高旗 on 16/9/12. 6 | // Copyright © 2016年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "DemoViewController.h" 11 | 12 | @interface AppDelegate () 13 | 14 | @end 15 | 16 | @implementation AppDelegate 17 | 18 | 19 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 20 | // Override point for customization after application launch. 21 | 22 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 23 | self.window.backgroundColor = [UIColor whiteColor]; 24 | 25 | DemoViewController *imageViewer = [[DemoViewController alloc] init]; 26 | UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:imageViewer]; 27 | self.window.rootViewController = nav; 28 | 29 | [self.window makeKeyAndVisible]; 30 | 31 | return YES; 32 | } 33 | - (void)applicationWillResignActive:(UIApplication *)application { 34 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 35 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 36 | } 37 | 38 | - (void)applicationDidEnterBackground:(UIApplication *)application { 39 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 40 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 41 | } 42 | 43 | - (void)applicationWillEnterForeground:(UIApplication *)application { 44 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 45 | } 46 | 47 | - (void)applicationDidBecomeActive:(UIApplication *)application { 48 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 49 | } 50 | 51 | - (void)applicationWillTerminate:(UIApplication *)application { 52 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /GQImageVideoViewerDemo/GQImageVideoViewerDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /GQImageVideoViewerDemo/GQImageVideoViewerDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /GQImageVideoViewerDemo/GQImageVideoViewerDemo/DemoViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // DemoViewController.h 3 | // GQImageVideoViewerDemo 4 | // 5 | // Created by 高旗 on 16/9/12. 6 | // Copyright © 2016年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface DemoViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /GQImageVideoViewerDemo/GQImageVideoViewerDemo/DemoViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // DemoViewController.m 3 | // GQImageVideoViewerDemo 4 | // 5 | // Created by 高旗 on 16/9/12. 6 | // Copyright © 2016年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import "DemoViewController.h" 10 | #import "GQImageVideoViewer.h" 11 | 12 | #import 13 | 14 | @interface DemoViewController () 15 | 16 | @property (nonatomic, strong) WKWebView *webView; 17 | 18 | @end 19 | 20 | @implementation DemoViewController 21 | 22 | - (void)viewDidLoad { 23 | [super viewDidLoad]; 24 | self.view.backgroundColor = [UIColor whiteColor]; 25 | } 26 | 27 | - (void)viewWillAppear:(BOOL)animated{ 28 | [super viewWillAppear:animated]; 29 | self.navigationController.visibleViewController.title = @"ImageViewer"; 30 | 31 | UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; 32 | [button setFrame:CGRectMake(CGRectGetMaxX(self.view.frame)/2-100, CGRectGetMaxY(self.view.frame)/2+140, 200, 40)]; 33 | [button setTitle:@"点击此处查看图片和视频" forState:UIControlStateNormal]; 34 | [button setTitleColor:[UIColor redColor] forState:UIControlStateNormal]; 35 | 36 | button.layer.borderColor = [UIColor orangeColor].CGColor; 37 | button.layer.borderWidth = 1; 38 | 39 | button.layer.cornerRadius = 5; 40 | [button setClipsToBounds:YES]; 41 | 42 | [button addTarget:self action:@selector(showImageViewer:) forControlEvents:UIControlEventTouchUpInside]; 43 | [self.view addSubview:button]; 44 | 45 | 46 | [self.view addSubview:self.webView]; 47 | NSString *string = @"https://v.qq.com/iframe/player.html?vid=g0023ffgekb&tiny=0&auto=0"; 48 | // NSString *HTMLString = [NSString stringWithFormat:@"",string]; 49 | // [self.webView loadHTMLString:HTMLString baseURL:nil]; 50 | NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:string]]; 51 | [self.webView loadRequest:request]; 52 | } 53 | 54 | - (void)showImageViewer:(id)sender{ 55 | NSMutableArray *imageArray = [[NSMutableArray alloc] initWithCapacity:0]; 56 | for (int i = 1; i <11; i ++) { 57 | NSString *fromPath = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"%d.jpg",i] ofType:nil]; 58 | NSData *data = [NSData dataWithContentsOfFile:fromPath]; 59 | [imageArray addObject:@{GQIsImageURL:@(YES), 60 | GQURLString:[UIImage imageWithData:data]}]; 61 | } 62 | NSURL *url = [NSURL URLWithString:@"http://static.tripbe.com/videofiles/20121214/9533522808.f4v.mp4"]; 63 | [imageArray addObjectsFromArray:@[@{GQIsImageURL:@(NO), 64 | GQURLString:[NSURL URLWithString:@"http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"]}, 65 | @{GQIsImageURL:@(NO), 66 | GQURLString:[NSURL URLWithString:@"http://res.jiuyan.info/in_promo/20160627_meinan/video/tang.mp4"]}, 67 | @{GQIsImageURL:@(NO), 68 | GQURLString:url}, 69 | @{GQIsImageURL:@(YES), 70 | GQURLString:@"http://cdn.cocimg.com/bbs/attachment/upload/30/5811301473150224.gif"}, 71 | @{GQIsImageURL:@(YES), 72 | GQURLString:@"http://img0.imgtn.bdimg.com/it/u=513437991,1334115219&fm=206&gp=0.jpg"}, 73 | @{GQIsImageURL:@(NO), 74 | GQIsRepeat:@(NO), 75 | GQURLString:@"https://download.91playmate.cn/5a2d71209c3250e5f073fcecbcbe7eab.mp4"}, 76 | @{GQIsImageURL:@(YES), 77 | GQURLString:@"http://desk.fd.zol-img.com.cn/t_s960x600c5/g4/M00/0D/01/Cg-4y1ULoXCII6fEAAeQFx3fsKgAAXCmAPjugYAB5Av166.jpg"}, 78 | @{GQIsImageURL:@(YES), 79 | GQURLString:@"http://desk.fd.zol-img.com.cn/t_s960x600c5/g5/M00/05/0F/ChMkJ1erCriIJ_opAAY8rSwt72wAAUU6gMmHKwABjzF444.jpgg"}, 80 | @{GQIsImageURL:@(YES), 81 | GQURLString:@"http://desk.fd.zol-img.com.cn/t_s960x600c5/g5/M00/02/00/ChMkJ1bKxCSIRtwrAA2uHQvukJIAALHCALaz_UADa41063.jpg"}, 82 | @{GQIsImageURL:@(YES), 83 | GQURLString:@"http://game2.1332255.com:80/group1/M00/00/0A/Cj2sWVhYtG6AOE4pAFfzq2lUi7E423.gif"}, 84 | @{GQIsImageURL:@(YES), 85 | GQURLString:@"http://desk.fd.zol-img.com.cn/t_s960x600c5/g5/M00/07/0D/ChMkJlgaksOIEZcSAAYHVJbTdlwAAXcSwNDVmYABgds319.jpg"}, 86 | @{GQIsImageURL:@(YES), 87 | GQURLString:@"http://desk.fd.zol-img.com.cn/t_s960x600c5/g5/M00/02/03/ChMkJlbKxtqIF93BABJ066MJkLcAALHrQL_qNkAEnUD253.jpg"}, 88 | @{GQIsImageURL:@(YES), 89 | GQURLString:@"http://image101.360doc.com/DownloadImg/2016/11/0404/83709873_1.jpg"}, 90 | @{GQIsImageURL:@(YES), 91 | GQURLString:@"http://image101.360doc.com/DownloadImg/2016/11/0404/83709873_8.jpg"}, 92 | @{GQIsImageURL:@(YES), 93 | GQURLString:@"http://game2.1332255.com:80/group1/M00/00/0A/Cj2sWVhYtGeAQV15ACz1-KrKcsE448.gif"} 94 | ]]; 95 | 96 | // 基本调用 97 | // [[GQImageVideoViewer sharedInstance] setImageArray:imageArray]; 98 | // [GQImageVideoViewer sharedInstance].usePageControl = YES; 99 | // [GQImageVideoViewer sharedInstance].selectIndex = 6; 100 | // [GQImageVideoViewer sharedInstance].achieveSelectIndex = ^(NSInteger selectIndex){ 101 | // NSLog(@"%ld",selectIndex); 102 | // }; 103 | // [GQImageVideoViewer sharedInstance].laucnDirection = GQLaunchDirectionRight; 104 | // [[GQImageVideoViewer sharedInstance] showInView:self.navigationController.view]; 105 | 106 | // 链式调用 107 | [GQImageVideoViewer sharedInstance] 108 | .videoViewClassNameChain(@"GQDemoVideoView") 109 | .dataArrayChain(imageArray) 110 | .showIndexTypeChain(GQShowIndexTypeLabel) 111 | .selectIndexChain(15) 112 | .achieveSelectIndexChain(^(NSInteger selectIndex){ 113 | NSLog(@"%zd",selectIndex); 114 | }) 115 | .launchDirectionChain(GQLaunchDirectionRight) 116 | .showViewChain(self.navigationController.view); 117 | } 118 | 119 | - (WKWebView *)webView { 120 | if (!_webView) { 121 | WKWebViewConfiguration *configure = [[WKWebViewConfiguration alloc] init]; 122 | configure.allowsPictureInPictureMediaPlayback = YES; 123 | configure.mediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypeAll; 124 | configure.allowsAirPlayForMediaPlayback = YES; 125 | _webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 60, 320, 300) configuration:configure]; 126 | _webView.navigationDelegate = self; 127 | } 128 | return _webView; 129 | } 130 | 131 | - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler 132 | { 133 | switch (navigationAction.navigationType) { 134 | case WKNavigationTypeLinkActivated: 135 | NSLog(@"WKNavigationTypeLinkActivated"); 136 | break; 137 | case WKNavigationTypeFormSubmitted: 138 | NSLog(@"WKNavigationTypeFormSubmitted"); 139 | break; 140 | case WKNavigationTypeOther: 141 | { 142 | NSLog(@"WKNavigationTypeOther"); 143 | break; 144 | } 145 | case WKNavigationTypeBackForward: 146 | { 147 | NSLog(@"WKNavigationTypeBackForward"); 148 | break; 149 | } 150 | case WKNavigationTypeReload: 151 | NSLog(@"WKNavigationTypeReload"); 152 | break; 153 | case WKNavigationTypeFormResubmitted: 154 | NSLog(@"WKNavigationTypeFormResubmitted"); 155 | break; 156 | default: 157 | break; 158 | } 159 | 160 | decisionHandler(WKNavigationActionPolicyAllow); 161 | } 162 | 163 | @end 164 | -------------------------------------------------------------------------------- /GQImageVideoViewerDemo/GQImageVideoViewerDemo/GQDemoVideoView.h: -------------------------------------------------------------------------------- 1 | // 2 | // GQDemoVideoView.h 3 | // GQImageVideoViewerDemo 4 | // 5 | // Created by tusm on 17/2/8. 6 | // Copyright © 2017年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import "GQBaseVideoView.h" 10 | 11 | @interface GQDemoVideoView : GQBaseVideoView 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /GQImageVideoViewerDemo/GQImageVideoViewerDemo/GQDemoVideoView.m: -------------------------------------------------------------------------------- 1 | // 2 | // GQDemoVideoView.m 3 | // GQImageVideoViewerDemo 4 | // 5 | // Created by tusm on 17/2/8. 6 | // Copyright © 2017年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import "GQDemoVideoView.h" 10 | 11 | @interface GQDemoVideoView() 12 | 13 | @property (nonatomic, strong) UIButton *button; 14 | 15 | @end 16 | 17 | @implementation GQDemoVideoView 18 | 19 | - (void)configureVideoView { 20 | [self addSubview:self.button]; 21 | } 22 | 23 | - (void)buttonAction:(id)sender { 24 | if (self.state == GQBaseVideoViewStatePlaying) { 25 | [self puase]; 26 | }else { 27 | [self play]; 28 | } 29 | } 30 | 31 | #pragma mark -- lazy load 32 | 33 | - (UIButton *)button { 34 | if (!_button) { 35 | _button = [UIButton buttonWithType:UIButtonTypeCustom]; 36 | _button.frame = CGRectMake(([UIScreen mainScreen].bounds.size.width-150)/2, 20, 150, 30); 37 | [_button setBackgroundColor:[UIColor yellowColor]]; 38 | [_button setTitle:@"点击播放暂停" forState:UIControlStateNormal]; 39 | [_button setTitleColor:[UIColor redColor] forState:UIControlStateNormal]; 40 | [_button addTarget:self action:@selector(buttonAction:) forControlEvents:UIControlEventTouchUpInside]; 41 | } 42 | return _button; 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /GQImageVideoViewerDemo/GQImageVideoViewerDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | 仿微信小视频图片浏览器 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | NSAllowsArbitraryLoads 30 | 31 | 32 | UILaunchStoryboardName 33 | LaunchScreen 34 | UIRequiredDeviceCapabilities 35 | 36 | armv7 37 | 38 | UIStatusBarTintParameters 39 | 40 | UINavigationBar 41 | 42 | Style 43 | UIBarStyleDefault 44 | Translucent 45 | 46 | 47 | 48 | UISupportedInterfaceOrientations 49 | 50 | UIInterfaceOrientationPortrait 51 | UIInterfaceOrientationLandscapeLeft 52 | UIInterfaceOrientationLandscapeRight 53 | 54 | UISupportedInterfaceOrientations~ipad 55 | 56 | UIInterfaceOrientationPortrait 57 | UIInterfaceOrientationPortraitUpsideDown 58 | UIInterfaceOrientationLandscapeLeft 59 | UIInterfaceOrientationLandscapeRight 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /GQImageVideoViewerDemo/GQImageVideoViewerDemo/image/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g763007297/GQImageVideoViewer/c73793e879e789928be8e9b201946a5dad569032/GQImageVideoViewerDemo/GQImageVideoViewerDemo/image/1.jpg -------------------------------------------------------------------------------- /GQImageVideoViewerDemo/GQImageVideoViewerDemo/image/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g763007297/GQImageVideoViewer/c73793e879e789928be8e9b201946a5dad569032/GQImageVideoViewerDemo/GQImageVideoViewerDemo/image/10.jpg -------------------------------------------------------------------------------- /GQImageVideoViewerDemo/GQImageVideoViewerDemo/image/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g763007297/GQImageVideoViewer/c73793e879e789928be8e9b201946a5dad569032/GQImageVideoViewerDemo/GQImageVideoViewerDemo/image/2.jpg -------------------------------------------------------------------------------- /GQImageVideoViewerDemo/GQImageVideoViewerDemo/image/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g763007297/GQImageVideoViewer/c73793e879e789928be8e9b201946a5dad569032/GQImageVideoViewerDemo/GQImageVideoViewerDemo/image/3.jpg -------------------------------------------------------------------------------- /GQImageVideoViewerDemo/GQImageVideoViewerDemo/image/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g763007297/GQImageVideoViewer/c73793e879e789928be8e9b201946a5dad569032/GQImageVideoViewerDemo/GQImageVideoViewerDemo/image/4.jpg -------------------------------------------------------------------------------- /GQImageVideoViewerDemo/GQImageVideoViewerDemo/image/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g763007297/GQImageVideoViewer/c73793e879e789928be8e9b201946a5dad569032/GQImageVideoViewerDemo/GQImageVideoViewerDemo/image/5.jpg -------------------------------------------------------------------------------- /GQImageVideoViewerDemo/GQImageVideoViewerDemo/image/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g763007297/GQImageVideoViewer/c73793e879e789928be8e9b201946a5dad569032/GQImageVideoViewerDemo/GQImageVideoViewerDemo/image/6.jpg -------------------------------------------------------------------------------- /GQImageVideoViewerDemo/GQImageVideoViewerDemo/image/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g763007297/GQImageVideoViewer/c73793e879e789928be8e9b201946a5dad569032/GQImageVideoViewerDemo/GQImageVideoViewerDemo/image/7.jpg -------------------------------------------------------------------------------- /GQImageVideoViewerDemo/GQImageVideoViewerDemo/image/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g763007297/GQImageVideoViewer/c73793e879e789928be8e9b201946a5dad569032/GQImageVideoViewerDemo/GQImageVideoViewerDemo/image/8.jpg -------------------------------------------------------------------------------- /GQImageVideoViewerDemo/GQImageVideoViewerDemo/image/9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g763007297/GQImageVideoViewer/c73793e879e789928be8e9b201946a5dad569032/GQImageVideoViewerDemo/GQImageVideoViewerDemo/image/9.jpg -------------------------------------------------------------------------------- /GQImageVideoViewerDemo/GQImageVideoViewerDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // GQImageVideoViewerDemo 4 | // 5 | // Created by 高旗 on 16/9/12. 6 | // Copyright © 2016年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /GQImageVideoViewerDemo/GQImageVideoViewerDemoTests/GQImageVideoViewerDemoTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // GQImageVideoViewerDemoTests.m 3 | // GQImageVideoViewerDemoTests 4 | // 5 | // Created by 高旗 on 16/9/12. 6 | // Copyright © 2016年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface GQImageVideoViewerDemoTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation GQImageVideoViewerDemoTests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | // Put setup code here. This method is called before the invocation of each test method in the class. 20 | } 21 | 22 | - (void)tearDown { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | [super tearDown]; 25 | } 26 | 27 | - (void)testExample { 28 | // This is an example of a functional test case. 29 | // Use XCTAssert and related functions to verify your tests produce the correct results. 30 | } 31 | 32 | - (void)testPerformanceExample { 33 | // This is an example of a performance test case. 34 | [self measureBlock:^{ 35 | // Put the code you want to measure the time of here. 36 | }]; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /GQImageVideoViewerDemo/GQImageVideoViewerDemoTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /GQImageVideoViewerDemo/GQImageVideoViewerDemoUITests/GQImageVideoViewerDemoUITests.m: -------------------------------------------------------------------------------- 1 | // 2 | // GQImageVideoViewerDemoUITests.m 3 | // GQImageVideoViewerDemoUITests 4 | // 5 | // Created by 高旗 on 16/9/12. 6 | // Copyright © 2016年 gaoqi. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface GQImageVideoViewerDemoUITests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation GQImageVideoViewerDemoUITests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | 22 | // In UI tests it is usually best to stop immediately when a failure occurs. 23 | self.continueAfterFailure = NO; 24 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. 25 | [[[XCUIApplication alloc] init] launch]; 26 | 27 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 28 | } 29 | 30 | - (void)tearDown { 31 | // Put teardown code here. This method is called after the invocation of each test method in the class. 32 | [super tearDown]; 33 | } 34 | 35 | - (void)testExample { 36 | // Use recording to get started writing UI tests. 37 | // Use XCTAssert and related functions to verify your tests produce the correct results. 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /GQImageVideoViewerDemo/GQImageVideoViewerDemoUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![License MIT](https://img.shields.io/badge/license-MIT-green.svg?style=flat)](https://raw.githubusercontent.com/angelcs1990/GQImageViewer/master/LICENSE)  2 | [![](https://img.shields.io/badge/platform-iOS-brightgreen.svg)](http://cocoapods.org/?q=GQImageViewer)  3 | [![support](https://img.shields.io/badge/support-iOS6.0%2B-blue.svg)](https://www.apple.com/nl/ios/)  4 | # GQImageVideoViewer 5 | 一款仿微信多图片及视频浏览器,图片和视频原尺寸显示,不会变形,双击图片放大缩小,单击消失,支持多张本地和网络图片以及网络视频混合查看,支持链式调用。不需要跳转到新的viewcontroller,就可以覆盖当前控制器显示。 6 | 7 | ## Overview 8 | 9 | ![Demo Overview](https://github.com/g763007297/GQImageVideoViewer/blob/master/Screenshot/demo.gif) 10 | 11 | ##CocoaPods 12 | 13 | 1.在 Podfile 中添加 pod 'GQImageVideoViewer'。 14 | 2.执行 pod install 或 pod update。 15 | 3.导入 GQImageVideoViewer.h。 16 | 17 | ## Basic usage 18 | 19 | 1.将GQImageVideoViewer文件夹加入到工程中。如果你的工程中有SDWebImage就不需要再添加,如果没有则需要将SDWebImage加入。 20 | 21 | 2.在需要使用的图片查看器的控制器中#import "GQImageVideoViewer.h"。 22 | 23 | 3.在需要触发查看器的地方添加以下代码: 24 | 25 | 注: 26 | < 1 >图片数组中可以放单独的NSString,NSUrl,UIImage,UIImageView,如果是单独放这些类型,则默认为是图片类型。 27 | 28 | < 2 >如果需要放视频链接,则需要放字典或者GQBaseImageVideoModel类型,数组的key为以下两个: 29 | 30 | (1).GQURLString(链接地址,支持类型为NSUrl和NSString) 31 | 32 | (2).GQIsImageURL(是否为图片,如果是图片地址就传YES,如果是视频地址就传NO)。 33 | 34 | < 3 >如果需要自定义图片和视频显示画面,则需要分别继承基类: (分别在两个覆盖的方法中写需要自定义的样式。) 35 | 36 | (1).自定义图片显示view: 继承 GQBaseImageView 类,然后在子类中覆盖方法: - (void)configureImageView; 37 | 38 | (2).自定义视频显示view: 继承 GQBaseVideoView 类,然后在子类中覆盖方法: - (void)configureVideoView; 39 | 40 | ```objc 41 | 42 | NSMutableArray *imageArray = [[NSMutableArray alloc] initWithCapacity:0]; 43 | 44 | NSURL *url = [NSURL URLWithString:@"http://static.tripbe.com/videofiles/20121214/9533522808.f4v.mp4"]; 45 | 46 | [imageArray addObjectsFromArray:@[@{GQIsImageURL:@(NO), 47 | GQURLString:url}, 48 | @{GQIsImageURL:@(NO), 49 | GQURLString:[NSURL URLWithString:@"http://192.168.31.152:8080/abc.mp4"]}, 50 | @{GQIsImageURL:@(NO), 51 | GQURLString:url}, 52 | @{GQIsImageURL:@(YES), 53 | GQURLString:@"http://cdn.cocimg.com/bbs/attachment/upload/30/5811301473150224.gif"}, 54 | @{GQIsImageURL:@(YES), 55 | GQURLString:@"http://img0.imgtn.bdimg.com/it/u=513437991,1334115219&fm=206&gp=0.jpg"}, 56 | @{GQIsImageURL:@(YES), 57 | GQURLString:@"http://h.hiphotos.baidu.com/image/pic/item/203fb80e7bec54e7f14e9ce2bf389b504ec26aa8.jpg"}, 58 | @{GQIsImageURL:@(YES), 59 | GQURLString:@"http://f.hiphotos.baidu.com/image/pic/item/a8014c086e061d9507500dd67ff40ad163d9cacd.jpg"}, 60 | @{GQIsImageURL:@(YES)]]; 61 | 62 | 63 | //基本调用 64 | [[GQImageVideoViewer sharedInstance] setImageArray:imageArray];//这是图片和视频数组 65 | [GQImageVideoViewer sharedInstance].usePageControl = YES;//设置是否使用pageControl 66 | [GQImageVideoViewer sharedInstance].selectIndex = 5;//设置选中的图片索引 67 | [GQImageVideoViewer sharedInstance].achieveSelectIndex = ^(NSInteger selectIndex){ 68 | NSLog(@"%ld",selectIndex); 69 | };//获取当前选中的图片索引 70 | [GQImageVideoViewer sharedInstance].laucnDirection = GQLaunchDirectionRight;//设置推出方向 71 | [[GQImageVideoViewer sharedInstance] showInView:self.navigationController.view];//显示GQImageViewer到指定view上 72 | 73 | //链式调用 74 | [GQImageVideoViewer sharedInstance] 75 | .imageArrayChain(imageArray) 76 | .usePageControlChain(NO) 77 | .selectIndexChain(6) 78 | .achieveSelectIndexChain(^(NSInteger selectIndex){ 79 | NSLog(@"%ld",selectIndex); 80 | }) 81 | .launchDirectionChain(GQLaunchDirectionRight) 82 | .showViewChain(self.navigationController.view); 83 | 84 | ``` 85 | 86 | 特别说明,如果是拉取网络图片的话,在iOS9以上的系统需要添加plist字段,否则无法拉取图片: 87 | 88 | ```objc 89 | 90 | NSAppTransportSecurity 91 | 92 | 93 | 94 | NSAllowsArbitraryLoads 95 | 96 | 97 | 98 | 99 | 100 | ``` 101 | 102 | ## Level history 103 | 104 | (1) 0.0.1 105 | 106 | github添加代码 107 | 108 | (2) 0.0.2 109 | 110 | 添加model,传图片和视频数组更加方便。 111 | 112 | (3) 0.0.3 113 | 114 | 将tableView替换成collectionView,完美适配屏幕旋转,修复滑动不播放的bug。 115 | 116 | (4) 0.0.4 117 | 118 | 添加自定义图片展示和视频展示功能 119 | 120 | (5) wait a moment 121 | 122 | ##Support 123 | 124 | 欢迎指出bug或者需要改善的地方,欢迎提出issues、加Q群交流iOS经验:578841619 , 我会及时的做出回应,觉得好用的话不妨给个star吧,你的每个star是我持续维护的强大动力。 125 | -------------------------------------------------------------------------------- /Screenshot/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g763007297/GQImageVideoViewer/c73793e879e789928be8e9b201946a5dad569032/Screenshot/demo.gif --------------------------------------------------------------------------------