├── 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 | 
6 |
7 | >功能描述:[WSL_RollView](https://github.com/wslcmk/WSL_RollView) 是基于UICollectionView实现的支持水平和垂直两个方向上的的分页和渐进循环轮播效果,可以设置时间间隔、渐进速率、是否循环、分页宽度和间隔,还支持高度自定义分页视图的控件。
8 |
9 | #### 一、实现方法
10 |
11 | ##### ①、 首先用UICollectionView和计时器实现一个基本的水平滚动效果,如下图,这个太简单就不在此详述。
12 |
13 | 
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 | 
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 |
--------------------------------------------------------------------------------