├── screenshots.png ├── SRCarouselViewDemo ├── SRCarouselViewDemo │ ├── Assets.xcassets │ │ ├── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── logo01.jpg │ ├── logo02.jpg │ ├── logo03.jpg │ ├── logo04.jpg │ ├── logo05.jpg │ ├── placeholder.png │ ├── ViewController.h │ ├── AppDelegate.h │ ├── main.m │ ├── Info.plist │ ├── AppDelegate.m │ ├── Base.lproj │ │ └── Main.storyboard │ ├── ViewController.m │ └── LaunchScreen.storyboard └── SRCarouselViewDemo.xcodeproj │ ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── project.pbxproj ├── LICENSE ├── .gitignore ├── SRCarouselView ├── SRCarouselView.h └── SRCarouselView.m └── README.md /screenshots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guowilling/SRCarouselView/HEAD/screenshots.png -------------------------------------------------------------------------------- /SRCarouselViewDemo/SRCarouselViewDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /SRCarouselViewDemo/SRCarouselViewDemo/logo01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guowilling/SRCarouselView/HEAD/SRCarouselViewDemo/SRCarouselViewDemo/logo01.jpg -------------------------------------------------------------------------------- /SRCarouselViewDemo/SRCarouselViewDemo/logo02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guowilling/SRCarouselView/HEAD/SRCarouselViewDemo/SRCarouselViewDemo/logo02.jpg -------------------------------------------------------------------------------- /SRCarouselViewDemo/SRCarouselViewDemo/logo03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guowilling/SRCarouselView/HEAD/SRCarouselViewDemo/SRCarouselViewDemo/logo03.jpg -------------------------------------------------------------------------------- /SRCarouselViewDemo/SRCarouselViewDemo/logo04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guowilling/SRCarouselView/HEAD/SRCarouselViewDemo/SRCarouselViewDemo/logo04.jpg -------------------------------------------------------------------------------- /SRCarouselViewDemo/SRCarouselViewDemo/logo05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guowilling/SRCarouselView/HEAD/SRCarouselViewDemo/SRCarouselViewDemo/logo05.jpg -------------------------------------------------------------------------------- /SRCarouselViewDemo/SRCarouselViewDemo/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guowilling/SRCarouselView/HEAD/SRCarouselViewDemo/SRCarouselViewDemo/placeholder.png -------------------------------------------------------------------------------- /SRCarouselViewDemo/SRCarouselViewDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SRCarouselViewDemo/SRCarouselViewDemo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // SRCarouselViewDemo 4 | // 5 | // Created by 郭伟林 on 16/9/3. 6 | // Copyright © 2016年 SR. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /SRCarouselViewDemo/SRCarouselViewDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SRCarouselViewDemo/SRCarouselViewDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // SRCarouselViewDemo 4 | // 5 | // Created by 郭伟林 on 16/9/3. 6 | // Copyright © 2016年 SR. 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 | -------------------------------------------------------------------------------- /SRCarouselViewDemo/SRCarouselViewDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // SRCarouselViewDemo 4 | // 5 | // Created by 郭伟林 on 16/9/3. 6 | // Copyright © 2016年 SR. 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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Willing Guo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /SRCarouselViewDemo/SRCarouselViewDemo/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" : "ios-marketing", 45 | "size" : "1024x1024", 46 | "scale" : "1x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } -------------------------------------------------------------------------------- /SRCarouselViewDemo/SRCarouselViewDemo/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 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSAllowsArbitraryLoads 28 | 29 | 30 | UILaunchStoryboardName 31 | LaunchScreen 32 | UIMainStoryboardFile 33 | Main 34 | UIRequiredDeviceCapabilities 35 | 36 | armv7 37 | 38 | UIStatusBarHidden 39 | 40 | UISupportedInterfaceOrientations 41 | 42 | UIInterfaceOrientationPortrait 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xcuserstate 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | 30 | # CocoaPods 31 | # 32 | # We recommend against adding the Pods directory to your .gitignore. However 33 | # you should judge for yourself, the pros and cons are mentioned at: 34 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 35 | # 36 | # Pods/ 37 | 38 | # Carthage 39 | # 40 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 41 | # Carthage/Checkouts 42 | 43 | Carthage/Build 44 | 45 | # fastlane 46 | # 47 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 48 | # screenshots whenever they are needed. 49 | # For more information about the recommended setup visit: 50 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 51 | 52 | fastlane/report.xml 53 | fastlane/screenshots 54 | 55 | #Code Injection 56 | # 57 | # After new code Injection tools there's a generated folder /iOSInjectionProject 58 | # https://github.com/johnno1962/injectionforxcode 59 | 60 | iOSInjectionProject/ 61 | -------------------------------------------------------------------------------- /SRCarouselViewDemo/SRCarouselViewDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // SRCarouselViewDemo 4 | // 5 | // Created by 郭伟林 on 16/9/3. 6 | // Copyright © 2016年 SR. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | - (void)applicationWillResignActive:(UIApplication *)application { 24 | // 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. 25 | // 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. 26 | } 27 | 28 | - (void)applicationDidEnterBackground:(UIApplication *)application { 29 | // 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. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | - (void)applicationWillEnterForeground:(UIApplication *)application { 34 | // 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. 35 | } 36 | 37 | - (void)applicationDidBecomeActive:(UIApplication *)application { 38 | // 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. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /SRCarouselView/SRCarouselView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SRCarouselView.h 3 | // SRCarouselView 4 | // 5 | // Created by https://github.com/guowilling on 17/1/10. 6 | // Copyright © 2017年 SR. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol SRCarouselViewDelegate 12 | 13 | - (void)carouselViewDidTapCarouselViewAtIndex:(NSInteger)index; 14 | 15 | @end 16 | 17 | typedef void (^SRDidTapCarouselViewAtIndexBlock)(NSInteger index); 18 | 19 | @interface SRCarouselView : UIView 20 | 21 | /** 22 | The time interval of auto paging, default is 5.0s. 23 | */ 24 | @property (nonatomic, assign) NSTimeInterval autoPagingInterval; 25 | 26 | @property (nonatomic, strong) UIColor *currentPageIndicatorTintColor; 27 | @property (nonatomic, strong) UIColor *pageIndicatorTintColor; 28 | 29 | @property (nonatomic, strong) UIImage *currentPageIndicatorImage; 30 | @property (nonatomic, strong) UIImage *pageIndicatorImage; 31 | 32 | + (instancetype)sr_carouselViewWithImageArrary:(NSArray *)imageArrary; 33 | + (instancetype)sr_carouselViewWithImageArrary:(NSArray *)imageArrary describeArray:(NSArray *)describeArray; 34 | + (instancetype)sr_carouselViewWithImageArrary:(NSArray *)imageArrary describeArray:(NSArray *)describeArray placeholderImage:(UIImage *)placeholderImage; 35 | 36 | /** 37 | Creates and returns a infinite carousel view with imageArrary, describeArray, placeholderImage and delegate. 38 | 39 | @param imageArrary An array contains local images, or urls of images, or mixed of them. 40 | @param describeArray An array contains image describes which in the same order as the images. 41 | @param placeholderImage The placeholder image when network image have not downloaded. 42 | @param delegate The receiver’s delegate object. 43 | @return A newly carousel view. 44 | */ 45 | + (instancetype)sr_carouselViewWithImageArrary:(NSArray *)imageArrary 46 | describeArray:(NSArray *)describeArray 47 | placeholderImage:(UIImage *)placeholderImage 48 | delegate:(id)delegate; 49 | 50 | /** 51 | Creates and returns a infinite carousel view with imageArrary, describeArray, placeholderImage and block. 52 | 53 | @param imageArrary An array contains local images, or urls of images, or mixed of them. 54 | @param describeArray An array contains image describes which in the same order as the images. 55 | @param placeholderImage The placeholder image when network image have not downloaded. 56 | @param block A block object to be executed when tap the carousel view. 57 | @return A newly carousel view. 58 | */ 59 | + (instancetype)sr_carouselViewWithImageArrary:(NSArray *)imageArrary 60 | describeArray:(NSArray *)describeArray 61 | placeholderImage:(UIImage *)placeholderImage 62 | block:(SRDidTapCarouselViewAtIndexBlock)block; 63 | 64 | + (void)clearCachedImages; 65 | 66 | @end 67 | -------------------------------------------------------------------------------- /SRCarouselViewDemo/SRCarouselViewDemo/Base.lproj/Main.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 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /SRCarouselViewDemo/SRCarouselViewDemo/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // SRCarouselViewDemo 4 | // 5 | // Created by 郭伟林 on 16/9/3. 6 | // Copyright © 2016年 SR. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "SRCarouselView.h" 11 | 12 | @interface ViewController () 13 | 14 | @end 15 | 16 | @implementation ViewController 17 | 18 | - (BOOL)prefersStatusBarHidden { 19 | return YES; 20 | } 21 | 22 | - (void)viewDidLoad { 23 | [super viewDidLoad]; 24 | 25 | [self carouselViewWithLocalImages]; 26 | 27 | [self carouselViewWithNetworkImages]; 28 | 29 | [self carouselViewWithMixedImages]; 30 | } 31 | 32 | - (void)carouselViewWithLocalImages { 33 | NSArray *imageArray = @[[UIImage imageNamed:@"logo01.jpg"], 34 | [UIImage imageNamed:@"logo02.jpg"], 35 | [UIImage imageNamed:@"logo03.jpg"], 36 | [UIImage imageNamed:@"logo04.jpg"], 37 | [UIImage imageNamed:@"logo05.jpg"]]; 38 | NSMutableArray *describeArray = [[NSMutableArray alloc] init]; 39 | for (NSInteger i = 0; i < imageArray.count; i++) { 40 | NSString *tempDesc = [NSString stringWithFormat:@"Image Description %zd", i + 1]; 41 | [describeArray addObject:tempDesc]; 42 | } 43 | SRCarouselView *carouselView = [SRCarouselView sr_carouselViewWithImageArrary:imageArray describeArray:nil placeholderImage:nil delegate:self]; 44 | carouselView.frame = CGRectMake(0, 44, self.view.frame.size.width, 200); 45 | carouselView.autoPagingInterval = 10.0; 46 | [self.view addSubview:carouselView]; 47 | } 48 | 49 | - (void)carouselViewWithNetworkImages { 50 | NSArray *imageArray = @[@"https://yixunfiles-ali.yixun.arhieason.com/9535a537ad2538d53ec1a351deff3856_jpg.jpg?x-oss-process=image/format,png", 51 | @"https://yixunfiles-ali.yixun.arhieason.com/8f8b02a025a7b62e81cafc4e9d89f70e_jpg.jpg?x-oss-process=image/format,png", 52 | @"https://yixunfiles-ali.yixun.arhieason.com/6c72618dc3412974369d544a2734d5cb_jpg.jpg?x-oss-process=image/format,png", 53 | @"https://yixunfiles-ali.yixun.arhieason.com/63a0bb8cd057e12c45ecbcb7f24e4ecf_jpg.jpg?x-oss-process=image/format,png", 54 | @"https://yixunfiles-ali.yixun.arhieason.com/2393d812560715fb9a38df9550b1f749_jpg.jpg?x-oss-process=image/format,png"]; 55 | NSMutableArray *describeArray = [[NSMutableArray alloc] init]; 56 | for (NSInteger i = 0; i < imageArray.count; i++) { 57 | NSString *tempDesc = [NSString stringWithFormat:@"Image Description %zd", i]; 58 | [describeArray addObject:tempDesc]; 59 | } 60 | SRCarouselView *carouselView = [SRCarouselView sr_carouselViewWithImageArrary:imageArray describeArray:nil placeholderImage:[UIImage imageNamed:@"placeholder_image.jpg"] block:^(NSInteger index) { 61 | NSLog(@"index: %zd", index); 62 | }]; 63 | carouselView.frame = CGRectMake(0, 245, self.view.frame.size.width, 200); 64 | [self.view addSubview:carouselView]; 65 | } 66 | 67 | - (void)carouselViewWithMixedImages { 68 | NSArray *imageArray = @[@"https://yixunfiles-ali.yixun.arhieason.com/9535a537ad2538d53ec1a351deff3856_jpg.jpg?x-oss-process=image/format,png", 69 | @"https://yixunfiles-ali.yixun.arhieason.com/8f8b02a025a7b62e81cafc4e9d89f70e_jpg.jpg?x-oss-process=image/format,png", 70 | [UIImage imageNamed:@"logo03.jpg"], 71 | [UIImage imageNamed:@"logo04.jpg"], 72 | [UIImage imageNamed:@"logo05.jpg"]]; 73 | NSMutableArray *describeArray = [[NSMutableArray alloc] init]; 74 | for (NSInteger i = 0; i < imageArray.count; i++) { 75 | NSString *tempDesc = [NSString stringWithFormat:@"Image Description"]; 76 | [describeArray addObject:tempDesc]; 77 | } 78 | SRCarouselView *carouselView = [SRCarouselView sr_carouselViewWithImageArrary:imageArray describeArray:describeArray placeholderImage:[UIImage imageNamed:@"placeholder.png"] delegate:self]; 79 | carouselView.frame = CGRectMake(0, 446, self.view.frame.size.width, 200); 80 | carouselView.autoPagingInterval = 10.0; 81 | [self.view addSubview:carouselView]; 82 | } 83 | 84 | - (void)carouselViewDidTapCarouselViewAtIndex:(NSInteger)index { 85 | NSLog(@"carouselViewDidTapCarouselViewAtIndex index: %zd", index); 86 | } 87 | 88 | @end 89 | -------------------------------------------------------------------------------- /SRCarouselViewDemo/SRCarouselViewDemo/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 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SRCarouselView 2 | 3 | A carousel view that only uses two UIImageView to achieve infinite carousel. 4 | 5 | ![image](./screenshots.png) 6 | 7 | ## Features 8 | 9 | * [x] Creates with an array that can contain local image, network image or both of them. 10 | * [x] Not rely on any third-party libraries, use the native api to download and cache image. 11 | * [x] UIPageControl will be displayed on the right If there are descriptions, otherwise displayed on the center. 12 | 13 | ## APIs 14 | 15 | ````objc 16 | + (instancetype)sr_carouselViewWithImageArrary:(NSArray *)imageArrary; 17 | 18 | + (instancetype)sr_carouselViewWithImageArrary:(NSArray *)imageArrary describeArray:(NSArray *)describeArray; 19 | 20 | + (instancetype)sr_carouselViewWithImageArrary:(NSArray *)imageArrary describeArray:(NSArray *)describeArray placeholderImage:(UIImage *)placeholderImage; 21 | 22 | /** 23 | Creates and returns a infinite carousel view with imageArrary, describeArray, placeholderImage and delegate. 24 | 25 | @param imageArrary An array contains local images, or urls of images, or mixed of them. 26 | @param describeArray An array contains image describes which in the same order as the images. 27 | @param placeholderImage The placeholder image when network image have not downloaded. 28 | @param delegate The receiver’s delegate object. 29 | @return A newly carousel view. 30 | */ 31 | + (instancetype)sr_carouselViewWithImageArrary:(NSArray *)imageArrary describeArray:(NSArray *)describeArray placeholderImage:(UIImage *)placeholderImage delegate:(id)delegate; 32 | 33 | /** 34 | Creates and returns a infinite carousel view with imageArrary, describeArray, placeholderImage and block. 35 | 36 | @param imageArrary An array contains local images, or urls of images, or mixed of them. 37 | @param describeArray An array contains image describes which in the same order as the images. 38 | @param placeholderImage The placeholder image when network image have not downloaded. 39 | @param block A block object to be executed when tap the carousel view. 40 | @return A newly carousel view. 41 | */ 42 | + (instancetype)sr_carouselViewWithImageArrary:(NSArray *)imageArrary describeArray:(NSArray *)describeArray placeholderImage:(UIImage *)placeholderImage block:(DidTapCarouselViewAtIndexBlock)block; 43 | ```` 44 | 45 | ## Usage 46 | 47 | ````objc 48 | // local images 49 | NSArray *imageArray = @[[UIImage imageNamed:@"logo01.jpg"], 50 | [UIImage imageNamed:@"logo02.jpg"], 51 | [UIImage imageNamed:@"logo03.jpg"], 52 | [UIImage imageNamed:@"logo04.jpg"], 53 | [UIImage imageNamed:@"logo05.jpg"]]; 54 | NSMutableArray *describeArray = [[NSMutableArray alloc] init]; 55 | for (NSInteger i = 0; i < imageArray.count; i++) { 56 | NSString *tempDesc = [NSString stringWithFormat:@"Image Description %zd", i + 1]; 57 | [describeArray addObject:tempDesc]; 58 | } 59 | SRCarouselView *carouselView = [SRCarouselView sr_carouselViewWithImageArrary:imageArray describeArray:nil placeholderImage:nil delegate:self]; 60 | carouselView.frame = CGRectMake(0, 44, self.view.frame.size.width, 200); 61 | carouselView.autoPagingInterval = 10.0; 62 | [self.view addSubview:carouselView]; 63 | ```` 64 | 65 | ````objc 66 | // network images 67 | NSArray *imageArray = @[@"https://yixunfiles-ali.yixun.arhieason.com/9535a537ad2538d53ec1a351deff3856_jpg.jpg?x-oss-process=image/format,png", 68 | @"https://yixunfiles-ali.yixun.arhieason.com/8f8b02a025a7b62e81cafc4e9d89f70e_jpg.jpg?x-oss-process=image/format,png", 69 | @"https://yixunfiles-ali.yixun.arhieason.com/6c72618dc3412974369d544a2734d5cb_jpg.jpg?x-oss-process=image/format,png", 70 | @"https://yixunfiles-ali.yixun.arhieason.com/63a0bb8cd057e12c45ecbcb7f24e4ecf_jpg.jpg?x-oss-process=image/format,png", 71 | @"https://yixunfiles-ali.yixun.arhieason.com/2393d812560715fb9a38df9550b1f749_jpg.jpg?x-oss-process=image/format,png"]; 72 | NSMutableArray *describeArray = [[NSMutableArray alloc] init]; 73 | for (NSInteger i = 0; i < imageArray.count; i++) { 74 | NSString *tempDesc = [NSString stringWithFormat:@"Image Description %zd", i]; 75 | [describeArray addObject:tempDesc]; 76 | } 77 | SRCarouselView *carouselView = [SRCarouselView sr_carouselViewWithImageArrary:imageArray describeArray:nil placeholderImage:[UIImage imageNamed:@"placeholder_image.jpg"] block:^(NSInteger index) { 78 | NSLog(@"index: %zd", index); 79 | }]; 80 | carouselView.frame = CGRectMake(0, 245, self.view.frame.size.width, 200); 81 | [self.view addSubview:carouselView]; 82 | ```` 83 | 84 | ````objc 85 | // local and network Images 86 | NSArray *imageArray = @[@"https://yixunfiles-ali.yixun.arhieason.com/9535a537ad2538d53ec1a351deff3856_jpg.jpg?x-oss-process=image/format,png", 87 | @"https://yixunfiles-ali.yixun.arhieason.com/8f8b02a025a7b62e81cafc4e9d89f70e_jpg.jpg?x-oss-process=image/format,png", 88 | [UIImage imageNamed:@"logo03.jpg"], 89 | [UIImage imageNamed:@"logo04.jpg"], 90 | [UIImage imageNamed:@"logo05.jpg"]]; 91 | NSMutableArray *describeArray = [[NSMutableArray alloc] init]; 92 | for (NSInteger i = 0; i < imageArray.count; i++) { 93 | NSString *tempDesc = [NSString stringWithFormat:@"Image Description"]; 94 | [describeArray addObject:tempDesc]; 95 | } 96 | SRCarouselView *carouselView = [SRCarouselView sr_carouselViewWithImageArrary:imageArray describeArray:describeArray placeholderImage:[UIImage imageNamed:@"placeholder"] delegate:self]; 97 | carouselView.frame = CGRectMake(0, 446, self.view.frame.size.width, 200); 98 | carouselView.autoPagingInterval = 10.0; 99 | [self.view addSubview:carouselView]; 100 | ```` 101 | 102 | ## Custom 103 | 104 | ````objc 105 | /** 106 | The time interval of auto paging, default is 5.0s. 107 | */ 108 | @property (nonatomic, assign) NSTimeInterval autoPagingInterval; 109 | 110 | @property (nonatomic, strong) UIColor *currentPageIndicatorTintColor; 111 | @property (nonatomic, strong) UIColor *pageIndicatorTintColor; 112 | 113 | @property (nonatomic, strong) UIImage *currentPageIndicatorImage; 114 | @property (nonatomic, strong) UIImage *pageIndicatorImage; 115 | ```` -------------------------------------------------------------------------------- /SRCarouselViewDemo/SRCarouselViewDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 685FD2C02148EF5D000ED53D /* placeholder.png in Resources */ = {isa = PBXBuildFile; fileRef = 685FD2BA2148EF5D000ED53D /* placeholder.png */; }; 11 | 685FD2C12148EF5D000ED53D /* logo02.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 685FD2BB2148EF5D000ED53D /* logo02.jpg */; }; 12 | 685FD2C22148EF5D000ED53D /* logo03.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 685FD2BC2148EF5D000ED53D /* logo03.jpg */; }; 13 | 685FD2C32148EF5D000ED53D /* logo05.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 685FD2BD2148EF5D000ED53D /* logo05.jpg */; }; 14 | 685FD2C42148EF5D000ED53D /* logo04.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 685FD2BE2148EF5D000ED53D /* logo04.jpg */; }; 15 | 685FD2C52148EF5D000ED53D /* logo01.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 685FD2BF2148EF5D000ED53D /* logo01.jpg */; }; 16 | 68E05AE21D7AC0BF00F214D5 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 68E05AE11D7AC0BF00F214D5 /* main.m */; }; 17 | 68E05AE51D7AC0BF00F214D5 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 68E05AE41D7AC0BF00F214D5 /* AppDelegate.m */; }; 18 | 68E05AE81D7AC0C000F214D5 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 68E05AE71D7AC0BF00F214D5 /* ViewController.m */; }; 19 | 68E05AEB1D7AC0C000F214D5 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 68E05AE91D7AC0C000F214D5 /* Main.storyboard */; }; 20 | 68E05AED1D7AC0C000F214D5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 68E05AEC1D7AC0C000F214D5 /* Assets.xcassets */; }; 21 | 6EBF581C1EB1164600B2B986 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6EBF581B1EB1164600B2B986 /* LaunchScreen.storyboard */; }; 22 | 6ED4F85B1EBE467E0074633B /* SRCarouselView.m in Sources */ = {isa = PBXBuildFile; fileRef = 6ED4F85A1EBE467E0074633B /* SRCarouselView.m */; }; 23 | /* End PBXBuildFile section */ 24 | 25 | /* Begin PBXFileReference section */ 26 | 685FD2BA2148EF5D000ED53D /* placeholder.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = placeholder.png; sourceTree = ""; }; 27 | 685FD2BB2148EF5D000ED53D /* logo02.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = logo02.jpg; sourceTree = ""; }; 28 | 685FD2BC2148EF5D000ED53D /* logo03.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = logo03.jpg; sourceTree = ""; }; 29 | 685FD2BD2148EF5D000ED53D /* logo05.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = logo05.jpg; sourceTree = ""; }; 30 | 685FD2BE2148EF5D000ED53D /* logo04.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = logo04.jpg; sourceTree = ""; }; 31 | 685FD2BF2148EF5D000ED53D /* logo01.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = logo01.jpg; sourceTree = ""; }; 32 | 68E05ADD1D7AC0BF00F214D5 /* SRCarouselViewDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SRCarouselViewDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33 | 68E05AE11D7AC0BF00F214D5 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 34 | 68E05AE31D7AC0BF00F214D5 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 35 | 68E05AE41D7AC0BF00F214D5 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 36 | 68E05AE61D7AC0BF00F214D5 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 37 | 68E05AE71D7AC0BF00F214D5 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 38 | 68E05AEA1D7AC0C000F214D5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 39 | 68E05AEC1D7AC0C000F214D5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 40 | 68E05AF11D7AC0C000F214D5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 41 | 6EBF581B1EB1164600B2B986 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; 42 | 6ED4F8591EBE467E0074633B /* SRCarouselView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRCarouselView.h; sourceTree = ""; }; 43 | 6ED4F85A1EBE467E0074633B /* SRCarouselView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRCarouselView.m; sourceTree = ""; }; 44 | /* End PBXFileReference section */ 45 | 46 | /* Begin PBXFrameworksBuildPhase section */ 47 | 68E05ADA1D7AC0BF00F214D5 /* Frameworks */ = { 48 | isa = PBXFrameworksBuildPhase; 49 | buildActionMask = 2147483647; 50 | files = ( 51 | ); 52 | runOnlyForDeploymentPostprocessing = 0; 53 | }; 54 | /* End PBXFrameworksBuildPhase section */ 55 | 56 | /* Begin PBXGroup section */ 57 | 685FD2B92148EEE1000ED53D /* Resource */ = { 58 | isa = PBXGroup; 59 | children = ( 60 | 685FD2BF2148EF5D000ED53D /* logo01.jpg */, 61 | 685FD2BB2148EF5D000ED53D /* logo02.jpg */, 62 | 685FD2BC2148EF5D000ED53D /* logo03.jpg */, 63 | 685FD2BE2148EF5D000ED53D /* logo04.jpg */, 64 | 685FD2BD2148EF5D000ED53D /* logo05.jpg */, 65 | 685FD2BA2148EF5D000ED53D /* placeholder.png */, 66 | ); 67 | name = Resource; 68 | sourceTree = ""; 69 | }; 70 | 68E05AD41D7AC0BF00F214D5 = { 71 | isa = PBXGroup; 72 | children = ( 73 | 68E05ADF1D7AC0BF00F214D5 /* SRCarouselViewDemo */, 74 | 68E05ADE1D7AC0BF00F214D5 /* Products */, 75 | ); 76 | sourceTree = ""; 77 | }; 78 | 68E05ADE1D7AC0BF00F214D5 /* Products */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | 68E05ADD1D7AC0BF00F214D5 /* SRCarouselViewDemo.app */, 82 | ); 83 | name = Products; 84 | sourceTree = ""; 85 | }; 86 | 68E05ADF1D7AC0BF00F214D5 /* SRCarouselViewDemo */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 68E05B131D7AC1F200F214D5 /* SRCarouselView */, 90 | 68E05AE61D7AC0BF00F214D5 /* ViewController.h */, 91 | 68E05AE71D7AC0BF00F214D5 /* ViewController.m */, 92 | 68E05AE91D7AC0C000F214D5 /* Main.storyboard */, 93 | 68E05AE01D7AC0BF00F214D5 /* Supporting Files */, 94 | ); 95 | path = SRCarouselViewDemo; 96 | sourceTree = ""; 97 | }; 98 | 68E05AE01D7AC0BF00F214D5 /* Supporting Files */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | 685FD2B92148EEE1000ED53D /* Resource */, 102 | 68E05AE11D7AC0BF00F214D5 /* main.m */, 103 | 68E05AE31D7AC0BF00F214D5 /* AppDelegate.h */, 104 | 68E05AE41D7AC0BF00F214D5 /* AppDelegate.m */, 105 | 6EBF581B1EB1164600B2B986 /* LaunchScreen.storyboard */, 106 | 68E05AEC1D7AC0C000F214D5 /* Assets.xcassets */, 107 | 68E05AF11D7AC0C000F214D5 /* Info.plist */, 108 | ); 109 | name = "Supporting Files"; 110 | sourceTree = ""; 111 | }; 112 | 68E05B131D7AC1F200F214D5 /* SRCarouselView */ = { 113 | isa = PBXGroup; 114 | children = ( 115 | 6ED4F8591EBE467E0074633B /* SRCarouselView.h */, 116 | 6ED4F85A1EBE467E0074633B /* SRCarouselView.m */, 117 | ); 118 | name = SRCarouselView; 119 | path = ../../SRCarouselView; 120 | sourceTree = ""; 121 | }; 122 | /* End PBXGroup section */ 123 | 124 | /* Begin PBXNativeTarget section */ 125 | 68E05ADC1D7AC0BF00F214D5 /* SRCarouselViewDemo */ = { 126 | isa = PBXNativeTarget; 127 | buildConfigurationList = 68E05B0A1D7AC0C000F214D5 /* Build configuration list for PBXNativeTarget "SRCarouselViewDemo" */; 128 | buildPhases = ( 129 | 68E05AD91D7AC0BF00F214D5 /* Sources */, 130 | 68E05ADA1D7AC0BF00F214D5 /* Frameworks */, 131 | 68E05ADB1D7AC0BF00F214D5 /* Resources */, 132 | ); 133 | buildRules = ( 134 | ); 135 | dependencies = ( 136 | ); 137 | name = SRCarouselViewDemo; 138 | productName = SRInfiniteCarouselViewDemo; 139 | productReference = 68E05ADD1D7AC0BF00F214D5 /* SRCarouselViewDemo.app */; 140 | productType = "com.apple.product-type.application"; 141 | }; 142 | /* End PBXNativeTarget section */ 143 | 144 | /* Begin PBXProject section */ 145 | 68E05AD51D7AC0BF00F214D5 /* Project object */ = { 146 | isa = PBXProject; 147 | attributes = { 148 | LastUpgradeCheck = 0830; 149 | ORGANIZATIONNAME = SR; 150 | TargetAttributes = { 151 | 68E05ADC1D7AC0BF00F214D5 = { 152 | CreatedOnToolsVersion = 7.3; 153 | DevelopmentTeam = L8C6M6B8E9; 154 | ProvisioningStyle = Manual; 155 | }; 156 | }; 157 | }; 158 | buildConfigurationList = 68E05AD81D7AC0BF00F214D5 /* Build configuration list for PBXProject "SRCarouselViewDemo" */; 159 | compatibilityVersion = "Xcode 3.2"; 160 | developmentRegion = English; 161 | hasScannedForEncodings = 0; 162 | knownRegions = ( 163 | en, 164 | Base, 165 | ); 166 | mainGroup = 68E05AD41D7AC0BF00F214D5; 167 | productRefGroup = 68E05ADE1D7AC0BF00F214D5 /* Products */; 168 | projectDirPath = ""; 169 | projectRoot = ""; 170 | targets = ( 171 | 68E05ADC1D7AC0BF00F214D5 /* SRCarouselViewDemo */, 172 | ); 173 | }; 174 | /* End PBXProject section */ 175 | 176 | /* Begin PBXResourcesBuildPhase section */ 177 | 68E05ADB1D7AC0BF00F214D5 /* Resources */ = { 178 | isa = PBXResourcesBuildPhase; 179 | buildActionMask = 2147483647; 180 | files = ( 181 | 6EBF581C1EB1164600B2B986 /* LaunchScreen.storyboard in Resources */, 182 | 685FD2C02148EF5D000ED53D /* placeholder.png in Resources */, 183 | 685FD2C12148EF5D000ED53D /* logo02.jpg in Resources */, 184 | 685FD2C22148EF5D000ED53D /* logo03.jpg in Resources */, 185 | 68E05AED1D7AC0C000F214D5 /* Assets.xcassets in Resources */, 186 | 685FD2C52148EF5D000ED53D /* logo01.jpg in Resources */, 187 | 68E05AEB1D7AC0C000F214D5 /* Main.storyboard in Resources */, 188 | 685FD2C42148EF5D000ED53D /* logo04.jpg in Resources */, 189 | 685FD2C32148EF5D000ED53D /* logo05.jpg in Resources */, 190 | ); 191 | runOnlyForDeploymentPostprocessing = 0; 192 | }; 193 | /* End PBXResourcesBuildPhase section */ 194 | 195 | /* Begin PBXSourcesBuildPhase section */ 196 | 68E05AD91D7AC0BF00F214D5 /* Sources */ = { 197 | isa = PBXSourcesBuildPhase; 198 | buildActionMask = 2147483647; 199 | files = ( 200 | 68E05AE81D7AC0C000F214D5 /* ViewController.m in Sources */, 201 | 6ED4F85B1EBE467E0074633B /* SRCarouselView.m in Sources */, 202 | 68E05AE51D7AC0BF00F214D5 /* AppDelegate.m in Sources */, 203 | 68E05AE21D7AC0BF00F214D5 /* main.m in Sources */, 204 | ); 205 | runOnlyForDeploymentPostprocessing = 0; 206 | }; 207 | /* End PBXSourcesBuildPhase section */ 208 | 209 | /* Begin PBXVariantGroup section */ 210 | 68E05AE91D7AC0C000F214D5 /* Main.storyboard */ = { 211 | isa = PBXVariantGroup; 212 | children = ( 213 | 68E05AEA1D7AC0C000F214D5 /* Base */, 214 | ); 215 | name = Main.storyboard; 216 | sourceTree = ""; 217 | }; 218 | /* End PBXVariantGroup section */ 219 | 220 | /* Begin XCBuildConfiguration section */ 221 | 68E05B081D7AC0C000F214D5 /* Debug */ = { 222 | isa = XCBuildConfiguration; 223 | buildSettings = { 224 | ALWAYS_SEARCH_USER_PATHS = NO; 225 | CLANG_ANALYZER_NONNULL = YES; 226 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 227 | CLANG_CXX_LIBRARY = "libc++"; 228 | CLANG_ENABLE_MODULES = YES; 229 | CLANG_ENABLE_OBJC_ARC = YES; 230 | CLANG_WARN_BOOL_CONVERSION = YES; 231 | CLANG_WARN_CONSTANT_CONVERSION = YES; 232 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 233 | CLANG_WARN_EMPTY_BODY = YES; 234 | CLANG_WARN_ENUM_CONVERSION = YES; 235 | CLANG_WARN_INFINITE_RECURSION = YES; 236 | CLANG_WARN_INT_CONVERSION = YES; 237 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 238 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 239 | CLANG_WARN_UNREACHABLE_CODE = YES; 240 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 241 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 242 | COPY_PHASE_STRIP = NO; 243 | DEBUG_INFORMATION_FORMAT = dwarf; 244 | ENABLE_STRICT_OBJC_MSGSEND = YES; 245 | ENABLE_TESTABILITY = YES; 246 | GCC_C_LANGUAGE_STANDARD = gnu99; 247 | GCC_DYNAMIC_NO_PIC = NO; 248 | GCC_NO_COMMON_BLOCKS = YES; 249 | GCC_OPTIMIZATION_LEVEL = 0; 250 | GCC_PREPROCESSOR_DEFINITIONS = ( 251 | "DEBUG=1", 252 | "$(inherited)", 253 | ); 254 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 255 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 256 | GCC_WARN_UNDECLARED_SELECTOR = YES; 257 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 258 | GCC_WARN_UNUSED_FUNCTION = YES; 259 | GCC_WARN_UNUSED_VARIABLE = YES; 260 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 261 | MTL_ENABLE_DEBUG_INFO = YES; 262 | ONLY_ACTIVE_ARCH = YES; 263 | SDKROOT = iphoneos; 264 | }; 265 | name = Debug; 266 | }; 267 | 68E05B091D7AC0C000F214D5 /* Release */ = { 268 | isa = XCBuildConfiguration; 269 | buildSettings = { 270 | ALWAYS_SEARCH_USER_PATHS = NO; 271 | CLANG_ANALYZER_NONNULL = YES; 272 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 273 | CLANG_CXX_LIBRARY = "libc++"; 274 | CLANG_ENABLE_MODULES = YES; 275 | CLANG_ENABLE_OBJC_ARC = YES; 276 | CLANG_WARN_BOOL_CONVERSION = YES; 277 | CLANG_WARN_CONSTANT_CONVERSION = YES; 278 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 279 | CLANG_WARN_EMPTY_BODY = YES; 280 | CLANG_WARN_ENUM_CONVERSION = YES; 281 | CLANG_WARN_INFINITE_RECURSION = YES; 282 | CLANG_WARN_INT_CONVERSION = YES; 283 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 284 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 285 | CLANG_WARN_UNREACHABLE_CODE = YES; 286 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 287 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 288 | COPY_PHASE_STRIP = NO; 289 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 290 | ENABLE_NS_ASSERTIONS = NO; 291 | ENABLE_STRICT_OBJC_MSGSEND = YES; 292 | GCC_C_LANGUAGE_STANDARD = gnu99; 293 | GCC_NO_COMMON_BLOCKS = YES; 294 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 295 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 296 | GCC_WARN_UNDECLARED_SELECTOR = YES; 297 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 298 | GCC_WARN_UNUSED_FUNCTION = YES; 299 | GCC_WARN_UNUSED_VARIABLE = YES; 300 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 301 | MTL_ENABLE_DEBUG_INFO = NO; 302 | SDKROOT = iphoneos; 303 | VALIDATE_PRODUCT = YES; 304 | }; 305 | name = Release; 306 | }; 307 | 68E05B0B1D7AC0C000F214D5 /* Debug */ = { 308 | isa = XCBuildConfiguration; 309 | buildSettings = { 310 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 311 | DEVELOPMENT_TEAM = L8C6M6B8E9; 312 | INFOPLIST_FILE = SRCarouselViewDemo/Info.plist; 313 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 314 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 315 | PRODUCT_BUNDLE_IDENTIFIER = com.willing.SRCarouselViewDemo; 316 | PRODUCT_NAME = "$(TARGET_NAME)"; 317 | PROVISIONING_PROFILE = "96079369-07f1-4278-b161-c73a21b11f47"; 318 | PROVISIONING_PROFILE_SPECIFIER = "PP For All"; 319 | }; 320 | name = Debug; 321 | }; 322 | 68E05B0C1D7AC0C000F214D5 /* Release */ = { 323 | isa = XCBuildConfiguration; 324 | buildSettings = { 325 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 326 | DEVELOPMENT_TEAM = L8C6M6B8E9; 327 | INFOPLIST_FILE = SRCarouselViewDemo/Info.plist; 328 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 329 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 330 | PRODUCT_BUNDLE_IDENTIFIER = com.willing.SRCarouselViewDemo; 331 | PRODUCT_NAME = "$(TARGET_NAME)"; 332 | PROVISIONING_PROFILE = "96079369-07f1-4278-b161-c73a21b11f47"; 333 | PROVISIONING_PROFILE_SPECIFIER = "PP For All"; 334 | }; 335 | name = Release; 336 | }; 337 | /* End XCBuildConfiguration section */ 338 | 339 | /* Begin XCConfigurationList section */ 340 | 68E05AD81D7AC0BF00F214D5 /* Build configuration list for PBXProject "SRCarouselViewDemo" */ = { 341 | isa = XCConfigurationList; 342 | buildConfigurations = ( 343 | 68E05B081D7AC0C000F214D5 /* Debug */, 344 | 68E05B091D7AC0C000F214D5 /* Release */, 345 | ); 346 | defaultConfigurationIsVisible = 0; 347 | defaultConfigurationName = Release; 348 | }; 349 | 68E05B0A1D7AC0C000F214D5 /* Build configuration list for PBXNativeTarget "SRCarouselViewDemo" */ = { 350 | isa = XCConfigurationList; 351 | buildConfigurations = ( 352 | 68E05B0B1D7AC0C000F214D5 /* Debug */, 353 | 68E05B0C1D7AC0C000F214D5 /* Release */, 354 | ); 355 | defaultConfigurationIsVisible = 0; 356 | defaultConfigurationName = Release; 357 | }; 358 | /* End XCConfigurationList section */ 359 | }; 360 | rootObject = 68E05AD51D7AC0BF00F214D5 /* Project object */; 361 | } 362 | -------------------------------------------------------------------------------- /SRCarouselView/SRCarouselView.m: -------------------------------------------------------------------------------- 1 | // 2 | // SRCarouselView.m 3 | // SRCarouselView 4 | // 5 | // Created by https://github.com/guowilling on 17/1/10. 6 | // Copyright © 2017年 SR. All rights reserved. 7 | // 8 | 9 | #import "SRCarouselView.h" 10 | 11 | @interface SRCarouselImageManager : NSObject 12 | 13 | @property (nonatomic, assign) NSUInteger repeatCountWhenDownloadFailed; 14 | 15 | @property (nonatomic, copy) void(^downloadImageSuccess)(UIImage *image, NSInteger imageIndex); 16 | 17 | @property (nonatomic, copy) void(^downloadImageFailure)(NSError *error, NSString *imageURLString); 18 | 19 | - (void)downloadImage:(NSString *)imageURLString index:(NSInteger)imageIndex; 20 | 21 | + (void)clearCachedImages; 22 | 23 | @end 24 | 25 | #pragma mark - 26 | 27 | @interface SRCarouselView () 28 | 29 | @property (nonatomic, weak) id delegate; 30 | @property (nonatomic, copy) SRDidTapCarouselViewAtIndexBlock block; 31 | 32 | @property (nonatomic, strong) SRCarouselImageManager *imageManager; 33 | @property (nonatomic, strong) NSMutableArray *images; 34 | 35 | @property (nonatomic, strong) NSArray *imageArray; 36 | @property (nonatomic, strong) NSArray *describeArray; 37 | @property (nonatomic, strong) UIImage *placeholderImage; 38 | 39 | @property (nonatomic, strong) UIScrollView *scrollView; 40 | @property (nonatomic, strong) UIImageView *currentImageView; 41 | @property (nonatomic, strong) UIImageView *nextImageView; 42 | @property (nonatomic, strong) UIView *bottomContainer; 43 | @property (nonatomic, strong) UIPageControl *pageControl; 44 | @property (nonatomic, strong) UILabel *descLabel; 45 | 46 | @property (nonatomic, assign) NSInteger currentIndex; 47 | @property (nonatomic, assign) NSInteger nextIndex; 48 | 49 | @property (nonatomic, strong) NSTimer *autoPagingTimer; 50 | 51 | @end 52 | 53 | @implementation SRCarouselView 54 | 55 | #pragma mark - Overriding 56 | 57 | - (void)dealloc { 58 | [self stopAutoPagingTimer]; 59 | } 60 | 61 | #pragma mark - Lazy Load 62 | 63 | - (SRCarouselImageManager *)imageManager { 64 | if (!_imageManager) { 65 | __weak typeof(self) weakSelf = self; 66 | _imageManager = [[SRCarouselImageManager alloc] init]; 67 | _imageManager.downloadImageSuccess = ^(UIImage *image, NSInteger imageIndex) { 68 | weakSelf.images[imageIndex] = image; 69 | if (weakSelf.currentIndex == imageIndex) { 70 | weakSelf.currentImageView.image = image; 71 | } 72 | }; 73 | _imageManager.downloadImageFailure = ^(NSError *error, NSString *imageURLString) { 74 | NSLog(@"downloadImageFailure error: %@", error); 75 | }; 76 | } 77 | return _imageManager; 78 | } 79 | 80 | - (UILabel *)descLabel { 81 | if (!_descLabel) { 82 | _descLabel = [[UILabel alloc] init]; 83 | _descLabel.textColor = [UIColor whiteColor]; 84 | _descLabel.font = [UIFont systemFontOfSize:15]; 85 | _descLabel.textAlignment = NSTextAlignmentLeft; 86 | [self addSubview:_descLabel]; 87 | } 88 | return _descLabel; 89 | } 90 | 91 | #pragma mark - Init Methods 92 | 93 | + (instancetype)sr_carouselViewWithImageArrary:(NSArray *)imageArrary { 94 | return [self sr_carouselViewWithImageArrary:imageArrary describeArray:nil placeholderImage:nil delegate:nil]; 95 | } 96 | 97 | + (instancetype)sr_carouselViewWithImageArrary:(NSArray *)imageArrary describeArray:(NSArray *)describeArray { 98 | return [self sr_carouselViewWithImageArrary:imageArrary describeArray:describeArray placeholderImage:nil delegate:nil]; 99 | } 100 | 101 | + (instancetype)sr_carouselViewWithImageArrary:(NSArray *)imageArrary describeArray:(NSArray *)describeArray placeholderImage:(UIImage *)placeholderImage { 102 | return [self sr_carouselViewWithImageArrary:imageArrary describeArray:describeArray placeholderImage:placeholderImage delegate:nil]; 103 | } 104 | 105 | + (instancetype)sr_carouselViewWithImageArrary:(NSArray *)imageArrary 106 | describeArray:(NSArray *)describeArray 107 | placeholderImage:(UIImage *)placeholderImage 108 | delegate:(id)delegate 109 | { 110 | return [[self alloc] initWithImageArrary:imageArrary 111 | describeArray:describeArray 112 | placeholderImage:placeholderImage 113 | delegate:delegate]; 114 | } 115 | 116 | - (instancetype)initWithImageArrary:(NSArray *)imageArrary 117 | describeArray:(NSArray *)describeArray 118 | placeholderImage:(UIImage *)placeholderImage 119 | delegate:(id)delegate 120 | { 121 | if (self = [super init]) { 122 | _imageArray = imageArrary; 123 | _describeArray = describeArray; 124 | _placeholderImage = placeholderImage; 125 | 126 | _delegate = delegate; 127 | 128 | _images = [NSMutableArray array]; 129 | 130 | _currentIndex = 0; 131 | _nextIndex = 0; 132 | 133 | [self setup]; 134 | [self startAutoPagingTimer]; 135 | } 136 | return self; 137 | } 138 | 139 | + (instancetype)sr_carouselViewWithImageArrary:(NSArray *)imageArrary 140 | describeArray:(NSArray *)describeArray 141 | placeholderImage:(UIImage *)placeholderImage 142 | block:(SRDidTapCarouselViewAtIndexBlock)block 143 | { 144 | return [[self alloc] initWithImageArrary:imageArrary 145 | describeArray:describeArray 146 | placeholderImage:placeholderImage 147 | block:block]; 148 | } 149 | 150 | - (instancetype)initWithImageArrary:(NSArray *)imageArrary 151 | describeArray:(NSArray *)describeArray 152 | placeholderImage:(UIImage *)placeholderImage 153 | block:(SRDidTapCarouselViewAtIndexBlock)block 154 | { 155 | if (self = [super init]) { 156 | _imageArray = imageArrary; 157 | _describeArray = describeArray; 158 | _placeholderImage = placeholderImage; 159 | 160 | _block = block; 161 | 162 | _images = [NSMutableArray array]; 163 | 164 | _currentIndex = 0; 165 | _nextIndex = 0; 166 | 167 | [self setup]; 168 | [self startAutoPagingTimer]; 169 | } 170 | return self; 171 | } 172 | 173 | #pragma mark - Setup UI 174 | 175 | - (void)setup { 176 | if (_imageArray.count == 0) { 177 | return; 178 | } 179 | [self setupSubviews]; 180 | [self setupImages]; 181 | [self setupImageDescribes]; 182 | } 183 | 184 | - (void)setupSubviews { 185 | _scrollView = [[UIScrollView alloc] init]; 186 | _scrollView.delegate = self; 187 | _scrollView.pagingEnabled = YES; 188 | _scrollView.bounces = NO; 189 | _scrollView.showsHorizontalScrollIndicator = NO; 190 | _scrollView.showsVerticalScrollIndicator = NO; 191 | [self addSubview:_scrollView]; 192 | 193 | _pageControl = [[UIPageControl alloc] init]; 194 | _pageControl.hidesForSinglePage = YES; 195 | _pageControl.userInteractionEnabled = NO; 196 | _pageControl.numberOfPages = _imageArray.count; 197 | _pageControl.currentPage = 0; 198 | [self addSubview:_pageControl]; 199 | 200 | _currentImageView = [[UIImageView alloc] init]; 201 | _currentImageView.contentMode = UIViewContentModeScaleAspectFill; 202 | _currentImageView.layer.masksToBounds = YES; 203 | [_scrollView addSubview:_currentImageView]; 204 | 205 | _nextImageView = [[UIImageView alloc] init]; 206 | _nextImageView.contentMode = UIViewContentModeScaleAspectFill; 207 | _nextImageView.layer.masksToBounds = YES; 208 | [_scrollView addSubview:_nextImageView]; 209 | 210 | _currentImageView.userInteractionEnabled = YES; 211 | [_currentImageView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didTapCurrentImageView)]]; 212 | } 213 | 214 | - (void)setupImages { 215 | for (int i = 0; i < _imageArray.count; i++) { 216 | if ([_imageArray[i] isKindOfClass:[UIImage class]]) { // local image 217 | [self.images addObject:_imageArray[i]]; 218 | } 219 | if ([_imageArray[i] isKindOfClass:[NSString class]]) { // internet image 220 | if (_placeholderImage) { // hold placeholder image if setted 221 | [self.images addObject:_placeholderImage]; 222 | } else { // use NSNull object replace if not setted 223 | [self.images addObject:[NSNull null]]; 224 | } 225 | [self.imageManager downloadImage:self.imageArray[i] index:i]; // use SRCarouselImageManager to download image 226 | } 227 | } 228 | 229 | if ([self.images[0] isKindOfClass:[NSNull class]]) { 230 | _currentImageView.image = nil; 231 | } else { // show first image or placeholder image if exists 232 | _currentImageView.image = self.images[0]; 233 | } 234 | } 235 | 236 | - (void)setupImageDescribes { 237 | if (_describeArray && _describeArray.count > 0) { 238 | if (_describeArray.count < self.images.count) { 239 | NSMutableArray *arrayM = [NSMutableArray arrayWithArray:_describeArray]; 240 | for (NSInteger i = _describeArray.count; i < self.images.count; i++) { 241 | [arrayM addObject:@""]; 242 | } 243 | _describeArray = arrayM; 244 | } 245 | _bottomContainer = [[UIView alloc] init]; 246 | _bottomContainer.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5]; 247 | [self addSubview:_bottomContainer]; 248 | [_bottomContainer addSubview:self.descLabel]; 249 | 250 | self.descLabel.text = _describeArray[0]; 251 | 252 | [self bringSubviewToFront:_pageControl]; 253 | } 254 | } 255 | 256 | - (void)layoutSubviews { 257 | [super layoutSubviews]; 258 | 259 | _scrollView.frame = self.bounds; 260 | _scrollView.contentInset = UIEdgeInsetsZero; 261 | 262 | CGFloat width = _scrollView.frame.size.width; 263 | CGFloat height = _scrollView.frame.size.height; 264 | 265 | if (self.images.count > 1) { 266 | _scrollView.contentSize = CGSizeMake(width * 3, 0); 267 | _scrollView.contentOffset = CGPointMake(width, 0); 268 | _currentImageView.frame = CGRectMake(width, 0, width, height); 269 | } else { 270 | _scrollView.contentSize = CGSizeZero; 271 | _scrollView.contentOffset = CGPointMake(0, 0); 272 | _currentImageView.frame = CGRectMake(0, 0, width, height); 273 | } 274 | 275 | CGFloat bottomContainerHeight = 25; 276 | CGFloat pageControlDotWidth = 15; 277 | if (!_describeArray || _describeArray.count == 0) { 278 | _pageControl.frame = CGRectMake(width * 0.5 - _pageControl.numberOfPages * pageControlDotWidth * 0.5, 279 | height - bottomContainerHeight, 280 | _pageControl.numberOfPages * pageControlDotWidth, 281 | bottomContainerHeight); 282 | } else { 283 | _bottomContainer.frame = CGRectMake(0, height - bottomContainerHeight, width, bottomContainerHeight); 284 | _pageControl.frame = CGRectMake(width - _pageControl.numberOfPages * pageControlDotWidth - 5, 285 | height - bottomContainerHeight, 286 | _pageControl.numberOfPages * pageControlDotWidth, 287 | bottomContainerHeight); 288 | _descLabel.frame = CGRectMake(5, 0, width - 10, bottomContainerHeight); 289 | } 290 | } 291 | 292 | #pragma mark - Timer 293 | 294 | - (void)startAutoPagingTimer { 295 | if (self.images.count <= 1) { 296 | return; 297 | } 298 | if (_autoPagingTimer) { 299 | [self stopAutoPagingTimer]; 300 | } 301 | _autoPagingTimer = [NSTimer timerWithTimeInterval:_autoPagingInterval == 0 ? 5.0 : _autoPagingInterval 302 | target:self 303 | selector:@selector(nextPage) 304 | userInfo:nil 305 | repeats:YES]; 306 | [[NSRunLoop currentRunLoop] addTimer:_autoPagingTimer forMode:NSRunLoopCommonModes]; 307 | } 308 | 309 | - (void)stopAutoPagingTimer { 310 | if (_autoPagingTimer) { 311 | [_autoPagingTimer invalidate]; 312 | _autoPagingTimer = nil; 313 | } 314 | } 315 | 316 | #pragma mark - Actions 317 | 318 | - (void)nextPage { 319 | CGFloat width = _scrollView.frame.size.width; 320 | [_scrollView setContentOffset:CGPointMake(width * 2, 0) animated:YES]; 321 | } 322 | 323 | - (void)didTapCurrentImageView { 324 | if (self.block) { 325 | self.block(self.currentIndex); 326 | } 327 | if ([self.delegate respondsToSelector:@selector(carouselViewDidTapCarouselViewAtIndex:)]) { 328 | [self.delegate carouselViewDidTapCarouselViewAtIndex:self.currentIndex]; 329 | } 330 | } 331 | 332 | #pragma mark - UIScrollViewDelegate 333 | 334 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView { 335 | CGFloat width = _scrollView.frame.size.width; 336 | CGFloat height = _scrollView.frame.size.height; 337 | 338 | CGFloat offsetX = scrollView.contentOffset.x; 339 | if (offsetX == width) { 340 | return; 341 | } 342 | 343 | if (offsetX > width) { 344 | _nextImageView.frame = CGRectMake(CGRectGetMaxX(_currentImageView.frame), 0, width, height); 345 | _nextIndex = _currentIndex + 1; 346 | if (_nextIndex == self.images.count) { 347 | _nextIndex = 0; 348 | } 349 | } 350 | 351 | if (offsetX < width) { 352 | _nextImageView.frame = CGRectMake(0, 0, width, height); 353 | _nextIndex = _currentIndex - 1; 354 | if (_nextIndex < 0) { 355 | _nextIndex = self.images.count - 1; 356 | } 357 | } 358 | 359 | if ([self.images[_nextIndex] isKindOfClass:[NSNull class]]) { 360 | _nextImageView.image = nil; 361 | } else { 362 | _nextImageView.image = self.images[_nextIndex]; 363 | } 364 | } 365 | 366 | - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { 367 | [self stopAutoPagingTimer]; // stop timer when dragging scrollview manually 368 | } 369 | 370 | - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { 371 | [self startAutoPagingTimer]; // start timer when stop dragging scrollview manually 372 | } 373 | 374 | - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { 375 | [self updateContent]; // update content when paging finishes manually 376 | } 377 | 378 | - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView { 379 | [self updateContent]; // update content when paging finishes automatically 380 | } 381 | 382 | - (void)updateContent { 383 | CGFloat width = _scrollView.frame.size.width; 384 | 385 | if (_scrollView.contentOffset.x == width) { // if paging not finished do not update content 386 | return; 387 | } 388 | 389 | _currentIndex = _nextIndex; 390 | _currentImageView.image = _nextImageView.image; 391 | _pageControl.currentPage = _currentIndex; 392 | if (self.describeArray.count > 0) { 393 | self.descLabel.text = self.describeArray[_currentIndex]; 394 | } 395 | 396 | [_scrollView setContentOffset:CGPointMake(width, 0) animated:NO]; 397 | } 398 | 399 | #pragma mark - Public Methods 400 | 401 | - (void)setCurrentPageIndicatorTintColor:(UIColor *)currentPageIndicatorTintColor { 402 | if (_currentPageIndicatorTintColor != currentPageIndicatorTintColor) { 403 | _currentPageIndicatorTintColor = currentPageIndicatorTintColor; 404 | _pageControl.currentPageIndicatorTintColor = currentPageIndicatorTintColor; 405 | } 406 | } 407 | 408 | - (void)setPageIndicatorTintColor:(UIColor *)pageIndicatorTintColor { 409 | if (_pageIndicatorTintColor != pageIndicatorTintColor) { 410 | _pageIndicatorTintColor = pageIndicatorTintColor; 411 | _pageControl.pageIndicatorTintColor = pageIndicatorTintColor; 412 | } 413 | } 414 | 415 | - (void)setCurrentPageIndicatorImage:(UIImage *)currentPageIndicatorImage { 416 | if (_currentPageIndicatorImage != currentPageIndicatorImage) { 417 | _currentPageIndicatorImage = currentPageIndicatorImage; 418 | [_pageControl setValue:currentPageIndicatorImage forKey:@"currentPageImage"]; 419 | } 420 | } 421 | 422 | - (void)setPageIndicatorImage:(UIImage *)pageIndicatorImage { 423 | if (_pageIndicatorImage != pageIndicatorImage) { 424 | _pageIndicatorImage = pageIndicatorImage; 425 | [_pageControl setValue:pageIndicatorImage forKey:@"pageImage"]; 426 | } 427 | } 428 | 429 | - (void)setAutoPagingInterval:(NSTimeInterval)autoPagingInterval { 430 | if (_autoPagingInterval != autoPagingInterval) { 431 | _autoPagingInterval = autoPagingInterval; 432 | [self startAutoPagingTimer]; 433 | } 434 | } 435 | 436 | + (void)clearCachedImages { 437 | [SRCarouselImageManager clearCachedImages]; 438 | } 439 | 440 | @end 441 | 442 | #define SRImagesDirectory [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] \ 443 | stringByAppendingPathComponent:NSStringFromClass([self class])] 444 | 445 | //#define SRImageName(URLString) [URLString lastPathComponent] 446 | 447 | //#define SRImagePath(URLString) [SRImagesDirectory stringByAppendingPathComponent:SRImageName(URLString)] 448 | 449 | @interface SRCarouselImageManager () 450 | 451 | @property (nonatomic, strong) NSMutableDictionary *redownloadManager; 452 | 453 | @end 454 | 455 | @implementation SRCarouselImageManager 456 | 457 | + (void)load { 458 | NSString *imagesDirectory = SRImagesDirectory; 459 | BOOL isDirectory = NO; 460 | NSFileManager *fileManager = [NSFileManager defaultManager]; 461 | BOOL isExists = [fileManager fileExistsAtPath:imagesDirectory isDirectory:&isDirectory]; 462 | if (!isExists || !isDirectory) { 463 | [fileManager createDirectoryAtPath:imagesDirectory withIntermediateDirectories:YES attributes:nil error:nil]; 464 | } 465 | } 466 | 467 | - (NSString *)imagePath:(NSString *)URLString { 468 | NSString *imageName = nil; 469 | NSString *query = [NSURL URLWithString:URLString].query; 470 | if (query) { 471 | // https://yixunfiles-ali.yixun.arhieason.com/9535a537ad2538d53ec1a351deff3856_jpg.jpg?x-oss-process=image/format,png 472 | // https://yixunfiles-ali.yixun.arhieason.com/9535a537ad2538d53ec1a351deff3856_jpg.jpg? 473 | // https://yixunfiles-ali.yixun.arhieason.com/9535a537ad2538d53ec1a351deff3856_jpg.jpg 474 | // 9535a537ad2538d53ec1a351deff3856_jpg.jpg 475 | imageName = [URLString stringByReplacingOccurrencesOfString:query withString:@""]; 476 | imageName = [imageName stringByReplacingOccurrencesOfString:@"?" withString:@""]; 477 | } 478 | imageName = imageName.lastPathComponent; 479 | return [SRImagesDirectory stringByAppendingPathComponent:imageName]; 480 | } 481 | 482 | - (NSMutableDictionary *)redownloadManager { 483 | if (!_redownloadManager) { 484 | _redownloadManager = [NSMutableDictionary dictionary]; 485 | } 486 | return _redownloadManager; 487 | } 488 | 489 | - (instancetype)init { 490 | if (self = [super init]) { 491 | _repeatCountWhenDownloadFailed = 2; 492 | } 493 | return self; 494 | } 495 | 496 | - (UIImage *)imageFromSandbox:(NSString *)imageURLString { 497 | NSString *imagePath = [self imagePath:imageURLString]; 498 | NSData *data = [NSData dataWithContentsOfFile:imagePath]; 499 | if (data.length > 0 ) { 500 | return [UIImage imageWithData:data]; 501 | } else { 502 | [[NSFileManager defaultManager] removeItemAtPath:imagePath error:NULL]; 503 | } 504 | return nil; 505 | } 506 | 507 | - (void)downloadImage:(NSString *)imageURLString index:(NSInteger)imageIndex { 508 | UIImage *image = [self imageFromSandbox:imageURLString]; 509 | if (image) { 510 | if (self.downloadImageSuccess) { 511 | self.downloadImageSuccess(image, imageIndex); 512 | } 513 | return; 514 | } 515 | [[[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:imageURLString] 516 | completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { 517 | dispatch_async(dispatch_get_main_queue(), ^{ 518 | if (error) { 519 | [self redownloadImage:imageURLString index:imageIndex error:error]; 520 | return; 521 | } 522 | UIImage *image = [UIImage imageWithData:data]; 523 | if (!image) { 524 | return; 525 | } 526 | if (self.downloadImageSuccess) { 527 | self.downloadImageSuccess(image, imageIndex); 528 | } 529 | if (![data writeToFile:[self imagePath:imageURLString] atomically:YES]) { 530 | NSLog(@"writeToFile Failed!"); 531 | } 532 | }); 533 | }] resume]; 534 | } 535 | 536 | - (void)redownloadImage:(NSString *)imageURLString index:(NSInteger)imageIndex error:(NSError *)error { 537 | NSNumber *redownloadNumber = self.redownloadManager[imageURLString]; 538 | NSInteger redownloadTimes = redownloadNumber ? redownloadNumber.integerValue : 0; 539 | if (self.repeatCountWhenDownloadFailed > redownloadTimes) { 540 | self.redownloadManager[imageURLString] = @(++redownloadTimes); 541 | [self downloadImage:imageURLString index:imageIndex]; 542 | return; 543 | } 544 | if (self.downloadImageFailure) { 545 | self.downloadImageFailure(error, imageURLString); 546 | } 547 | } 548 | 549 | + (void)clearCachedImages { 550 | NSFileManager *fileManager = [NSFileManager defaultManager]; 551 | NSArray *fileNames = [fileManager contentsOfDirectoryAtPath:SRImagesDirectory error:nil]; 552 | for (NSString *fileName in fileNames) { 553 | if (![fileManager removeItemAtPath:[SRImagesDirectory stringByAppendingPathComponent:fileName] error:nil]) { 554 | NSLog(@"removeItemAtPath Failed!"); 555 | } 556 | } 557 | } 558 | 559 | @end 560 | --------------------------------------------------------------------------------