├── WSL_RollView ├── Assets.xcassets │ ├── Contents.json │ └── AppIcon.appiconset │ │ └── Contents.json ├── ViewController.h ├── VerticalRollViewController.h ├── HorizontalRollViewController.h ├── AppDelegate.h ├── main.m ├── Info.plist ├── AppDelegate.m ├── ViewController.m ├── PrefixHeader.pch ├── WSLRollView.h ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── VerticalRollViewController.m ├── HorizontalRollViewController.m └── WSLRollView.m ├── WSL_RollView.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcuserdata │ │ └── wangshuanglong.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcuserdata │ └── wangshuanglong.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ ├── xcschememanagement.plist │ │ └── WSL_RollView.xcscheme └── project.pbxproj ├── WSL_RollViewTests ├── Info.plist └── WSL_RollViewTests.m ├── WSL_RollViewUITests ├── Info.plist └── WSL_RollViewUITests.m └── README.md /WSL_RollView/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /WSL_RollView.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /WSL_RollView.xcodeproj/project.xcworkspace/xcuserdata/wangshuanglong.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wsl2ls/WSL_RollView/HEAD/WSL_RollView.xcodeproj/project.xcworkspace/xcuserdata/wangshuanglong.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /WSL_RollView/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // WSL_RollView 4 | // 5 | // Created by 王双龙 on 2018/9/4. 6 | // Copyright © 2018年 https://www.jianshu.com/u/e15d1f644bea. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /WSL_RollView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /WSL_RollView/VerticalRollViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // VerticalRollViewController.h 3 | // WSL_RollView 4 | // 5 | // Created by 王双龙 on 2018/9/4. 6 | // Copyright © 2018年 https://www.jianshu.com/u/e15d1f644bea. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface VerticalRollViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /WSL_RollView/HorizontalRollViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // HorizontalRollViewController.h 3 | // WSL_RollView 4 | // 5 | // Created by 王双龙 on 2018/9/9. 6 | // Copyright © 2018年 https://www.jianshu.com/u/e15d1f644bea. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface HorizontalRollViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /WSL_RollView/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // WSL_RollView 4 | // 5 | // Created by 王双龙 on 2018/9/4. 6 | // Copyright © 2018年 https://www.jianshu.com/u/e15d1f644bea. 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 | -------------------------------------------------------------------------------- /WSL_RollView/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // WSL_RollView 4 | // 5 | // Created by 王双龙 on 2018/9/4. 6 | // Copyright © 2018年 https://www.jianshu.com/u/e15d1f644bea. 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 | -------------------------------------------------------------------------------- /WSL_RollView.xcodeproj/xcuserdata/wangshuanglong.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /WSL_RollViewTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /WSL_RollViewUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /WSL_RollView.xcodeproj/xcuserdata/wangshuanglong.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | WSL_RollView.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 3101BCFA213E5A7A00589B36 16 | 17 | primary 18 | 19 | 20 | 3101BD12213E5A7C00589B36 21 | 22 | primary 23 | 24 | 25 | 3101BD1D213E5A7C00589B36 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /WSL_RollViewTests/WSL_RollViewTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // WSL_RollViewTests.m 3 | // WSL_RollViewTests 4 | // 5 | // Created by 王双龙 on 2018/9/4. 6 | // Copyright © 2018年 https://www.jianshu.com/u/e15d1f644bea. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface WSL_RollViewTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation WSL_RollViewTests 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 | -------------------------------------------------------------------------------- /WSL_RollViewUITests/WSL_RollViewUITests.m: -------------------------------------------------------------------------------- 1 | // 2 | // WSL_RollViewUITests.m 3 | // WSL_RollViewUITests 4 | // 5 | // Created by 王双龙 on 2018/9/4. 6 | // Copyright © 2018年 https://www.jianshu.com/u/e15d1f644bea. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface WSL_RollViewUITests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation WSL_RollViewUITests 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 | -------------------------------------------------------------------------------- /WSL_RollView/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /WSL_RollView/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 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /WSL_RollView/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // WSL_RollView 4 | // 5 | // Created by 王双龙 on 2018/9/4. 6 | // Copyright © 2018年 https://www.jianshu.com/u/e15d1f644bea. 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 | 24 | - (void)applicationWillResignActive:(UIApplication *)application { 25 | // 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. 26 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 27 | } 28 | 29 | 30 | - (void)applicationDidEnterBackground:(UIApplication *)application { 31 | // 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. 32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 33 | } 34 | 35 | 36 | - (void)applicationWillEnterForeground:(UIApplication *)application { 37 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 38 | } 39 | 40 | 41 | - (void)applicationDidBecomeActive:(UIApplication *)application { 42 | // 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. 43 | } 44 | 45 | 46 | - (void)applicationWillTerminate:(UIApplication *)application { 47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 48 | } 49 | 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /WSL_RollView/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // WSL_RollView 4 | // 5 | // Created by 王双龙 on 2018/9/4. 6 | // Copyright © 2018年 https://www.jianshu.com/u/e15d1f644bea. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "HorizontalRollViewController.h" 11 | #import "VerticalRollViewController.h" 12 | 13 | @interface ViewController () 14 | 15 | @property (weak, nonatomic) IBOutlet UITableView *tableView; 16 | 17 | @property (nonatomic, strong) NSArray * dataSource; 18 | @property (nonatomic, strong) NSArray * vcArray; 19 | 20 | @end 21 | 22 | @implementation ViewController 23 | 24 | - (void)viewDidLoad { 25 | [super viewDidLoad]; 26 | self.navigationItem.title = @"跑马灯、轮播效果"; 27 | _dataSource = @[@"水平滚动",@"垂直滚动"]; 28 | _vcArray = @[@"HorizontalRollViewController", @"VerticalRollViewController"]; 29 | } 30 | 31 | #pragma mark -- UITableViewDelegate UITableViewDataSource 32 | 33 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{ 34 | return 1; 35 | } 36 | 37 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ 38 | return _dataSource.count; 39 | } 40 | 41 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ 42 | return 80; 43 | } 44 | 45 | - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{ 46 | return 0; 47 | } 48 | - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{ 49 | return nil; 50 | } 51 | 52 | - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section{ 53 | return 0; 54 | } 55 | - (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section{ 56 | return nil; 57 | } 58 | 59 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ 60 | UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"cellID"]; 61 | if (cell == nil) { 62 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:@"cellID"]; 63 | } 64 | 65 | cell.textLabel.text = _dataSource[indexPath.row]; 66 | 67 | return cell; 68 | } 69 | 70 | - (void )tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{ 71 | [tableView deselectRowAtIndexPath:indexPath animated:NO]; 72 | 73 | [self.navigationController pushViewController:[NSClassFromString(_vcArray[indexPath.row]) new] animated:NO]; 74 | } 75 | 76 | 77 | - (void)didReceiveMemoryWarning { 78 | [super didReceiveMemoryWarning]; 79 | // Dispose of any resources that can be recreated. 80 | } 81 | 82 | 83 | @end 84 | -------------------------------------------------------------------------------- /WSL_RollView/PrefixHeader.pch: -------------------------------------------------------------------------------- 1 | // 2 | // PrefixHeader.pch 3 | // 4 | // Created by 王双龙 on 2018/3/30. 5 | // Copyright © 2018年 王双龙. All rights reserved. 6 | // 7 | 8 | #ifndef PrefixHeader_pch 9 | #define PrefixHeader_pch 10 | 11 | //设备型号 12 | #define iPhone4 ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(640, 960), [[UIScreen mainScreen] currentMode].size) : NO) 13 | #define iPhone5 ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(640, 1136), [[UIScreen mainScreen] currentMode].size) : NO) 14 | #define iPhone6 ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(750, 1334), [[UIScreen mainScreen] currentMode].size) : NO) 15 | #define iPhone6Plus ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1242, 2208), [[UIScreen mainScreen] currentMode].size) : NO) 16 | #define iPhone6PlusScale ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1125, 2001), [[UIScreen mainScreen] currentMode].size) : NO) 17 | #define iPhoneX ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1125, 2436), [[UIScreen mainScreen] currentMode].size) : NO) 18 | 19 | //竖屏幕宽高 20 | #define SCREEN_WIDTH ([UIScreen mainScreen].bounds.size.width) 21 | #define SCREEN_HEIGHT ([UIScreen mainScreen].bounds.size.height) 22 | 23 | //导航栏 24 | #define StatusBarHeight (iPhoneX ? 44.f : 20.f) 25 | #define StatusBarAndNavigationBarHeight (iPhoneX ? 88.f : 64.f) 26 | #define TabbarHeight (iPhoneX ? (49.f + 34.f) : (49.f)) 27 | #define BottomSafeAreaHeight (iPhoneX ? (34.f) : (0.f)) 28 | 29 | 30 | //----------------------ABOUT PRINTING LOG 打印日志 ---------------------------- 31 | //Using dlog to print while in debug model. 调试状态下打印日志 32 | #ifdef DEBUG 33 | # define WSL_Log(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__); 34 | #else 35 | # define WSL_Log(...) 36 | #endif 37 | 38 | //Printing while in the debug model and pop an alert. 模式下打印日志,当前行 并弹出一个警告 39 | #ifdef DEBUG 40 | # define WSL_ULog(fmt, ...) { UIAlertView *alert = [[UIAlertView alloc] initWithTitle:[NSString stringWithFormat:@"%s\n [Line %d] ", __PRETTY_FUNCTION__, __LINE__] message:[NSString stringWithFormat:fmt, ##__VA_ARGS__] delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil]; [alert show]; } 41 | #else 42 | # define WSL_ULog(...) 43 | #endif 44 | 45 | #define RGBCOLOR(r,g,b) [UIColor colorWithRed:(r)/255.0f green:(g)/255.0f blue:(b)/255.0f alpha:1] 46 | //随机颜色 47 | #define RGBRANDOMCOLOR [UIColor colorWithRed:arc4random()%255/255.0 green:arc4random()%255/255.0 blue:arc4random()%255/255.0 alpha:1.0] 48 | #define RGBACOLOR(r,g,b,a) [UIColor colorWithRed:(r)/255.0f green:(g)/255.0f blue:(b)/255.0f alpha:(a)] 49 | // rgb颜色转换(16进制->10进制) 50 | #define UIColorFromRGB(rgbValue) [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 green:((float)((rgbValue & 0xFF00) >> 8))/255.0 blue:((float)(rgbValue & 0xFF))/255.0 alpha:1.0] 51 | 52 | 53 | 54 | #endif /* PrefixHeader_pch */ 55 | -------------------------------------------------------------------------------- /WSL_RollView/WSLRollView.h: -------------------------------------------------------------------------------- 1 | // 2 | // WSLRollView.h 3 | // WSL_RollView 4 | // 5 | // Created by 王双龙 on 2018/9/8. 6 | // Copyright © 2018年 https://www.jianshu.com/u/e15d1f644bea. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /** 12 | 默认cell样式 WSLItemID 13 | */ 14 | @interface WSLRollViewCell : UICollectionViewCell 15 | @end 16 | 17 | @class WSLRollView; 18 | 19 | //代理协议 20 | @protocol WSLRollViewDelegate 21 | @optional 22 | /** 23 | 返回itemSize 默认值是CGSizeMake(self.frame.size.width, self.frame.size.height); 24 | */ 25 | - (CGSize)rollView:(WSLRollView *)rollView sizeForItemAtIndex:(NSInteger)index; 26 | /** 27 | item的间隔 默认值0 28 | */ 29 | - (CGFloat)spaceOfItemInRollView:(WSLRollView *)rollView; 30 | /** 31 | 内边距 上 左 下 右 默认值UIEdgeInsetsMake(0, 0, 0, 0) 32 | */ 33 | - (UIEdgeInsets)paddingOfRollView:(WSLRollView *)rollView; 34 | /** 35 | 点击事件 36 | */ 37 | - (void)rollView:(WSLRollView *)rollView didSelectItemAtIndex:(NSInteger)index; 38 | /** 39 | 翻页完成的回调 只针对于分页效果 40 | */ 41 | - (void)rollView:(WSLRollView *)rollView didRollItemToIndex:(NSInteger)currentIndex; 42 | /** 43 | 自定义item样式 44 | */ 45 | - (WSLRollViewCell *)rollView:(WSLRollView *)rollView cellForItemAtIndex:(NSInteger )index; 46 | @end 47 | 48 | /** 49 | 滚动样式 50 | */ 51 | typedef NS_ENUM(NSInteger, WSLRollViewScrollStyle) { 52 | WSLRollViewScrollStylePage = 0, /** 分页 必须等宽或高*/ 53 | WSLRollViewScrollStyleStep /** 渐进 可以不等宽或高*/ 54 | }; 55 | 56 | @interface WSLRollView : UIView 57 | 58 | /** 59 | 原始数据源 60 | */ 61 | @property (nonatomic, strong) NSMutableArray * sourceArray; 62 | 63 | /** 64 | 是否循环轮播 默认YES 如果NO,则自动禁止计时器 65 | */ 66 | @property (nonatomic, assign) BOOL loopEnabled; 67 | /** 68 | 是否允许滑动 默认YES 69 | */ 70 | @property (nonatomic, assign) BOOL scrollEnabled; 71 | /** 72 | 轮播方向 默认是 UICollectionViewScrollDirectionHorizontal 水平 73 | */ 74 | @property (nonatomic, assign) UICollectionViewScrollDirection scrollDirection; 75 | /** 76 | 轮播样式 默认是 WSLRollViewScrollStylePage 分页 77 | */ 78 | @property (nonatomic, assign) WSLRollViewScrollStyle scrollStyle; 79 | /** 80 | 设置初始化时的位置页码,默认为0 只对分页效果有效 81 | */ 82 | @property (nonatomic, assign) NSInteger startingPosition; 83 | 84 | /** 85 | 渐进轮播速率 单位是Point/s,以坐标系单位为准 默认60/s 如果为0 表示禁止计时器 86 | */ 87 | @property (nonatomic, assign) CGFloat speed; 88 | /** 89 | 分页轮播间隔时长 单位是s 默认3s 如果为0 表示禁止计时器 90 | */ 91 | @property (nonatomic, assign) CGFloat interval; 92 | 93 | /** 94 | item的间隔 默认值0 95 | */ 96 | @property (nonatomic, assign) CGFloat spaceOfItem; 97 | 98 | /** 99 | 内边距 上 左 下 右 默认值UIEdgeInsetsMake(0, 0, 0, 0) 100 | */ 101 | @property (nonatomic, assign) UIEdgeInsets padding; 102 | 103 | /** delegate*/ 104 | @property (nonatomic, weak) id delegate; 105 | 106 | /** 107 | 初始化方法 direction 滚动方向 108 | */ 109 | - (instancetype)initWithFrame:(CGRect)frame scrollDirection:(UICollectionViewScrollDirection)direction; 110 | 111 | /** 112 | 注册item样式 用法和UICollectionView相似 113 | */ 114 | - (void)registerClass:(nullable Class)cellClass forCellWithReuseIdentifier:(NSString *)identifier; 115 | /** 116 | 注册item样式 用法和UICollectionView相似 117 | */ 118 | - (void)registerNib:(nullable UINib *)nib forCellWithReuseIdentifier:(NSString *)identifier; 119 | /** 120 | 用于初始化WSLRollViewCell,自定义cell样式 用法和UICollectionView相似 121 | */ 122 | - (WSLRollViewCell *)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndex:(NSInteger)index; 123 | //返回索引为index的cell 124 | - (WSLRollViewCell *)cellForItemAtIndexPath:(NSInteger)index; 125 | 126 | /** 127 | 刷新数据源 128 | */ 129 | - (void)reloadData; 130 | /** 131 | 暂停自动轮播 132 | */ 133 | - (void)pause; 134 | /** 135 | 继续自动轮播 136 | */ 137 | - (void)play; 138 | 139 | @end 140 | -------------------------------------------------------------------------------- /WSL_RollView/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 | 30 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /WSL_RollView.xcodeproj/xcuserdata/wangshuanglong.xcuserdatad/xcschemes/WSL_RollView.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 43 | 49 | 50 | 51 | 52 | 53 | 59 | 60 | 61 | 62 | 63 | 64 | 74 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 93 | 95 | 101 | 102 | 103 | 104 | 106 | 107 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /WSL_RollView/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 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /WSL_RollView/VerticalRollViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // VerticalRollViewController.m 3 | // WSL_RollView 4 | // 5 | // Created by 王双龙 on 2018/9/4. 6 | // Copyright © 2018年 https://www.jianshu.com/u/e15d1f644bea. All rights reserved. 7 | // 8 | 9 | #import "VerticalRollViewController.h" 10 | #import "WSLRollView.h" 11 | #import "PrefixHeader.pch" 12 | 13 | #define KRollViewHeight 200 14 | 15 | @interface WSLRollViewVerticalCell : WSLRollViewCell 16 | @property (strong, nonatomic) UILabel * titleLabel; 17 | @end 18 | 19 | @implementation WSLRollViewVerticalCell 20 | - (instancetype)initWithFrame:(CGRect)frame{ 21 | if (self == [super initWithFrame:frame]) { 22 | self.titleLabel = [[UILabel alloc] init]; 23 | self.titleLabel.textAlignment = NSTextAlignmentCenter; 24 | self.titleLabel.font = [UIFont systemFontOfSize:30]; 25 | self.titleLabel.userInteractionEnabled = YES; 26 | [self.contentView addSubview:self.titleLabel]; 27 | self.contentView.clipsToBounds = YES; 28 | } 29 | return self; 30 | } 31 | - (void)refreshData{ 32 | self.titleLabel.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height); 33 | } 34 | @end 35 | 36 | @interface VerticalRollViewController () 37 | { 38 | NSMutableArray * _array; 39 | } 40 | @end 41 | 42 | @implementation VerticalRollViewController 43 | 44 | - (void)viewDidLoad { 45 | [super viewDidLoad]; 46 | 47 | self.view.backgroundColor = [UIColor whiteColor]; 48 | self.navigationItem.title = @"垂直滚动"; 49 | _array = [NSMutableArray array]; 50 | for (int i = 0 ; i < 3; i++) { 51 | [_array addObject:@{@"title":[NSString stringWithFormat:@"第%d页",i],@"color":RGBRANDOMCOLOR,@"height":@(arc4random()%(int)(KRollViewHeight)),@"width":@(SCREEN_WIDTH)}]; 52 | } 53 | 54 | WSLRollView * pageRollView = [[WSLRollView alloc] initWithFrame:CGRectMake(0,StatusBarAndNavigationBarHeight + 50 , SCREEN_WIDTH, KRollViewHeight)]; 55 | pageRollView.sourceArray = [NSMutableArray arrayWithArray:_array]; 56 | pageRollView.backgroundColor = [UIColor blackColor]; 57 | pageRollView.scrollStyle = WSLRollViewScrollStylePage; 58 | pageRollView.scrollDirection = UICollectionViewScrollDirectionVertical; 59 | pageRollView.interval = 2; 60 | pageRollView.delegate = self; 61 | [pageRollView registerClass:[WSLRollViewVerticalCell class] forCellWithReuseIdentifier:@"PageRollID"]; 62 | [self.view addSubview:pageRollView]; 63 | [pageRollView reloadData]; 64 | 65 | WSLRollView * stepRollView = [[WSLRollView alloc] initWithFrame:CGRectMake(0,pageRollView.frame.origin.y + KRollViewHeight + 50, SCREEN_WIDTH, KRollViewHeight)]; 66 | stepRollView.sourceArray = [NSMutableArray arrayWithArray:_array]; 67 | stepRollView.backgroundColor = [UIColor blackColor]; 68 | stepRollView.scrollStyle = WSLRollViewScrollStyleStep; 69 | stepRollView.scrollDirection = UICollectionViewScrollDirectionVertical; 70 | stepRollView.speed = 120; 71 | stepRollView.delegate = self; 72 | [stepRollView registerClass:[WSLRollViewVerticalCell class] forCellWithReuseIdentifier:@"StepRollID"]; 73 | [self.view addSubview:stepRollView]; 74 | [stepRollView reloadData]; 75 | } 76 | 77 | - (void)dealloc{ 78 | WSL_Log(@"WSLRollView计时器已释放"); 79 | } 80 | 81 | #pragma mark - WSLRollViewDelegate 82 | 83 | //返回itemSize 84 | - (CGSize)rollView:(WSLRollView *)rollView sizeForItemAtIndex:(NSInteger)index{ 85 | if (rollView.scrollStyle == WSLRollViewScrollStylePage){ 86 | return CGSizeMake(SCREEN_WIDTH, 150); 87 | }else{ 88 | NSNumber * width = _array[index][@"width"]; 89 | NSNumber * height = _array[index][@"height"]; 90 | return CGSizeMake([width floatValue], [height floatValue]); 91 | } 92 | } 93 | 94 | //间隔 95 | - (CGFloat)spaceOfItemInRollView:(WSLRollView *)rollView{ 96 | if (rollView.scrollStyle == WSLRollViewScrollStylePage){ 97 | return 0; 98 | }else{ 99 | return 10; 100 | } 101 | } 102 | 103 | //内边距 104 | - (UIEdgeInsets)paddingOfRollView:(WSLRollView *)rollView{ 105 | if (rollView.scrollStyle == WSLRollViewScrollStylePage){ 106 | return UIEdgeInsetsMake(0,0,0,0); 107 | }else{ 108 | return UIEdgeInsetsMake(0,0,0,0); 109 | } 110 | } 111 | 112 | //点击事件 113 | - (void)rollView:(WSLRollView *)rollView didSelectItemAtIndex:(NSInteger)index{ 114 | WSL_Log(@" 点击了 %ld",index); 115 | } 116 | 117 | //当前页码 118 | - (void)rollView:(WSLRollView *)rollView didRollItemToIndex:(NSInteger)currentIndex{ 119 | WSL_Log(@"当前页码 %ld",currentIndex); 120 | } 121 | 122 | //返回自定义cell样式 123 | -(WSLRollViewCell *)rollView:(WSLRollView *)rollView cellForItemAtIndex:(NSInteger)index{ 124 | 125 | WSLRollViewVerticalCell * cell; 126 | if (rollView.scrollStyle == WSLRollViewScrollStylePage){ 127 | cell = (WSLRollViewVerticalCell *)[rollView dequeueReusableCellWithReuseIdentifier:@"PageRollID" forIndex:index]; 128 | }else{ 129 | cell = (WSLRollViewVerticalCell *)[rollView dequeueReusableCellWithReuseIdentifier:@"StepRollID" forIndex:index]; 130 | } 131 | 132 | cell.backgroundColor = _array[index][@"color"]; 133 | cell.titleLabel.text = _array[index][@"title"]; 134 | [cell refreshData]; 135 | 136 | return cell; 137 | } 138 | - (void)didReceiveMemoryWarning { 139 | [super didReceiveMemoryWarning]; 140 | // Dispose of any resources that can be recreated. 141 | } 142 | 143 | /* 144 | #pragma mark - Navigation 145 | 146 | // In a storyboard-based application, you will often want to do a little preparation before navigation 147 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 148 | // Get the new view controller using [segue destinationViewController]. 149 | // Pass the selected object to the new view controller. 150 | } 151 | */ 152 | 153 | @end 154 | -------------------------------------------------------------------------------- /WSL_RollView/HorizontalRollViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // HorizontalRollViewController.m 3 | // WSL_RollView 4 | // 5 | // Created by 王双龙 on 2018/9/9. 6 | // Copyright © 2018年 https://www.jianshu.com/u/e15d1f644bea. All rights reserved. 7 | // 8 | 9 | #import "HorizontalRollViewController.h" 10 | #import "WSLRollView.h" 11 | #import "PrefixHeader.pch" 12 | 13 | #define KRollViewHeight 150 14 | 15 | @interface WSLRollViewHorizontalCell : WSLRollViewCell 16 | @property (strong, nonatomic) UILabel * titleLabel; 17 | @end 18 | 19 | @implementation WSLRollViewHorizontalCell 20 | - (instancetype)initWithFrame:(CGRect)frame{ 21 | if (self == [super initWithFrame:frame]) { 22 | self.titleLabel = [[UILabel alloc] init]; 23 | self.titleLabel.textAlignment = NSTextAlignmentCenter; 24 | self.titleLabel.font = [UIFont systemFontOfSize:30]; 25 | self.titleLabel.userInteractionEnabled = YES; 26 | [self.contentView addSubview:self.titleLabel]; 27 | self.contentView.clipsToBounds = YES; 28 | // self.contentView.autoresizesSubviews = YES; 29 | // self.titleLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 30 | } 31 | return self; 32 | } 33 | - (void)refreshData{ 34 | self.titleLabel.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height); 35 | } 36 | @end 37 | 38 | @interface HorizontalRollViewController () 39 | { 40 | NSMutableArray * _array; 41 | } 42 | @end 43 | 44 | @implementation HorizontalRollViewController 45 | 46 | - (void)viewDidLoad { 47 | [super viewDidLoad]; 48 | 49 | self.view.backgroundColor = [UIColor whiteColor]; 50 | self.navigationItem.title = @"水平滚动"; 51 | _array = [NSMutableArray array]; 52 | for (int i = 0 ; i < 6; i++) { 53 | [_array addObject:@{@"title":[NSString stringWithFormat:@"第%d页",i],@"color":RGBRANDOMCOLOR,@"width":@(arc4random()%(int)(SCREEN_WIDTH)),@"height":@(KRollViewHeight)}]; 54 | } 55 | 56 | WSLRollView * pageRollView = [[WSLRollView alloc] initWithFrame:CGRectMake(0,StatusBarAndNavigationBarHeight + 50 , SCREEN_WIDTH, KRollViewHeight)]; 57 | pageRollView.sourceArray = [NSMutableArray arrayWithArray:_array]; 58 | pageRollView.backgroundColor = [UIColor blackColor]; 59 | pageRollView.scrollStyle = WSLRollViewScrollStylePage; 60 | pageRollView.scrollDirection = UICollectionViewScrollDirectionHorizontal; 61 | pageRollView.interval = 2; 62 | pageRollView.startingPosition = 1; 63 | pageRollView.loopEnabled = YES; 64 | pageRollView.delegate = self; 65 | [pageRollView registerClass:[WSLRollViewHorizontalCell class] forCellWithReuseIdentifier:@"PageRollID"]; 66 | [self.view addSubview:pageRollView]; 67 | 68 | //GCD延迟 69 | // NSMutableArray * array = [NSMutableArray arrayWithArray:_array]; 70 | // dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 71 | // pageRollView.sourceArray = array; 72 | // [pageRollView reloadData]; 73 | // }); 74 | 75 | 76 | WSLRollView * stepRollView = [[WSLRollView alloc] initWithFrame:CGRectMake(0,pageRollView.frame.origin.y + KRollViewHeight + 50, SCREEN_WIDTH, KRollViewHeight)]; 77 | stepRollView.sourceArray = [NSMutableArray arrayWithArray:_array]; 78 | stepRollView.backgroundColor = [UIColor blackColor]; 79 | stepRollView.scrollStyle = WSLRollViewScrollStyleStep; 80 | stepRollView.scrollDirection = UICollectionViewScrollDirectionHorizontal; 81 | stepRollView.loopEnabled = YES; 82 | stepRollView.speed = 120; 83 | stepRollView.delegate = self; 84 | [stepRollView registerClass:[WSLRollViewHorizontalCell class] forCellWithReuseIdentifier:@"StepRollID"]; 85 | [self.view addSubview:stepRollView]; 86 | 87 | } 88 | 89 | - (void)dealloc{ 90 | WSL_Log(@"WSLRollView计时器已释放"); 91 | } 92 | 93 | #pragma mark - WSLRollViewDelegate 94 | 95 | //返回itemSize 96 | - (CGSize)rollView:(WSLRollView *)rollView sizeForItemAtIndex:(NSInteger)index{ 97 | if (rollView.scrollStyle == WSLRollViewScrollStylePage){ 98 | // return CGSizeMake(SCREEN_WIDTH, KRollViewHeight); 99 | // return CGSizeMake((SCREEN_WIDTH - [self spaceOfItemInRollView:rollView] * 2)/2.0, KRollViewHeight); 100 | return CGSizeMake(300, KRollViewHeight); 101 | }else{ 102 | NSNumber * width = _array[index][@"width"]; 103 | NSNumber * height = _array[index][@"height"]; 104 | return CGSizeMake([width floatValue], [height floatValue]); 105 | } 106 | } 107 | 108 | //间隔 109 | - (CGFloat)spaceOfItemInRollView:(WSLRollView *)rollView{ 110 | if (rollView.scrollStyle == WSLRollViewScrollStylePage){ 111 | return 10; 112 | }else{ 113 | return 10; 114 | } 115 | } 116 | 117 | //内边距 118 | - (UIEdgeInsets)paddingOfRollView:(WSLRollView *)rollView{ 119 | if (rollView.scrollStyle == WSLRollViewScrollStylePage){ 120 | return UIEdgeInsetsMake(0,[self spaceOfItemInRollView:rollView],0,[self spaceOfItemInRollView:rollView]); 121 | }else{ 122 | return UIEdgeInsetsMake(0,10,0,10); 123 | } 124 | } 125 | 126 | //点击事件 127 | - (void)rollView:(WSLRollView *)rollView didSelectItemAtIndex:(NSInteger)index{ 128 | WSL_Log(@" 点击了 %ld",index); 129 | } 130 | 131 | //当前页码 132 | - (void)rollView:(WSLRollView *)rollView didRollItemToIndex:(NSInteger)currentIndex{ 133 | WSL_Log(@"当前页码 %ld",currentIndex); 134 | } 135 | 136 | //返回自定义cell样式 137 | -(WSLRollViewCell *)rollView:(WSLRollView *)rollView cellForItemAtIndex:(NSInteger)index{ 138 | 139 | WSLRollViewHorizontalCell * cell; 140 | if (rollView.scrollStyle == WSLRollViewScrollStylePage){ 141 | cell = (WSLRollViewHorizontalCell *)[rollView dequeueReusableCellWithReuseIdentifier:@"PageRollID" forIndex:index]; 142 | }else{ 143 | cell = (WSLRollViewHorizontalCell *)[rollView dequeueReusableCellWithReuseIdentifier:@"StepRollID" forIndex:index]; 144 | } 145 | 146 | cell.backgroundColor = _array[index][@"color"]; 147 | cell.titleLabel.text = _array[index][@"title"]; 148 | [cell refreshData]; 149 | 150 | return cell; 151 | } 152 | 153 | 154 | 155 | - (void)didReceiveMemoryWarning { 156 | [super didReceiveMemoryWarning]; 157 | // Dispose of any resources that can be recreated. 158 | } 159 | 160 | /* 161 | #pragma mark - Navigation 162 | 163 | // In a storyboard-based application, you will often want to do a little preparation before navigation 164 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 165 | // Get the new view controller using [segue destinationViewController]. 166 | // Pass the selected object to the new view controller. 167 | } 168 | */ 169 | 170 | @end 171 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WSL_RollView 2 | 3 | 简书地址:https://www.jianshu.com/p/e73f54f2ea51 4 | 5 | ![iOS UICollectionView实现跑马灯和轮播效果.gif](https://upload-images.jianshu.io/upload_images/1708447-b45d768d5895dfc1.gif?imageMogr2/auto-orient/strip) 6 | 7 | >功能描述:[WSL_RollView](https://github.com/wslcmk/WSL_RollView) 是基于UICollectionView实现的支持水平和垂直两个方向上的的分页和渐进循环轮播效果,可以设置时间间隔、渐进速率、是否循环、分页宽度和间隔,还支持高度自定义分页视图的控件。 8 | 9 | #### 一、实现方法 10 | 11 | ##### ①、 首先用UICollectionView和计时器实现一个基本的水平滚动效果,如下图,这个太简单就不在此详述。 12 | 13 | ![iOS UICollectionView](https://upload-images.jianshu.io/upload_images/1708447-4f6d268a3df79ee2.gif?imageMogr2/auto-orient/strip) 14 | 15 | ##### ②、对比上面的效果图,我们还需要解决分页的宽度和循环滚动的问题。 16 | 17 | > * 自定义分页宽度:默认的分页宽度是UICollectionView的宽度,所以当分页宽度的不等于UICollectionView的宽度或分页间隔不等于0时会出现错误,这时就需要我们通过自定义UICollectionViewFlowLayout来实现效果。 18 | 19 | ``` 20 | 21 | /** 返回值决定了collectionView停止滚动时的偏移量 手指松开后执行 22 | * proposedContentOffset:原本情况下,collectionView停止滚动时最终的偏移量 23 | * velocity 滚动速率,通过这个参数可以了解滚动的方向 24 | */ 25 | - (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity{ 26 | 27 | if (_scrollStyle == WSLRollViewScrollStylePage) { 28 | CGSize size = self.collectionView.frame.size; 29 | // 计算可见区域的面积 30 | CGRect rect = CGRectMake(proposedContentOffset.x, proposedContentOffset.y, size.width, size.height); 31 | NSArray *array = [super layoutAttributesForElementsInRect:rect]; 32 | // 标记 cell 的中点与 UICollectionView 中点最小的间距 33 | CGFloat minDetal = MAXFLOAT; 34 | 35 | if (self.scrollDirection == UICollectionViewScrollDirectionHorizontal){ 36 | // 计算 CollectionView 中点值 37 | CGFloat centerX = proposedContentOffset.x + self.collectionView.frame.size.width * 0.5; 38 | for (UICollectionViewLayoutAttributes *attrs in array){ 39 | if (ABS(minDetal) > ABS(centerX - attrs.center.x)){ 40 | minDetal = attrs.center.x - centerX; 41 | } 42 | } 43 | return CGPointMake(proposedContentOffset.x + minDetal, proposedContentOffset.y); 44 | }else{ 45 | // 计算 CollectionView 中点值 46 | CGFloat centerY = proposedContentOffset.y + self.collectionView.frame.size.height * 0.5; 47 | for (UICollectionViewLayoutAttributes *attrs in array){ 48 | if (ABS(minDetal) > ABS(centerY - attrs.center.y)){ 49 | minDetal = attrs.center.y - centerY; 50 | } 51 | } 52 | return CGPointMake(proposedContentOffset.x, proposedContentOffset.y + minDetal); 53 | } 54 | } 55 | return proposedContentOffset; 56 | } 57 | 58 | ``` 59 | 60 | > * 循环滚动:思想当然还是3 >4 >0 >1 >2 >3 >4 >0 >1,关键就在于怎么确定弥补两端轮播首尾相连需要增加的cell,前边尾首相连需要UICollectionView可见范围内的数据源后边的元素cell,后边首尾相连需要UICollectionView可见范围内的数据源前边的元素cell 61 | 62 | ``` 63 | 64 | //获取首尾相连循环滚动时需要用到的元素,并重组数据源 65 | - (void)resetDataSourceForLoop{ 66 | if(_loopEnabled == NO){ 67 | return; 68 | } 69 | if(_scrollDirection == UICollectionViewScrollDirectionHorizontal && _collectionView.contentSize.width >= self.frame.size.width){ 70 | //用于右侧连接元素数量 71 | _addRightCount = [_collectionView indexPathForItemAtPoint:CGPointMake(self.frame.size.width - 1, 0)].row + 1 ; 72 | if (_scrollStyle == WSLRollViewScrollStylePage){ 73 | //如果是分页,还需要用于左侧连接元素数量 74 | _addLeftCount = _sourceArray.count - [_collectionView indexPathForItemAtPoint:CGPointMake(_collectionView.contentSize.width - self.frame.size.width + 1, 0)].row; 75 | } 76 | }else if(_scrollDirection == UICollectionViewScrollDirectionVertical && _collectionView.contentSize.height >= self.frame.size.height){ 77 | //用于右侧连接元素数量 78 | _addRightCount = [_collectionView indexPathForItemAtPoint:CGPointMake(0, self.frame.size.height - 1)].row + 1 ; 79 | if (_scrollStyle == WSLRollViewScrollStylePage){ 80 | //用于左侧连接元素数量 81 | _addLeftCount = _sourceArray.count - [_collectionView indexPathForItemAtPoint:CGPointMake(0, _collectionView.contentSize.height - self.frame.size.height + 1)].row; 82 | } 83 | } 84 | NSArray * rightSubArray = [_sourceArray subarrayWithRange:NSMakeRange(0, _addRightCount)]; 85 | //增加右侧连接元素 86 | [_dataSource addObjectsFromArray:rightSubArray]; 87 | 88 | if (_scrollStyle == WSLRollViewScrollStylePage){ 89 | NSArray * leftSubArray = [_sourceArray subarrayWithRange:NSMakeRange(_sourceArray.count - _addLeftCount, _addLeftCount)]; 90 | //增加左侧连接元素 91 | [_dataSource insertObjects:leftSubArray atIndexes: [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0,_addLeftCount)]]; 92 | } 93 | } 94 | 95 | ``` 96 | 97 | #### 二、WSL_RollView用法 98 | 99 | > 请看WSLRollView.h文件中的注释,属性和用法很明朗,详情和效果请前往我的Github查看示例:[WSL_RollView](https://github.com/wslcmk/WSL_RollView) 100 | 101 | ``` 102 | // 103 | // WSLRollView.h 104 | // WSL_RollView 105 | // 106 | // Created by 王双龙 on 2018/9/8. 107 | // Copyright © 2018年 https://www.jianshu.com/u/e15d1f644bea. All rights reserved. 108 | // 109 | 110 | #import 111 | 112 | /** 113 | 默认cell样式 WSLItemID 114 | */ 115 | @interface WSLRollViewCell : UICollectionViewCell 116 | @end 117 | 118 | @class WSLRollView; 119 | 120 | //代理协议 121 | @protocol WSLRollViewDelegate 122 | @optional 123 | /** 124 | 返回itemSize 默认值是CGSizeMake(self.frame.size.width, self.frame.size.height); 125 | */ 126 | - (CGSize)rollView:(WSLRollView *)rollView sizeForItemAtIndex:(NSInteger)index; 127 | /** 128 | item的间隔 默认值0 129 | */ 130 | - (CGFloat)spaceOfItemInRollView:(WSLRollView *)rollView; 131 | /** 132 | 内边距 上 左 下 右 默认值UIEdgeInsetsMake(0, 0, 0, 0) 133 | */ 134 | - (UIEdgeInsets)paddingOfRollView:(WSLRollView *)rollView; 135 | /** 136 | 点击事件 137 | */ 138 | - (void)rollView:(WSLRollView *)rollView didSelectItemAtIndex:(NSInteger)index; 139 | /** 140 | 自定义item样式 141 | */ 142 | - (WSLRollViewCell *)rollView:(WSLRollView *)rollView cellForItemAtIndex:(NSInteger )index; 143 | @end 144 | 145 | /** 146 | 滚动样式 147 | */ 148 | typedef NS_ENUM(NSInteger, WSLRollViewScrollStyle) { 149 | WSLRollViewScrollStylePage = 0, /** 分页 必须等宽或高*/ 150 | WSLRollViewScrollStyleStep /** 渐进 可以不等宽或高*/ 151 | }; 152 | 153 | @interface WSLRollView : UIView 154 | 155 | /** 156 | 原始数据源 157 | */ 158 | @property (nonatomic, strong) NSMutableArray * sourceArray; 159 | 160 | /** 161 | 是否循环轮播 默认YES 162 | */ 163 | @property (nonatomic, assign) BOOL loopEnabled; 164 | 165 | /** 166 | 轮播方向 默认是 UICollectionViewScrollDirectionHorizontal 水平 167 | */ 168 | @property (nonatomic, assign) UICollectionViewScrollDirection scrollDirection; 169 | 170 | /** 171 | 轮播样式 默认是 WSLRollViewScrollStylePage 分页 172 | */ 173 | @property (nonatomic, assign) WSLRollViewScrollStyle scrollStyle; 174 | 175 | /** 176 | 渐进轮播速率 单位是Point/s,以坐标系单位为准 默认60/s 如果为0 表示禁止计时器 177 | */ 178 | @property (nonatomic, assign) CGFloat speed; 179 | /** 180 | 分页轮播间隔时长 单位是s 默认3s 如果为0 表示禁止计时器 181 | */ 182 | @property (nonatomic, assign) CGFloat interval; 183 | 184 | /** 185 | item的间隔 默认值0 186 | */ 187 | @property (nonatomic, assign) CGFloat spaceOfItem; 188 | 189 | /** 190 | 内边距 上 左 下 右 默认值UIEdgeInsetsMake(0, 0, 0, 0) 191 | */ 192 | @property (nonatomic, assign) UIEdgeInsets padding; 193 | 194 | /** delegate*/ 195 | @property (nonatomic, weak) id delegate; 196 | 197 | /** 198 | 初始化方法 direction 滚动方向 199 | */ 200 | - (instancetype)initWithFrame:(CGRect)frame scrollDirection:(UICollectionViewScrollDirection)direction; 201 | 202 | /** 203 | 注册item样式 用法和UICollectionView相似 204 | */ 205 | - (void)registerClass:(nullable Class)cellClass forCellWithReuseIdentifier:(NSString *)identifier; 206 | /** 207 | 注册item样式 用法和UICollectionView相似 208 | */ 209 | - (void)registerNib:(nullable UINib *)nib forCellWithReuseIdentifier:(NSString *)identifier; 210 | /** 211 | 用于初始化和获取WSLRollViewCell,自定义cell样式 用法和UICollectionView相似 212 | */ 213 | - (WSLRollViewCell *)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndex:(NSInteger)index; 214 | /** 215 | 刷新数据源 216 | */ 217 | - (void)reloadData; 218 | /** 219 | 暂停自动轮播 220 | */ 221 | - (void)pause; 222 | /** 223 | 继续自动轮播 224 | */ 225 | - (void)play; 226 | 227 | @end 228 | 229 | ``` 230 | 231 | > 以上就是我实现这个效果的过程,如果小伙伴们有其他的实现方法,欢迎再此留言交流😊😊😀😀🤗🤗 232 | 233 | 推荐阅读: 234 | [iOS UITableView/UICollectionView获取特定位置的cell](https://www.jianshu.com/p/70cdcdcb6764) 235 | [iOS 图片浏览的放大缩小](https://www.jianshu.com/p/b5a55099f4fc) 236 | [UIScrollerView当前显示3张图](https://www.jianshu.com/p/2aa464ae84ca) 237 | [iOS 自定义转场动画](https://www.jianshu.com/p/a9b1307b305b) 238 | [iOS 瀑布流封装](https://www.jianshu.com/p/9fafd89c97ad) 239 | [WKWebView的使用](https://www.jianshu.com/p/5cf0d241ae12) 240 | [UIScrollView视觉差动画](https://www.jianshu.com/p/3b30b9cdd274) 241 | [iOS 传感器集锦](https://www.jianshu.com/p/5fc26af852b6) 242 | [iOS 音乐播放器之锁屏歌词+歌词解析+锁屏效果](https://www.jianshu.com/p/35ce7e1076d2) 243 | [UIActivityViewController系统原生分享-仿简书分享](https://www.jianshu.com/p/b6b44662dfda) 244 | 245 | 246 | ![😁😁](https://upload-images.jianshu.io/upload_images/1708447-826d458d52c32bb6.gif?imageMogr2/auto-orient/strip) 247 | -------------------------------------------------------------------------------- /WSL_RollView.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 3101BD00213E5A7A00589B36 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 3101BCFF213E5A7A00589B36 /* AppDelegate.m */; }; 11 | 3101BD03213E5A7A00589B36 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3101BD02213E5A7A00589B36 /* ViewController.m */; }; 12 | 3101BD06213E5A7A00589B36 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3101BD04213E5A7A00589B36 /* Main.storyboard */; }; 13 | 3101BD08213E5A7C00589B36 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3101BD07213E5A7C00589B36 /* Assets.xcassets */; }; 14 | 3101BD0B213E5A7C00589B36 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3101BD09213E5A7C00589B36 /* LaunchScreen.storyboard */; }; 15 | 3101BD0E213E5A7C00589B36 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 3101BD0D213E5A7C00589B36 /* main.m */; }; 16 | 3101BD18213E5A7C00589B36 /* WSL_RollViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3101BD17213E5A7C00589B36 /* WSL_RollViewTests.m */; }; 17 | 3101BD23213E5A7C00589B36 /* WSL_RollViewUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3101BD22213E5A7C00589B36 /* WSL_RollViewUITests.m */; }; 18 | 3101BD32213E604D00589B36 /* VerticalRollViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3101BD31213E604D00589B36 /* VerticalRollViewController.m */; }; 19 | 31ABBA1D2143A223007E004F /* WSLRollView.m in Sources */ = {isa = PBXBuildFile; fileRef = 31ABBA1C2143A223007E004F /* WSLRollView.m */; }; 20 | 31B863F02144EDE900B4CDD7 /* HorizontalRollViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 31B863EF2144EDE900B4CDD7 /* HorizontalRollViewController.m */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXContainerItemProxy section */ 24 | 3101BD14213E5A7C00589B36 /* PBXContainerItemProxy */ = { 25 | isa = PBXContainerItemProxy; 26 | containerPortal = 3101BCF3213E5A7A00589B36 /* Project object */; 27 | proxyType = 1; 28 | remoteGlobalIDString = 3101BCFA213E5A7A00589B36; 29 | remoteInfo = WSL_RollView; 30 | }; 31 | 3101BD1F213E5A7C00589B36 /* PBXContainerItemProxy */ = { 32 | isa = PBXContainerItemProxy; 33 | containerPortal = 3101BCF3213E5A7A00589B36 /* Project object */; 34 | proxyType = 1; 35 | remoteGlobalIDString = 3101BCFA213E5A7A00589B36; 36 | remoteInfo = WSL_RollView; 37 | }; 38 | /* End PBXContainerItemProxy section */ 39 | 40 | /* Begin PBXFileReference section */ 41 | 3101BCFB213E5A7A00589B36 /* WSL_RollView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WSL_RollView.app; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | 3101BCFE213E5A7A00589B36 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 43 | 3101BCFF213E5A7A00589B36 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 44 | 3101BD01213E5A7A00589B36 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 45 | 3101BD02213E5A7A00589B36 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 46 | 3101BD05213E5A7A00589B36 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 47 | 3101BD07213E5A7C00589B36 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 48 | 3101BD0A213E5A7C00589B36 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 49 | 3101BD0C213E5A7C00589B36 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 50 | 3101BD0D213E5A7C00589B36 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 51 | 3101BD13213E5A7C00589B36 /* WSL_RollViewTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = WSL_RollViewTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 52 | 3101BD17213E5A7C00589B36 /* WSL_RollViewTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WSL_RollViewTests.m; sourceTree = ""; }; 53 | 3101BD19213E5A7C00589B36 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 54 | 3101BD1E213E5A7C00589B36 /* WSL_RollViewUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = WSL_RollViewUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 55 | 3101BD22213E5A7C00589B36 /* WSL_RollViewUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WSL_RollViewUITests.m; sourceTree = ""; }; 56 | 3101BD24213E5A7D00589B36 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 57 | 3101BD30213E604C00589B36 /* VerticalRollViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VerticalRollViewController.h; sourceTree = ""; }; 58 | 3101BD31213E604D00589B36 /* VerticalRollViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VerticalRollViewController.m; sourceTree = ""; }; 59 | 3101BD33213E614F00589B36 /* PrefixHeader.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PrefixHeader.pch; sourceTree = ""; }; 60 | 31ABBA1B2143A223007E004F /* WSLRollView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WSLRollView.h; sourceTree = ""; }; 61 | 31ABBA1C2143A223007E004F /* WSLRollView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WSLRollView.m; sourceTree = ""; }; 62 | 31B863EE2144EDE900B4CDD7 /* HorizontalRollViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HorizontalRollViewController.h; sourceTree = ""; }; 63 | 31B863EF2144EDE900B4CDD7 /* HorizontalRollViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HorizontalRollViewController.m; sourceTree = ""; }; 64 | /* End PBXFileReference section */ 65 | 66 | /* Begin PBXFrameworksBuildPhase section */ 67 | 3101BCF8213E5A7A00589B36 /* Frameworks */ = { 68 | isa = PBXFrameworksBuildPhase; 69 | buildActionMask = 2147483647; 70 | files = ( 71 | ); 72 | runOnlyForDeploymentPostprocessing = 0; 73 | }; 74 | 3101BD10213E5A7C00589B36 /* Frameworks */ = { 75 | isa = PBXFrameworksBuildPhase; 76 | buildActionMask = 2147483647; 77 | files = ( 78 | ); 79 | runOnlyForDeploymentPostprocessing = 0; 80 | }; 81 | 3101BD1B213E5A7C00589B36 /* Frameworks */ = { 82 | isa = PBXFrameworksBuildPhase; 83 | buildActionMask = 2147483647; 84 | files = ( 85 | ); 86 | runOnlyForDeploymentPostprocessing = 0; 87 | }; 88 | /* End PBXFrameworksBuildPhase section */ 89 | 90 | /* Begin PBXGroup section */ 91 | 3101BCF2213E5A7A00589B36 = { 92 | isa = PBXGroup; 93 | children = ( 94 | 3101BCFD213E5A7A00589B36 /* WSL_RollView */, 95 | 3101BD16213E5A7C00589B36 /* WSL_RollViewTests */, 96 | 3101BD21213E5A7C00589B36 /* WSL_RollViewUITests */, 97 | 3101BCFC213E5A7A00589B36 /* Products */, 98 | ); 99 | sourceTree = ""; 100 | }; 101 | 3101BCFC213E5A7A00589B36 /* Products */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | 3101BCFB213E5A7A00589B36 /* WSL_RollView.app */, 105 | 3101BD13213E5A7C00589B36 /* WSL_RollViewTests.xctest */, 106 | 3101BD1E213E5A7C00589B36 /* WSL_RollViewUITests.xctest */, 107 | ); 108 | name = Products; 109 | sourceTree = ""; 110 | }; 111 | 3101BCFD213E5A7A00589B36 /* WSL_RollView */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | 3101BCFE213E5A7A00589B36 /* AppDelegate.h */, 115 | 3101BCFF213E5A7A00589B36 /* AppDelegate.m */, 116 | 3101BD33213E614F00589B36 /* PrefixHeader.pch */, 117 | 3101BD01213E5A7A00589B36 /* ViewController.h */, 118 | 3101BD02213E5A7A00589B36 /* ViewController.m */, 119 | 31ABBA1B2143A223007E004F /* WSLRollView.h */, 120 | 31ABBA1C2143A223007E004F /* WSLRollView.m */, 121 | 31B863EE2144EDE900B4CDD7 /* HorizontalRollViewController.h */, 122 | 31B863EF2144EDE900B4CDD7 /* HorizontalRollViewController.m */, 123 | 3101BD30213E604C00589B36 /* VerticalRollViewController.h */, 124 | 3101BD31213E604D00589B36 /* VerticalRollViewController.m */, 125 | 3101BD04213E5A7A00589B36 /* Main.storyboard */, 126 | 3101BD07213E5A7C00589B36 /* Assets.xcassets */, 127 | 3101BD09213E5A7C00589B36 /* LaunchScreen.storyboard */, 128 | 3101BD0C213E5A7C00589B36 /* Info.plist */, 129 | 3101BD0D213E5A7C00589B36 /* main.m */, 130 | ); 131 | path = WSL_RollView; 132 | sourceTree = ""; 133 | }; 134 | 3101BD16213E5A7C00589B36 /* WSL_RollViewTests */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | 3101BD17213E5A7C00589B36 /* WSL_RollViewTests.m */, 138 | 3101BD19213E5A7C00589B36 /* Info.plist */, 139 | ); 140 | path = WSL_RollViewTests; 141 | sourceTree = ""; 142 | }; 143 | 3101BD21213E5A7C00589B36 /* WSL_RollViewUITests */ = { 144 | isa = PBXGroup; 145 | children = ( 146 | 3101BD22213E5A7C00589B36 /* WSL_RollViewUITests.m */, 147 | 3101BD24213E5A7D00589B36 /* Info.plist */, 148 | ); 149 | path = WSL_RollViewUITests; 150 | sourceTree = ""; 151 | }; 152 | /* End PBXGroup section */ 153 | 154 | /* Begin PBXNativeTarget section */ 155 | 3101BCFA213E5A7A00589B36 /* WSL_RollView */ = { 156 | isa = PBXNativeTarget; 157 | buildConfigurationList = 3101BD27213E5A7D00589B36 /* Build configuration list for PBXNativeTarget "WSL_RollView" */; 158 | buildPhases = ( 159 | 3101BCF7213E5A7A00589B36 /* Sources */, 160 | 3101BCF8213E5A7A00589B36 /* Frameworks */, 161 | 3101BCF9213E5A7A00589B36 /* Resources */, 162 | ); 163 | buildRules = ( 164 | ); 165 | dependencies = ( 166 | ); 167 | name = WSL_RollView; 168 | productName = WSL_RollView; 169 | productReference = 3101BCFB213E5A7A00589B36 /* WSL_RollView.app */; 170 | productType = "com.apple.product-type.application"; 171 | }; 172 | 3101BD12213E5A7C00589B36 /* WSL_RollViewTests */ = { 173 | isa = PBXNativeTarget; 174 | buildConfigurationList = 3101BD2A213E5A7D00589B36 /* Build configuration list for PBXNativeTarget "WSL_RollViewTests" */; 175 | buildPhases = ( 176 | 3101BD0F213E5A7C00589B36 /* Sources */, 177 | 3101BD10213E5A7C00589B36 /* Frameworks */, 178 | 3101BD11213E5A7C00589B36 /* Resources */, 179 | ); 180 | buildRules = ( 181 | ); 182 | dependencies = ( 183 | 3101BD15213E5A7C00589B36 /* PBXTargetDependency */, 184 | ); 185 | name = WSL_RollViewTests; 186 | productName = WSL_RollViewTests; 187 | productReference = 3101BD13213E5A7C00589B36 /* WSL_RollViewTests.xctest */; 188 | productType = "com.apple.product-type.bundle.unit-test"; 189 | }; 190 | 3101BD1D213E5A7C00589B36 /* WSL_RollViewUITests */ = { 191 | isa = PBXNativeTarget; 192 | buildConfigurationList = 3101BD2D213E5A7D00589B36 /* Build configuration list for PBXNativeTarget "WSL_RollViewUITests" */; 193 | buildPhases = ( 194 | 3101BD1A213E5A7C00589B36 /* Sources */, 195 | 3101BD1B213E5A7C00589B36 /* Frameworks */, 196 | 3101BD1C213E5A7C00589B36 /* Resources */, 197 | ); 198 | buildRules = ( 199 | ); 200 | dependencies = ( 201 | 3101BD20213E5A7C00589B36 /* PBXTargetDependency */, 202 | ); 203 | name = WSL_RollViewUITests; 204 | productName = WSL_RollViewUITests; 205 | productReference = 3101BD1E213E5A7C00589B36 /* WSL_RollViewUITests.xctest */; 206 | productType = "com.apple.product-type.bundle.ui-testing"; 207 | }; 208 | /* End PBXNativeTarget section */ 209 | 210 | /* Begin PBXProject section */ 211 | 3101BCF3213E5A7A00589B36 /* Project object */ = { 212 | isa = PBXProject; 213 | attributes = { 214 | LastUpgradeCheck = 0940; 215 | ORGANIZATIONNAME = "https://www.jianshu.com/u/e15d1f644bea"; 216 | TargetAttributes = { 217 | 3101BCFA213E5A7A00589B36 = { 218 | CreatedOnToolsVersion = 9.4.1; 219 | }; 220 | 3101BD12213E5A7C00589B36 = { 221 | CreatedOnToolsVersion = 9.4.1; 222 | TestTargetID = 3101BCFA213E5A7A00589B36; 223 | }; 224 | 3101BD1D213E5A7C00589B36 = { 225 | CreatedOnToolsVersion = 9.4.1; 226 | TestTargetID = 3101BCFA213E5A7A00589B36; 227 | }; 228 | }; 229 | }; 230 | buildConfigurationList = 3101BCF6213E5A7A00589B36 /* Build configuration list for PBXProject "WSL_RollView" */; 231 | compatibilityVersion = "Xcode 9.3"; 232 | developmentRegion = en; 233 | hasScannedForEncodings = 0; 234 | knownRegions = ( 235 | en, 236 | Base, 237 | ); 238 | mainGroup = 3101BCF2213E5A7A00589B36; 239 | productRefGroup = 3101BCFC213E5A7A00589B36 /* Products */; 240 | projectDirPath = ""; 241 | projectRoot = ""; 242 | targets = ( 243 | 3101BCFA213E5A7A00589B36 /* WSL_RollView */, 244 | 3101BD12213E5A7C00589B36 /* WSL_RollViewTests */, 245 | 3101BD1D213E5A7C00589B36 /* WSL_RollViewUITests */, 246 | ); 247 | }; 248 | /* End PBXProject section */ 249 | 250 | /* Begin PBXResourcesBuildPhase section */ 251 | 3101BCF9213E5A7A00589B36 /* Resources */ = { 252 | isa = PBXResourcesBuildPhase; 253 | buildActionMask = 2147483647; 254 | files = ( 255 | 3101BD0B213E5A7C00589B36 /* LaunchScreen.storyboard in Resources */, 256 | 3101BD08213E5A7C00589B36 /* Assets.xcassets in Resources */, 257 | 3101BD06213E5A7A00589B36 /* Main.storyboard in Resources */, 258 | ); 259 | runOnlyForDeploymentPostprocessing = 0; 260 | }; 261 | 3101BD11213E5A7C00589B36 /* Resources */ = { 262 | isa = PBXResourcesBuildPhase; 263 | buildActionMask = 2147483647; 264 | files = ( 265 | ); 266 | runOnlyForDeploymentPostprocessing = 0; 267 | }; 268 | 3101BD1C213E5A7C00589B36 /* Resources */ = { 269 | isa = PBXResourcesBuildPhase; 270 | buildActionMask = 2147483647; 271 | files = ( 272 | ); 273 | runOnlyForDeploymentPostprocessing = 0; 274 | }; 275 | /* End PBXResourcesBuildPhase section */ 276 | 277 | /* Begin PBXSourcesBuildPhase section */ 278 | 3101BCF7213E5A7A00589B36 /* Sources */ = { 279 | isa = PBXSourcesBuildPhase; 280 | buildActionMask = 2147483647; 281 | files = ( 282 | 3101BD03213E5A7A00589B36 /* ViewController.m in Sources */, 283 | 31B863F02144EDE900B4CDD7 /* HorizontalRollViewController.m in Sources */, 284 | 3101BD32213E604D00589B36 /* VerticalRollViewController.m in Sources */, 285 | 3101BD0E213E5A7C00589B36 /* main.m in Sources */, 286 | 31ABBA1D2143A223007E004F /* WSLRollView.m in Sources */, 287 | 3101BD00213E5A7A00589B36 /* AppDelegate.m in Sources */, 288 | ); 289 | runOnlyForDeploymentPostprocessing = 0; 290 | }; 291 | 3101BD0F213E5A7C00589B36 /* Sources */ = { 292 | isa = PBXSourcesBuildPhase; 293 | buildActionMask = 2147483647; 294 | files = ( 295 | 3101BD18213E5A7C00589B36 /* WSL_RollViewTests.m in Sources */, 296 | ); 297 | runOnlyForDeploymentPostprocessing = 0; 298 | }; 299 | 3101BD1A213E5A7C00589B36 /* Sources */ = { 300 | isa = PBXSourcesBuildPhase; 301 | buildActionMask = 2147483647; 302 | files = ( 303 | 3101BD23213E5A7C00589B36 /* WSL_RollViewUITests.m in Sources */, 304 | ); 305 | runOnlyForDeploymentPostprocessing = 0; 306 | }; 307 | /* End PBXSourcesBuildPhase section */ 308 | 309 | /* Begin PBXTargetDependency section */ 310 | 3101BD15213E5A7C00589B36 /* PBXTargetDependency */ = { 311 | isa = PBXTargetDependency; 312 | target = 3101BCFA213E5A7A00589B36 /* WSL_RollView */; 313 | targetProxy = 3101BD14213E5A7C00589B36 /* PBXContainerItemProxy */; 314 | }; 315 | 3101BD20213E5A7C00589B36 /* PBXTargetDependency */ = { 316 | isa = PBXTargetDependency; 317 | target = 3101BCFA213E5A7A00589B36 /* WSL_RollView */; 318 | targetProxy = 3101BD1F213E5A7C00589B36 /* PBXContainerItemProxy */; 319 | }; 320 | /* End PBXTargetDependency section */ 321 | 322 | /* Begin PBXVariantGroup section */ 323 | 3101BD04213E5A7A00589B36 /* Main.storyboard */ = { 324 | isa = PBXVariantGroup; 325 | children = ( 326 | 3101BD05213E5A7A00589B36 /* Base */, 327 | ); 328 | name = Main.storyboard; 329 | sourceTree = ""; 330 | }; 331 | 3101BD09213E5A7C00589B36 /* LaunchScreen.storyboard */ = { 332 | isa = PBXVariantGroup; 333 | children = ( 334 | 3101BD0A213E5A7C00589B36 /* Base */, 335 | ); 336 | name = LaunchScreen.storyboard; 337 | sourceTree = ""; 338 | }; 339 | /* End PBXVariantGroup section */ 340 | 341 | /* Begin XCBuildConfiguration section */ 342 | 3101BD25213E5A7D00589B36 /* Debug */ = { 343 | isa = XCBuildConfiguration; 344 | buildSettings = { 345 | ALWAYS_SEARCH_USER_PATHS = NO; 346 | CLANG_ANALYZER_NONNULL = YES; 347 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 348 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 349 | CLANG_CXX_LIBRARY = "libc++"; 350 | CLANG_ENABLE_MODULES = YES; 351 | CLANG_ENABLE_OBJC_ARC = YES; 352 | CLANG_ENABLE_OBJC_WEAK = YES; 353 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 354 | CLANG_WARN_BOOL_CONVERSION = YES; 355 | CLANG_WARN_COMMA = YES; 356 | CLANG_WARN_CONSTANT_CONVERSION = YES; 357 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 358 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 359 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 360 | CLANG_WARN_EMPTY_BODY = YES; 361 | CLANG_WARN_ENUM_CONVERSION = YES; 362 | CLANG_WARN_INFINITE_RECURSION = YES; 363 | CLANG_WARN_INT_CONVERSION = YES; 364 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 365 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 366 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 367 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 368 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 369 | CLANG_WARN_STRICT_PROTOTYPES = YES; 370 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 371 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 372 | CLANG_WARN_UNREACHABLE_CODE = YES; 373 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 374 | CODE_SIGN_IDENTITY = "iPhone Developer"; 375 | COPY_PHASE_STRIP = NO; 376 | DEBUG_INFORMATION_FORMAT = dwarf; 377 | ENABLE_STRICT_OBJC_MSGSEND = YES; 378 | ENABLE_TESTABILITY = YES; 379 | GCC_C_LANGUAGE_STANDARD = gnu11; 380 | GCC_DYNAMIC_NO_PIC = NO; 381 | GCC_NO_COMMON_BLOCKS = YES; 382 | GCC_OPTIMIZATION_LEVEL = 0; 383 | GCC_PREPROCESSOR_DEFINITIONS = ( 384 | "DEBUG=1", 385 | "$(inherited)", 386 | ); 387 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 388 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 389 | GCC_WARN_UNDECLARED_SELECTOR = YES; 390 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 391 | GCC_WARN_UNUSED_FUNCTION = YES; 392 | GCC_WARN_UNUSED_VARIABLE = YES; 393 | IPHONEOS_DEPLOYMENT_TARGET = 11.4; 394 | MTL_ENABLE_DEBUG_INFO = YES; 395 | ONLY_ACTIVE_ARCH = YES; 396 | SDKROOT = iphoneos; 397 | }; 398 | name = Debug; 399 | }; 400 | 3101BD26213E5A7D00589B36 /* Release */ = { 401 | isa = XCBuildConfiguration; 402 | buildSettings = { 403 | ALWAYS_SEARCH_USER_PATHS = NO; 404 | CLANG_ANALYZER_NONNULL = YES; 405 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 406 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 407 | CLANG_CXX_LIBRARY = "libc++"; 408 | CLANG_ENABLE_MODULES = YES; 409 | CLANG_ENABLE_OBJC_ARC = YES; 410 | CLANG_ENABLE_OBJC_WEAK = YES; 411 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 412 | CLANG_WARN_BOOL_CONVERSION = YES; 413 | CLANG_WARN_COMMA = YES; 414 | CLANG_WARN_CONSTANT_CONVERSION = YES; 415 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 416 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 417 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 418 | CLANG_WARN_EMPTY_BODY = YES; 419 | CLANG_WARN_ENUM_CONVERSION = YES; 420 | CLANG_WARN_INFINITE_RECURSION = YES; 421 | CLANG_WARN_INT_CONVERSION = YES; 422 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 423 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 424 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 425 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 426 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 427 | CLANG_WARN_STRICT_PROTOTYPES = YES; 428 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 429 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 430 | CLANG_WARN_UNREACHABLE_CODE = YES; 431 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 432 | CODE_SIGN_IDENTITY = "iPhone Developer"; 433 | COPY_PHASE_STRIP = NO; 434 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 435 | ENABLE_NS_ASSERTIONS = NO; 436 | ENABLE_STRICT_OBJC_MSGSEND = YES; 437 | GCC_C_LANGUAGE_STANDARD = gnu11; 438 | GCC_NO_COMMON_BLOCKS = YES; 439 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 440 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 441 | GCC_WARN_UNDECLARED_SELECTOR = YES; 442 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 443 | GCC_WARN_UNUSED_FUNCTION = YES; 444 | GCC_WARN_UNUSED_VARIABLE = YES; 445 | IPHONEOS_DEPLOYMENT_TARGET = 11.4; 446 | MTL_ENABLE_DEBUG_INFO = NO; 447 | SDKROOT = iphoneos; 448 | VALIDATE_PRODUCT = YES; 449 | }; 450 | name = Release; 451 | }; 452 | 3101BD28213E5A7D00589B36 /* Debug */ = { 453 | isa = XCBuildConfiguration; 454 | buildSettings = { 455 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 456 | CODE_SIGN_STYLE = Manual; 457 | DEVELOPMENT_TEAM = 2N77G66QN3; 458 | INFOPLIST_FILE = WSL_RollView/Info.plist; 459 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 460 | LD_RUNPATH_SEARCH_PATHS = ( 461 | "$(inherited)", 462 | "@executable_path/Frameworks", 463 | ); 464 | PRODUCT_BUNDLE_IDENTIFIER = com.chinadaily.ChinaDaily; 465 | PRODUCT_NAME = "$(TARGET_NAME)"; 466 | PROVISIONING_PROFILE_SPECIFIER = "News for iPhone"; 467 | TARGETED_DEVICE_FAMILY = "1,2"; 468 | }; 469 | name = Debug; 470 | }; 471 | 3101BD29213E5A7D00589B36 /* Release */ = { 472 | isa = XCBuildConfiguration; 473 | buildSettings = { 474 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 475 | CODE_SIGN_STYLE = Manual; 476 | DEVELOPMENT_TEAM = 2N77G66QN3; 477 | INFOPLIST_FILE = WSL_RollView/Info.plist; 478 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 479 | LD_RUNPATH_SEARCH_PATHS = ( 480 | "$(inherited)", 481 | "@executable_path/Frameworks", 482 | ); 483 | PRODUCT_BUNDLE_IDENTIFIER = com.chinadaily.ChinaDaily; 484 | PRODUCT_NAME = "$(TARGET_NAME)"; 485 | PROVISIONING_PROFILE_SPECIFIER = "News for iPhone"; 486 | TARGETED_DEVICE_FAMILY = "1,2"; 487 | }; 488 | name = Release; 489 | }; 490 | 3101BD2B213E5A7D00589B36 /* Debug */ = { 491 | isa = XCBuildConfiguration; 492 | buildSettings = { 493 | BUNDLE_LOADER = "$(TEST_HOST)"; 494 | CODE_SIGN_STYLE = Automatic; 495 | INFOPLIST_FILE = WSL_RollViewTests/Info.plist; 496 | LD_RUNPATH_SEARCH_PATHS = ( 497 | "$(inherited)", 498 | "@executable_path/Frameworks", 499 | "@loader_path/Frameworks", 500 | ); 501 | PRODUCT_BUNDLE_IDENTIFIER = "com.chinaDaily.WSL-RollViewTests"; 502 | PRODUCT_NAME = "$(TARGET_NAME)"; 503 | TARGETED_DEVICE_FAMILY = "1,2"; 504 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/WSL_RollView.app/WSL_RollView"; 505 | }; 506 | name = Debug; 507 | }; 508 | 3101BD2C213E5A7D00589B36 /* Release */ = { 509 | isa = XCBuildConfiguration; 510 | buildSettings = { 511 | BUNDLE_LOADER = "$(TEST_HOST)"; 512 | CODE_SIGN_STYLE = Automatic; 513 | INFOPLIST_FILE = WSL_RollViewTests/Info.plist; 514 | LD_RUNPATH_SEARCH_PATHS = ( 515 | "$(inherited)", 516 | "@executable_path/Frameworks", 517 | "@loader_path/Frameworks", 518 | ); 519 | PRODUCT_BUNDLE_IDENTIFIER = "com.chinaDaily.WSL-RollViewTests"; 520 | PRODUCT_NAME = "$(TARGET_NAME)"; 521 | TARGETED_DEVICE_FAMILY = "1,2"; 522 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/WSL_RollView.app/WSL_RollView"; 523 | }; 524 | name = Release; 525 | }; 526 | 3101BD2E213E5A7D00589B36 /* Debug */ = { 527 | isa = XCBuildConfiguration; 528 | buildSettings = { 529 | CODE_SIGN_STYLE = Automatic; 530 | INFOPLIST_FILE = WSL_RollViewUITests/Info.plist; 531 | LD_RUNPATH_SEARCH_PATHS = ( 532 | "$(inherited)", 533 | "@executable_path/Frameworks", 534 | "@loader_path/Frameworks", 535 | ); 536 | PRODUCT_BUNDLE_IDENTIFIER = "com.chinaDaily.WSL-RollViewUITests"; 537 | PRODUCT_NAME = "$(TARGET_NAME)"; 538 | TARGETED_DEVICE_FAMILY = "1,2"; 539 | TEST_TARGET_NAME = WSL_RollView; 540 | }; 541 | name = Debug; 542 | }; 543 | 3101BD2F213E5A7D00589B36 /* Release */ = { 544 | isa = XCBuildConfiguration; 545 | buildSettings = { 546 | CODE_SIGN_STYLE = Automatic; 547 | INFOPLIST_FILE = WSL_RollViewUITests/Info.plist; 548 | LD_RUNPATH_SEARCH_PATHS = ( 549 | "$(inherited)", 550 | "@executable_path/Frameworks", 551 | "@loader_path/Frameworks", 552 | ); 553 | PRODUCT_BUNDLE_IDENTIFIER = "com.chinaDaily.WSL-RollViewUITests"; 554 | PRODUCT_NAME = "$(TARGET_NAME)"; 555 | TARGETED_DEVICE_FAMILY = "1,2"; 556 | TEST_TARGET_NAME = WSL_RollView; 557 | }; 558 | name = Release; 559 | }; 560 | /* End XCBuildConfiguration section */ 561 | 562 | /* Begin XCConfigurationList section */ 563 | 3101BCF6213E5A7A00589B36 /* Build configuration list for PBXProject "WSL_RollView" */ = { 564 | isa = XCConfigurationList; 565 | buildConfigurations = ( 566 | 3101BD25213E5A7D00589B36 /* Debug */, 567 | 3101BD26213E5A7D00589B36 /* Release */, 568 | ); 569 | defaultConfigurationIsVisible = 0; 570 | defaultConfigurationName = Release; 571 | }; 572 | 3101BD27213E5A7D00589B36 /* Build configuration list for PBXNativeTarget "WSL_RollView" */ = { 573 | isa = XCConfigurationList; 574 | buildConfigurations = ( 575 | 3101BD28213E5A7D00589B36 /* Debug */, 576 | 3101BD29213E5A7D00589B36 /* Release */, 577 | ); 578 | defaultConfigurationIsVisible = 0; 579 | defaultConfigurationName = Release; 580 | }; 581 | 3101BD2A213E5A7D00589B36 /* Build configuration list for PBXNativeTarget "WSL_RollViewTests" */ = { 582 | isa = XCConfigurationList; 583 | buildConfigurations = ( 584 | 3101BD2B213E5A7D00589B36 /* Debug */, 585 | 3101BD2C213E5A7D00589B36 /* Release */, 586 | ); 587 | defaultConfigurationIsVisible = 0; 588 | defaultConfigurationName = Release; 589 | }; 590 | 3101BD2D213E5A7D00589B36 /* Build configuration list for PBXNativeTarget "WSL_RollViewUITests" */ = { 591 | isa = XCConfigurationList; 592 | buildConfigurations = ( 593 | 3101BD2E213E5A7D00589B36 /* Debug */, 594 | 3101BD2F213E5A7D00589B36 /* Release */, 595 | ); 596 | defaultConfigurationIsVisible = 0; 597 | defaultConfigurationName = Release; 598 | }; 599 | /* End XCConfigurationList section */ 600 | }; 601 | rootObject = 3101BCF3213E5A7A00589B36 /* Project object */; 602 | } 603 | -------------------------------------------------------------------------------- /WSL_RollView/WSLRollView.m: -------------------------------------------------------------------------------- 1 | // 2 | // WSLRollView.m 3 | // WSL_RollView 4 | // 5 | // Created by 王双龙 on 2018/9/8. 6 | // Copyright © 2018年 https://www.jianshu.com/u/e15d1f644bea. All rights reserved. 7 | // 8 | 9 | //----------------------ABOUT PRINTING LOG 打印日志 ---------------------------- 10 | //Using dlog to print while in debug model. 调试状态下打印日志 11 | #ifdef DEBUG 12 | # define WSL_Log(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__); 13 | #else 14 | # define WSL_Log(...) 15 | #endif 16 | 17 | #import "WSLRollView.h" 18 | 19 | /** 20 | 默认样式 21 | */ 22 | @interface WSLRollViewCell () 23 | @end 24 | @implementation WSLRollViewCell 25 | @end 26 | 27 | //自定义分页布局 28 | @interface WSLRollViewFlowLayout : UICollectionViewFlowLayout 29 | 30 | /** 31 | 轮播样式 默认是 WSLRollViewScrollStylePage 分页 32 | */ 33 | @property (nonatomic, assign) WSLRollViewScrollStyle scrollStyle; 34 | 35 | @end 36 | 37 | @implementation WSLRollViewFlowLayout 38 | 39 | - (void)prepareLayout{ 40 | 41 | // 必须要调用父类(父类也有一些准备操作) 42 | [super prepareLayout]; 43 | } 44 | 45 | /* 46 | 返回collectionView上面所有元素(比如cell)的布局属性:这个方法决定了cell怎么排布 47 | 每个cell都有自己对应的布局属性:UICollectionViewLayoutAttributes 48 | 要求返回的数组中装着UICollectionViewLayoutAttributes对象 49 | */ 50 | - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect{ 51 | 52 | NSArray *array = [super layoutAttributesForElementsInRect:rect]; 53 | /* 54 | if (self.scrollDirection == UICollectionViewScrollDirectionHorizontal){ 55 | // 计算 CollectionView 的中点 56 | CGFloat centerX = self.collectionView.contentOffset.x + self.collectionView.frame.size.width * 0.5; 57 | for (UICollectionViewLayoutAttributes *attrs in array){ 58 | // 计算 cell 中点的 x 值 与 centerX 的差值 59 | CGFloat delta = ABS(centerX - attrs.center.x); 60 | CGFloat scale = 1 - delta / self.collectionView.frame.size.width; 61 | scale = 1; 62 | attrs.transform = CGAffineTransformMakeScale(scale, scale); 63 | } 64 | }else{ 65 | CGFloat centerY = self.collectionView.contentOffset.y + self.collectionView.frame.size.height * 0.5; 66 | for (UICollectionViewLayoutAttributes *attrs in array){ 67 | // 计算 cell 中点的 y 值 与 centerY 的差值 68 | CGFloat delta = ABS(centerY - attrs.center.y); 69 | CGFloat scale = 1 - delta / self.collectionView.frame.size.height; 70 | scale = 1; 71 | attrs.transform = CGAffineTransformMakeScale(scale, scale); 72 | } 73 | } 74 | */ 75 | return array; 76 | } 77 | 78 | // Invalidate:刷新 79 | // 在滚动的时候是否允许刷新布局 80 | - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds{ 81 | return NO; 82 | } 83 | 84 | /** 返回值决定了collectionView停止滚动时的偏移量 手指松开后执行 85 | * proposedContentOffset:原本情况下,collectionView停止滚动时最终的偏移量 86 | * velocity 滚动速率,通过这个参数可以了解滚动的方向 87 | */ 88 | - (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity{ 89 | 90 | if (_scrollStyle == WSLRollViewScrollStylePage) { 91 | CGSize size = self.collectionView.frame.size; 92 | // 计算可见区域的面积 93 | CGRect rect = CGRectMake(proposedContentOffset.x, proposedContentOffset.y, size.width, size.height); 94 | NSArray *array = [super layoutAttributesForElementsInRect:rect]; 95 | // 标记 cell 的中点与 UICollectionView 中点最小的间距 96 | CGFloat minDetal = MAXFLOAT; 97 | 98 | if (self.scrollDirection == UICollectionViewScrollDirectionHorizontal){ 99 | // 计算 CollectionView 中点值 100 | CGFloat centerX = proposedContentOffset.x + self.collectionView.frame.size.width * 0.5; 101 | for (UICollectionViewLayoutAttributes *attrs in array){ 102 | if (ABS(minDetal) > ABS(centerX - attrs.center.x)){ 103 | minDetal = attrs.center.x - centerX; 104 | } 105 | } 106 | return CGPointMake(proposedContentOffset.x + minDetal, proposedContentOffset.y); 107 | }else{ 108 | // 计算 CollectionView 中点值 109 | CGFloat centerY = proposedContentOffset.y + self.collectionView.frame.size.height * 0.5; 110 | for (UICollectionViewLayoutAttributes *attrs in array){ 111 | if (ABS(minDetal) > ABS(centerY - attrs.center.y)){ 112 | minDetal = attrs.center.y - centerY; 113 | } 114 | } 115 | return CGPointMake(proposedContentOffset.x, proposedContentOffset.y + minDetal); 116 | } 117 | } 118 | 119 | return proposedContentOffset; 120 | 121 | } 122 | 123 | @end 124 | 125 | //临时弱引用对象,解决循环引用的问题 引自 YYWeakProxy 126 | @interface WSLWeakProxy : NSProxy 127 | /** 128 | The proxy target. 129 | */ 130 | @property (nullable, nonatomic, weak, readonly) id target; 131 | 132 | @end 133 | 134 | @implementation WSLWeakProxy 135 | 136 | + (instancetype)proxyWithTarget:(id)target { 137 | return [[WSLWeakProxy alloc] initWithTarget:target]; 138 | } 139 | - (instancetype)initWithTarget:(id)target { 140 | _target = target; 141 | return self; 142 | } 143 | //将消息接收对象改为 _target 144 | - (id)forwardingTargetForSelector:(SEL)selector { 145 | return _target; 146 | } 147 | //self 对 target 是弱引用,一旦 target 被释放将调用下面两个方法,如果不实现的话会 crash 148 | - (void)forwardInvocation:(NSInvocation *)invocation { 149 | void *null = NULL; 150 | [invocation setReturnValue:&null]; 151 | } 152 | - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector { 153 | return [NSObject instanceMethodSignatureForSelector:@selector(init)]; 154 | } 155 | - (BOOL)respondsToSelector:(SEL)aSelector { 156 | return [_target respondsToSelector:aSelector]; 157 | } 158 | - (BOOL)isEqual:(id)object { 159 | return [_target isEqual:object]; 160 | } 161 | - (NSUInteger)hash { 162 | return [_target hash]; 163 | } 164 | - (Class)superclass { 165 | return [_target superclass]; 166 | } 167 | - (Class)class { 168 | return [_target class]; 169 | } 170 | - (BOOL)isKindOfClass:(Class)aClass { 171 | return [_target isKindOfClass:aClass]; 172 | } 173 | - (BOOL)isMemberOfClass:(Class)aClass { 174 | return [_target isMemberOfClass:aClass]; 175 | } 176 | - (BOOL)conformsToProtocol:(Protocol *)aProtocol { 177 | return [_target conformsToProtocol:aProtocol]; 178 | } 179 | - (BOOL)isProxy { 180 | return YES; 181 | } 182 | - (NSString *)description { 183 | return [_target description]; 184 | } 185 | - (NSString *)debugDescription { 186 | return [_target debugDescription]; 187 | } 188 | @end 189 | 190 | 191 | @interface WSLRollView () 192 | 193 | //计时器 194 | @property (nonatomic, strong) NSTimer * timer; 195 | //列表视图 196 | @property (nonatomic, strong) UICollectionView * collectionView; 197 | //处理重组后的数据源 198 | @property (nonatomic, strong) NSMutableArray * dataSource; 199 | 200 | //弥补轮播右侧首尾相连需要增加的cell数量 比如:0 1 2 3 4 0 1 2 ,这时addRightCount = 3 201 | @property (nonatomic, assign) NSInteger addRightCount; 202 | //弥补轮播左侧首尾相连需要增加的cell数量 比如:3 4 0 1 2 3 4 ,这时addLeftCount = 2 只有分页效果用的到 203 | @property (nonatomic, assign) NSInteger addLeftCount; 204 | //轮播右侧首尾相连的交汇点位置坐标 只有渐进效果用到 205 | @property (nonatomic, assign) CGPoint connectionPoint; 206 | 207 | //当前源数据的索引 208 | @property (nonatomic, assign) NSInteger currentPage; 209 | 210 | //处理后的数据cell索引 211 | @property (nonatomic, strong) NSIndexPath *indexPath; 212 | 213 | @end 214 | 215 | @implementation WSLRollView 216 | 217 | - (instancetype)initWithFrame:(CGRect)frame{ 218 | if (self == [super initWithFrame:frame]) { 219 | _scrollStyle = WSLRollViewScrollStylePage; 220 | _scrollDirection = UICollectionViewScrollDirectionHorizontal; 221 | _startingPosition = 0; 222 | _speed = 60; 223 | _interval = 3.0; 224 | _spaceOfItem = 0; 225 | _padding = UIEdgeInsetsMake(0, 0, 0, 0); 226 | _loopEnabled = YES; 227 | _scrollEnabled = YES; 228 | [self setupUi]; 229 | } 230 | return self; 231 | } 232 | 233 | - (instancetype)initWithFrame:(CGRect)frame scrollDirection:(UICollectionViewScrollDirection)direction{ 234 | if (self == [super initWithFrame:frame]) { 235 | _scrollStyle = WSLRollViewScrollStylePage; 236 | _startingPosition = 0; 237 | _scrollDirection = direction; 238 | _speed = 60; 239 | _interval = 3.0; 240 | _spaceOfItem = 0; 241 | _padding = UIEdgeInsetsMake(0, 0, 0, 0); 242 | _loopEnabled = YES; 243 | _scrollEnabled = YES; 244 | [self setupUi]; 245 | } 246 | return self; 247 | } 248 | 249 | //获取某视图所在的控制器 250 | + (UIViewController *)viewControllerFromView:(UIView *)view { 251 | for (UIView *next = [view superview]; next; next = next.superview) { 252 | UIResponder *nextResponder = [next nextResponder]; 253 | if ([nextResponder isKindOfClass:[UIViewController class]]) { 254 | return (UIViewController *)nextResponder; 255 | } 256 | } 257 | return nil; 258 | } 259 | 260 | #pragma mark - Help Methods 261 | 262 | - (void)setupUi{ 263 | //注册默认cell 264 | [self.collectionView registerClass:[WSLRollViewCell class] forCellWithReuseIdentifier:@"WSLItemID"]; 265 | [self addSubview:self.collectionView]; 266 | } 267 | 268 | //获取首尾相连循环滚动时需要用到的元素,并重组数据源 269 | - (void)resetDataSourceForLoop{ 270 | 271 | if(_loopEnabled == NO){ 272 | return; 273 | } 274 | 275 | CGSize contentSize = CGSizeMake(0, 0); 276 | for (int i = 0; i < self.sourceArray.count; i++) { 277 | NSIndexPath * indexPath = [NSIndexPath indexPathForRow:i inSection:0]; 278 | if (_scrollDirection == UICollectionViewScrollDirectionHorizontal) { 279 | contentSize.width = contentSize.width + (i == 0 ? _padding.left : 0) + [self collectionView:_collectionView layout:_collectionView.collectionViewLayout sizeForItemAtIndexPath:indexPath].width + (i == self.sourceArray.count - 1 ? _padding.right : _spaceOfItem); 280 | contentSize.height = self.frame.size.height; 281 | }else{ 282 | contentSize.height = contentSize.height + (i == 0 ? _padding.top : 0) + [self collectionView:_collectionView layout:_collectionView.collectionViewLayout sizeForItemAtIndexPath:indexPath].height + (i == self.sourceArray.count - 1 ? _padding.bottom : _spaceOfItem); 283 | contentSize.width = self.frame.size.width; 284 | } 285 | } 286 | 287 | if(_scrollDirection == UICollectionViewScrollDirectionHorizontal && contentSize.width >= self.frame.size.width){ 288 | 289 | //用于右侧连接元素数量 290 | _addRightCount = [_collectionView indexPathForItemAtPoint:CGPointMake(self.frame.size.width - 1, 0)].row + 1 ; 291 | if (_scrollStyle == WSLRollViewScrollStylePage){ 292 | //如果是分页,还需要用于左侧连接元素数量 293 | _addLeftCount = _sourceArray.count - [_collectionView indexPathForItemAtPoint:CGPointMake(contentSize.width - self.frame.size.width + 1, 0)].row; 294 | } 295 | }else if(_scrollDirection == UICollectionViewScrollDirectionVertical && contentSize.height >= self.frame.size.height){ 296 | 297 | //用于右侧连接元素数量 298 | _addRightCount = [_collectionView indexPathForItemAtPoint:CGPointMake(0, self.frame.size.height - 1)].row + 1 ; 299 | if (_scrollStyle == WSLRollViewScrollStylePage){ 300 | //用于左侧连接元素数量 301 | _addLeftCount = _sourceArray.count - [_collectionView indexPathForItemAtPoint:CGPointMake(0, contentSize.height - self.frame.size.height + 1)].row; 302 | } 303 | } 304 | 305 | NSArray * rightSubArray = [_sourceArray subarrayWithRange:NSMakeRange(0, _addRightCount)]; 306 | //增加右侧连接元素 307 | [_dataSource addObjectsFromArray:rightSubArray]; 308 | 309 | if (_scrollStyle == WSLRollViewScrollStylePage){ 310 | NSArray * leftSubArray = [_sourceArray subarrayWithRange:NSMakeRange(_sourceArray.count - _addLeftCount, _addLeftCount)]; 311 | //增加左侧连接元素 312 | [_dataSource insertObjects:leftSubArray atIndexes: [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0,_addLeftCount)]]; 313 | } 314 | 315 | } 316 | 317 | //根据处理后的数据源的索引row 返回原数据的索引index 318 | - (NSInteger)indexOfSourceArray:(NSInteger)row{ 319 | NSInteger index = 0; 320 | if(row < _addLeftCount){ 321 | index = _sourceArray.count - _addLeftCount + row; 322 | }else if (row < _sourceArray.count + _addLeftCount && row >= _addLeftCount) { 323 | index = row - _addLeftCount; 324 | }else{ 325 | index = row % (_sourceArray.count + _addLeftCount); 326 | } 327 | return index; 328 | } 329 | 330 | //根据原数据的索引index 返回处理后的数据源索引row 331 | - (NSInteger)rowOfDataSource:(NSInteger)index{ 332 | NSInteger row = 0; 333 | row = index + _addLeftCount; 334 | return row; 335 | } 336 | 337 | //分页效果 指定原数据的索引index页滚动到当前位置 338 | - (void)rollToIndex:(NSInteger)index{ 339 | if (index >= self.sourceArray.count) { 340 | index = 0; 341 | } 342 | if(_scrollStyle == WSLRollViewScrollStylePage){ 343 | if (_scrollDirection == UICollectionViewScrollDirectionHorizontal) { 344 | [_collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:[self rowOfDataSource:index] inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO]; 345 | }else{ 346 | [_collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:[self rowOfDataSource:index] inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:NO]; 347 | } 348 | if ([self.delegate respondsToSelector:@selector(rollView:didRollItemToIndex:)]) { 349 | [self.delegate rollView:self didRollItemToIndex:index]; 350 | } 351 | } 352 | } 353 | 354 | //获得当前页码 只针对分页效果 355 | - (void)getCurrentIndex{ 356 | if (_scrollStyle == WSLRollViewScrollStylePage){ 357 | NSInteger currentIndex= 0; 358 | if(_scrollDirection == UICollectionViewScrollDirectionHorizontal){ 359 | currentIndex= [_collectionView indexPathForItemAtPoint:CGPointMake(_collectionView.contentOffset.x + self.frame.size.width/2, 0)].row; 360 | }else{ 361 | currentIndex= [_collectionView indexPathForItemAtPoint:CGPointMake(0,_collectionView.contentOffset.y + self.frame.size.height/2)].row; 362 | } 363 | NSInteger currentPage = [self indexOfSourceArray:currentIndex]; 364 | if ([self.delegate respondsToSelector:@selector(rollView:didRollItemToIndex:)] && _currentPage != currentPage) { 365 | _currentPage = currentPage; 366 | [self.delegate rollView:self didRollItemToIndex:_currentPage]; 367 | } 368 | } 369 | } 370 | 371 | //传递进来自定义item的样式,用法和UICollectionView相似 372 | - (void)registerClass:(nullable Class)cellClass forCellWithReuseIdentifier:(NSString *)identifier{ 373 | [_collectionView registerClass:cellClass forCellWithReuseIdentifier:identifier]; 374 | } 375 | - (void)registerNib:(nullable UINib *)nib forCellWithReuseIdentifier:(NSString *)identifier{ 376 | [_collectionView registerNib:nib forCellWithReuseIdentifier:identifier]; 377 | } 378 | - (WSLRollViewCell *)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndex:(NSInteger)index{ 379 | return [_collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:_indexPath]; 380 | } 381 | - (WSLRollViewCell *)cellForItemAtIndexPath:(NSInteger)index{ 382 | return (WSLRollViewCell *)[_collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForRow:[self rowOfDataSource:index] inSection:0]]; 383 | } 384 | 385 | 386 | #pragma mark - Event Handle 387 | 388 | - (void)pause{ 389 | [self invalidateTimer]; 390 | } 391 | 392 | - (void)play{ 393 | 394 | [self invalidateTimer]; 395 | //如果速率或者时间间隔为0,表示不启用计时器 396 | if(_interval == 0 || _speed == 0 || _loopEnabled == NO){ 397 | _collectionView.scrollEnabled = self.scrollEnabled; 398 | return; 399 | } 400 | 401 | if(_scrollStyle == WSLRollViewScrollStylePage){ 402 | _timer = [NSTimer scheduledTimerWithTimeInterval:_interval target:[WSLWeakProxy proxyWithTarget:self] selector:@selector(timerEvent) userInfo:nil repeats:YES]; 403 | [[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes]; 404 | }else if(_scrollStyle == WSLRollViewScrollStyleStep){ 405 | _timer = [NSTimer scheduledTimerWithTimeInterval:1.0/60 target:[WSLWeakProxy proxyWithTarget:self] selector:@selector(timerEvent) userInfo:nil repeats:YES]; 406 | [[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes]; 407 | } 408 | } 409 | 410 | - (void)close{ 411 | [self invalidateTimer]; 412 | for (UIView * subView in self.subviews) { 413 | [subView removeFromSuperview]; 414 | } 415 | // [self removeFromSuperview]; 416 | } 417 | 418 | - (void)willMoveToSuperview:(UIView *)newSuperview { 419 | [super willMoveToSuperview:newSuperview]; 420 | if (!newSuperview && _timer) { 421 | // 销毁定时器 422 | [self close]; 423 | } 424 | } 425 | 426 | - (void)didMoveToSuperview{ 427 | [super didMoveToSuperview]; 428 | if (self.superview) { 429 | //处理collectionView 内容视图自动下移的问题 430 | UIViewController * superVC = [WSLRollView viewControllerFromView:self]; 431 | superVC.automaticallyAdjustsScrollViewInsets = NO; 432 | [self reloadData]; 433 | } 434 | } 435 | 436 | /** 437 | 释放计时器 必须执行,防止内存暴涨 438 | */ 439 | - (void)invalidateTimer{ 440 | [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(play) object:nil]; 441 | if(_timer != nil){ 442 | [_timer invalidate]; 443 | _timer = nil; 444 | } 445 | } 446 | 447 | //刷新 448 | - (void)reloadData{ 449 | 450 | _addLeftCount = 0; 451 | _addRightCount = 0; 452 | [_dataSource removeAllObjects]; 453 | _dataSource = [NSMutableArray arrayWithArray:_sourceArray]; 454 | 455 | [self resetDataSourceForLoop]; 456 | [self.collectionView reloadData]; 457 | 458 | if (_sourceArray.count == 0) { 459 | return; 460 | } 461 | 462 | if (self.scrollStyle == WSLRollViewScrollStylePage) { 463 | [self.collectionView layoutIfNeeded]; 464 | __weak typeof(self) weakSelf = self; 465 | dispatch_async(dispatch_get_main_queue(),^{ 466 | if (weakSelf.startingPosition >= weakSelf.sourceArray.count || weakSelf.startingPosition < 0) { 467 | weakSelf.startingPosition = 0; 468 | } 469 | [weakSelf rollToIndex:weakSelf.startingPosition]; 470 | }); 471 | } 472 | [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(play) object:nil]; 473 | [self performSelector:@selector(play) withObject:nil afterDelay:0.6]; 474 | } 475 | 476 | /** 477 | 计时器任务 478 | */ 479 | - (void)timerEvent{ 480 | 481 | if(self.scrollDirection == UICollectionViewScrollDirectionHorizontal){ 482 | //如果不够一屏就停止滚动效果 483 | if (_collectionView.contentSize.width < self.frame.size.width) { 484 | [self pause]; 485 | return; 486 | } 487 | [self horizontalRollAnimation]; 488 | }else{ 489 | if (_collectionView.contentSize.height < self.frame.size.height) { 490 | [self pause]; 491 | return; 492 | } 493 | [self verticalRollAnimation]; 494 | } 495 | } 496 | 497 | /** 498 | 水平方向跑马灯 分页/渐进动画 499 | */ 500 | - (void)horizontalRollAnimation{ 501 | // 没有滚动新闻时退出 502 | if (_collectionView.contentSize.width == 0){ 503 | return; 504 | } 505 | [self resetContentOffset]; 506 | if (self.scrollStyle == WSLRollViewScrollStylePage){ 507 | //分页动画 508 | NSInteger currentMiddleIndex= [_collectionView indexPathForItemAtPoint:CGPointMake(_collectionView.contentOffset.x + self.frame.size.width/2, 0)].row; 509 | NSIndexPath * nextIndexPath = [NSIndexPath indexPathForRow:(currentMiddleIndex + 1) inSection:0]; 510 | [_collectionView scrollToItemAtIndexPath:nextIndexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:YES]; 511 | }else if (self.scrollStyle == WSLRollViewScrollStyleStep){ 512 | //渐进动画 513 | [_collectionView setContentOffset:CGPointMake(_collectionView.contentOffset.x + _speed * 1.0/60, _padding.top) animated:NO]; 514 | } 515 | } 516 | 517 | /** 518 | 垂直方向跑马灯 分页/渐进动画 519 | */ 520 | - (void)verticalRollAnimation{ 521 | // 没有滚动新闻时退出 522 | if (_collectionView.contentSize.height == 0){ 523 | return; 524 | } 525 | [self resetContentOffset]; 526 | if (self.scrollStyle == WSLRollViewScrollStylePage){ 527 | //分页动画 528 | NSInteger currentMiddleIndex= [_collectionView indexPathForItemAtPoint:CGPointMake(0, _collectionView.contentOffset.y + self.frame.size.height/2)].row; 529 | NSIndexPath * nextIndexPath = [NSIndexPath indexPathForRow:(currentMiddleIndex + 1) inSection:0]; 530 | [_collectionView scrollToItemAtIndexPath:nextIndexPath atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:YES]; 531 | }else if (self.scrollStyle == WSLRollViewScrollStyleStep){ 532 | //渐进动画 533 | [_collectionView setContentOffset:CGPointMake(_padding.left, _collectionView.contentOffset.y + _speed * 1.0/60) animated:NO]; 534 | } 535 | } 536 | 537 | /** 538 | 滑动到首尾连接处时需要复原至对应的位置 539 | */ 540 | - (void)resetContentOffset{ 541 | 542 | if (self.scrollStyle == WSLRollViewScrollStyleStep){ 543 | //只有当IndexPath位置上的cell可见时,才能用如下方法获取到对应的cell,否则为nil 544 | WSLRollViewCell * item = (WSLRollViewCell *)[_collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForRow:_dataSource.count - (self.addRightCount) inSection:0]]; 545 | //获取渐进轮播首尾相连的交汇点位置坐标 546 | _connectionPoint = [_collectionView convertRect:item.frame toView:_collectionView].origin; 547 | } 548 | 549 | if(self.scrollDirection == UICollectionViewScrollDirectionHorizontal){ 550 | //水平 551 | if (self.scrollStyle == WSLRollViewScrollStylePage) { 552 | NSInteger currentMiddleIndex = [_collectionView indexPathForItemAtPoint:CGPointMake(_collectionView.contentOffset.x + self.frame.size.width/2, 0)].row; 553 | if (currentMiddleIndex >= _sourceArray.count + _addLeftCount) { 554 | //右侧循环 555 | [_collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:currentMiddleIndex - _sourceArray.count inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO]; 556 | }else if (currentMiddleIndex < _addLeftCount) { 557 | //左侧循环 558 | [_collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow: _sourceArray.count + currentMiddleIndex inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO]; 559 | } 560 | 561 | }else if (self.scrollStyle == WSLRollViewScrollStyleStep){ 562 | if ((_collectionView.contentOffset.x >= _connectionPoint.x) && _connectionPoint.x != 0){ 563 | [_collectionView setContentOffset:CGPointMake(_padding.left,_padding.top) animated:NO]; 564 | } 565 | } 566 | 567 | }else{ 568 | //垂直 569 | if (self.scrollStyle == WSLRollViewScrollStylePage) { 570 | NSInteger currentMiddleIndex = [_collectionView indexPathForItemAtPoint:CGPointMake(0, _collectionView.contentOffset.y + self.frame.size.height/2)].row; 571 | if (currentMiddleIndex >= _sourceArray.count + _addLeftCount) { 572 | //右侧循环 573 | [_collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:currentMiddleIndex - _sourceArray.count inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:NO]; 574 | }else if (currentMiddleIndex < _addLeftCount) { 575 | //左侧循环 576 | [_collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow: _sourceArray.count + currentMiddleIndex inSection:0] atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:NO]; 577 | } 578 | 579 | }else if (self.scrollStyle == WSLRollViewScrollStyleStep){ 580 | if ((_collectionView.contentOffset.y >= _connectionPoint.y) && _connectionPoint.y != 0){ 581 | [_collectionView setContentOffset:CGPointMake(_padding.left,_padding.top) animated:NO]; 582 | } 583 | } 584 | } 585 | } 586 | 587 | #pragma mark - UIScrollViewDelegate 588 | 589 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView{ 590 | [self getCurrentIndex]; 591 | if (!_loopEnabled) { 592 | return; 593 | } 594 | if ((scrollView.contentOffset.x < 1 || scrollView.contentOffset.x > scrollView.contentSize.width - self.frame.size.width - 1) && _scrollDirection == UICollectionViewScrollDirectionHorizontal) { 595 | [self resetContentOffset]; 596 | }else if ((scrollView.contentOffset.y < 1 || scrollView.contentOffset.y > scrollView.contentSize.height - self.frame.size.height - 1) && _scrollDirection == UICollectionViewScrollDirectionVertical){ 597 | [self resetContentOffset]; 598 | } 599 | } 600 | 601 | - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{ 602 | if (_timer != nil) { 603 | [self pause]; 604 | } 605 | } 606 | 607 | - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{ 608 | 609 | if (decelerate == NO && _loopEnabled) { 610 | [self resetContentOffset]; 611 | } 612 | } 613 | 614 | //拖拽之后减速完成 615 | - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{ 616 | if (_loopEnabled) { 617 | [self resetContentOffset]; 618 | [self play]; 619 | } 620 | } 621 | 622 | //设置偏移量的动画结束之后 623 | - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView{ 624 | if (_loopEnabled) { 625 | [self resetContentOffset]; 626 | } 627 | } 628 | 629 | #pragma mark - UICollectionViewDelegateFlowLayout 630 | 631 | /** 632 | item的大小 633 | */ 634 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath{ 635 | if ([self.delegate respondsToSelector:@selector(rollView:sizeForItemAtIndex:)]) { 636 | return [self.delegate rollView:self sizeForItemAtIndex:[self indexOfSourceArray:indexPath.row]]; 637 | } 638 | return CGSizeMake(self.frame.size.width, self.frame.size.height); 639 | } 640 | 641 | /** 642 | 行间距 643 | */ 644 | - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section{ 645 | if ([self.delegate respondsToSelector:@selector(spaceOfItemInRollView:)]) { 646 | _spaceOfItem = [self.delegate spaceOfItemInRollView:self]; 647 | } 648 | return _spaceOfItem; 649 | } 650 | 651 | /** 652 | 列间距 653 | */ 654 | - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section{ 655 | if ([self.delegate respondsToSelector:@selector(spaceOfItemInRollView:)]) { 656 | _spaceOfItem = [self.delegate spaceOfItemInRollView:self]; 657 | } 658 | return _spaceOfItem; 659 | } 660 | 661 | /** 662 | 组间距 663 | */ 664 | - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section{ 665 | if ([self.delegate respondsToSelector:@selector(paddingOfRollView:)]){ 666 | _padding = [self.delegate paddingOfRollView:self]; 667 | } 668 | return _padding; 669 | } 670 | 671 | #pragma mark - UICollectionViewDelegate UICollectionViewDataSource 672 | 673 | //组个数 674 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{ 675 | return 1; 676 | } 677 | 678 | //组内成员个数 679 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{ 680 | return _dataSource.count; 681 | } 682 | 683 | // 返回每个cell 684 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{ 685 | _indexPath = indexPath; 686 | if ([self.delegate respondsToSelector:@selector(rollView:cellForItemAtIndex:)]) { 687 | return [self.delegate rollView:self cellForItemAtIndex:[self indexOfSourceArray:indexPath.row]]; 688 | }else{ 689 | //默认样式 690 | WSLRollViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"WSLItemID" forIndexPath:indexPath]; 691 | cell.backgroundColor = [UIColor whiteColor]; 692 | return cell; 693 | } 694 | } 695 | 696 | //点击事件 697 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath{ 698 | 699 | if ([self.delegate respondsToSelector:@selector(rollView:didSelectItemAtIndex:)]) { 700 | [self.delegate rollView:self didSelectItemAtIndex:[self indexOfSourceArray:indexPath.row]]; 701 | } 702 | } 703 | 704 | #pragma mark - Setter 705 | 706 | - (void)setScrollStyle:(WSLRollViewScrollStyle)scrollStyle{ 707 | _scrollStyle = scrollStyle; 708 | WSLRollViewFlowLayout * layout = (WSLRollViewFlowLayout *)_collectionView.collectionViewLayout; 709 | layout.scrollStyle = scrollStyle; 710 | } 711 | 712 | - (void)setScrollDirection:(UICollectionViewScrollDirection)scrollDirection{ 713 | _scrollDirection = scrollDirection; 714 | WSLRollViewFlowLayout * layout = (WSLRollViewFlowLayout *)_collectionView.collectionViewLayout; 715 | layout.scrollDirection = scrollDirection; 716 | } 717 | - (void)setScrollEnabled:(BOOL)scrollEnabled{ 718 | _scrollEnabled = scrollEnabled; 719 | self.collectionView.scrollEnabled = scrollEnabled; 720 | } 721 | 722 | #pragma mark - Getter 723 | 724 | - (UICollectionView *)collectionView{ 725 | if (_collectionView == nil) { 726 | WSLRollViewFlowLayout * layout = [[WSLRollViewFlowLayout alloc] init]; 727 | layout.scrollDirection = UICollectionViewScrollDirectionHorizontal; 728 | layout.scrollStyle = WSLRollViewScrollStylePage; 729 | 730 | _collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0,0, self.frame.size.width, self.frame.size.height) collectionViewLayout:layout]; 731 | if (@available(iOS 11.0, *)) { 732 | _collectionView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; 733 | } else { 734 | } 735 | _collectionView.backgroundColor = [UIColor clearColor]; 736 | _collectionView.delegate = self; 737 | _collectionView.dataSource = self; 738 | _collectionView.showsVerticalScrollIndicator = NO; 739 | _collectionView.showsHorizontalScrollIndicator = NO; 740 | _collectionView.decelerationRate = 0; 741 | _collectionView.scrollEnabled = self.scrollEnabled; 742 | } 743 | return _collectionView; 744 | } 745 | 746 | @end 747 | --------------------------------------------------------------------------------