├── .gitignore ├── CyclePageView ├── SYACyclePageTransformLayout.h ├── SYACyclePageTransformLayout.m ├── SYACyclePageView.h └── SYACyclePageView.m ├── LICENSE ├── README.md ├── SYACyclePageView.podspec └── SYACyclePageViewDemo ├── SYACyclePageViewDemo.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata └── SYACyclePageViewDemo ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets └── AppIcon.appiconset │ └── Contents.json ├── Base.lproj └── LaunchScreen.storyboard ├── CyclePageView ├── SYACyclePageTransformLayout.h ├── SYACyclePageTransformLayout.m ├── SYACyclePageView.h └── SYACyclePageView.m ├── Info.plist ├── Main.storyboard ├── TYCyclePagerViewCell.h ├── TYCyclePagerViewCell.m ├── TYPageControl.h ├── TYPageControl.m ├── ViewController.h ├── ViewController.m └── main.m /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | # CocoaPods 32 | # 33 | # We recommend against adding the Pods directory to your .gitignore. However 34 | # you should judge for yourself, the pros and cons are mentioned at: 35 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 36 | # 37 | # Pods/ 38 | 39 | # Carthage 40 | # 41 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 42 | # Carthage/Checkouts 43 | 44 | Carthage/Build 45 | 46 | # fastlane 47 | # 48 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 49 | # screenshots whenever they are needed. 50 | # For more information about the recommended setup visit: 51 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 52 | 53 | fastlane/report.xml 54 | fastlane/Preview.html 55 | fastlane/screenshots 56 | fastlane/test_output 57 | 58 | # Code Injection 59 | # 60 | # After new code Injection tools there's a generated folder /iOSInjectionProject 61 | # https://github.com/johnno1962/injectionforxcode 62 | 63 | iOSInjectionProject/ 64 | -------------------------------------------------------------------------------- /CyclePageView/SYACyclePageTransformLayout.h: -------------------------------------------------------------------------------- 1 | // 2 | // SYACyclePageTransformLayout.h 3 | // SYACyclePageViewDemo 4 | // 5 | // Created by zq on 2017/12/13. 6 | // Copyright © 2017年 www.lejiakeji.com. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | typedef NS_ENUM(NSUInteger, SYACyclePageTransformLayoutType) { 14 | SYACyclePageTransformLayoutNormal, 15 | SYAyclePageTransformLayoutLinear, 16 | SYACyclePageTransformLayoutCoverflow, 17 | }; 18 | @class SYACyclePageTransformLayout; 19 | @protocol SYACyclePageTransformLayoutDelegate 20 | 21 | - (void)pageViewTransformLayout:(SYACyclePageTransformLayout *)pageViewTransformLayout initializeTransformAttributes:(UICollectionViewLayoutAttributes *)attributes; 22 | 23 | - (void)pageViewTransformLayout:(SYACyclePageTransformLayout *)pageViewTransformLayout applyTransformToAttributes:(UICollectionViewLayoutAttributes *)attributes; 24 | 25 | @end 26 | 27 | @interface SYACyclePageViewLayout : NSObject 28 | 29 | @property (nonatomic, assign) CGSize itemSize; 30 | 31 | @property (nonatomic, assign) CGFloat itemSpacing; 32 | 33 | @property (nonatomic, assign) UIEdgeInsets sectionInset; 34 | 35 | @property (nonatomic, assign) SYACyclePageTransformLayoutType layoutType; 36 | 37 | @property (nonatomic, assign) CGFloat minimumScale; 38 | 39 | @property (nonatomic, assign) CGFloat minimumAlpha; 40 | 41 | @property (nonatomic, assign) CGFloat maximumAngle; 42 | 43 | @property (nonatomic, assign) BOOL isInfiniteLoop; 44 | 45 | @property (nonatomic, assign) CGFloat rateOfChange; 46 | 47 | @property (nonatomic, assign) BOOL adjustSpacingWhenScroling; 48 | 49 | @property (nonatomic, assign) BOOL itemVerticalCenter; 50 | 51 | @property (nonatomic, assign) BOOL itemHorizontalCenter; 52 | 53 | // sectionInset 54 | @property (nonatomic, assign, readonly) UIEdgeInsets onlyOneSectionInset; 55 | @property (nonatomic, assign, readonly) UIEdgeInsets firstSectionInset; 56 | @property (nonatomic, assign, readonly) UIEdgeInsets lastSectionInset; 57 | @property (nonatomic, assign, readonly) UIEdgeInsets middleSectionInset; 58 | 59 | @end 60 | 61 | @interface SYACyclePageTransformLayout : UICollectionViewFlowLayout 62 | 63 | @property (nonatomic, strong) SYACyclePageViewLayout *layout; 64 | 65 | @property (nonatomic, weak) id delegate; 66 | 67 | @end 68 | 69 | NS_ASSUME_NONNULL_END 70 | 71 | -------------------------------------------------------------------------------- /CyclePageView/SYACyclePageTransformLayout.m: -------------------------------------------------------------------------------- 1 | // 2 | // SYACyclePageTransformLayout.m 3 | // SYACyclePageViewDemo 4 | // 5 | // Created by zq on 2017/12/13. 6 | // Copyright © 2017年 www.lejiakeji.com. All rights reserved. 7 | // 8 | 9 | #import "SYACyclePageTransformLayout.h" 10 | typedef NS_ENUM(NSUInteger, SYATransformLayoutItemDirection) { 11 | SYATransformLayoutItemLeft, 12 | SYATransformLayoutItemCenter, 13 | SYATransformLayoutItemRight, 14 | }; 15 | @interface SYACyclePageTransformLayout(){ 16 | struct { 17 | unsigned int applyTransformToAttributes :1; 18 | unsigned int initializeTransformAttributes :1; 19 | }_delegateFlags; 20 | } 21 | @property (nonatomic, assign) BOOL applyTransformToAttributesDelegate; 22 | 23 | @end 24 | 25 | @interface SYACyclePageViewLayout() 26 | 27 | @property (nonatomic, weak) UIView *pageView; 28 | 29 | @end 30 | 31 | @implementation SYACyclePageTransformLayout 32 | - (instancetype)init { 33 | if (self = [super init]) { 34 | self.scrollDirection = UICollectionViewScrollDirectionHorizontal; 35 | } 36 | return self; 37 | } 38 | 39 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 40 | if (self = [super initWithCoder:aDecoder]) { 41 | self.scrollDirection = UICollectionViewScrollDirectionHorizontal; 42 | } 43 | return self; 44 | } 45 | 46 | - (void)setDelegate:(id)delegate { 47 | _delegate = delegate; 48 | _delegateFlags.initializeTransformAttributes = [delegate respondsToSelector:@selector(pageViewTransformLayout:initializeTransformAttributes:)]; 49 | _delegateFlags.applyTransformToAttributes = [delegate respondsToSelector:@selector(pageViewTransformLayout:applyTransformToAttributes:)]; 50 | } 51 | 52 | - (void)setLayout:(SYACyclePageViewLayout *)layout { 53 | _layout = layout; 54 | _layout.pageView = self.collectionView; 55 | self.itemSize = _layout.itemSize; 56 | self.minimumInteritemSpacing = _layout.itemSpacing; 57 | self.minimumLineSpacing = _layout.itemSpacing; 58 | } 59 | 60 | - (CGSize)itemSize { 61 | if (!_layout) { 62 | return [super itemSize]; 63 | } 64 | return _layout.itemSize; 65 | } 66 | 67 | - (CGFloat)minimumLineSpacing { 68 | if (!_layout) { 69 | return [super minimumLineSpacing]; 70 | } 71 | return _layout.itemSpacing; 72 | } 73 | 74 | - (CGFloat)minimumInteritemSpacing { 75 | if (!_layout) { 76 | return [super minimumInteritemSpacing]; 77 | } 78 | return _layout.itemSpacing; 79 | } 80 | 81 | - (SYATransformLayoutItemDirection)directionWithCenterX:(CGFloat)centerX { 82 | SYATransformLayoutItemDirection direction= SYATransformLayoutItemRight; 83 | CGFloat contentCenterX = self.collectionView.contentOffset.x + CGRectGetWidth(self.collectionView.frame)/2; 84 | if (ABS(centerX - contentCenterX) < 0.5) { 85 | direction = SYATransformLayoutItemCenter; 86 | }else if (centerX - contentCenterX < 0) { 87 | direction = SYATransformLayoutItemLeft; 88 | } 89 | return direction; 90 | } 91 | 92 | #pragma mark - layout 93 | 94 | -(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds 95 | { 96 | return _layout.layoutType == SYACyclePageTransformLayoutNormal ? [super shouldInvalidateLayoutForBoundsChange:newBounds] : YES; 97 | } 98 | 99 | - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { 100 | if (_delegateFlags.applyTransformToAttributes || _layout.layoutType != SYACyclePageTransformLayoutNormal) { 101 | NSArray *attributesArray = [[NSArray alloc] initWithArray:[super layoutAttributesForElementsInRect:rect] copyItems:YES]; 102 | CGRect visibleRect = {self.collectionView.contentOffset,self.collectionView.bounds.size}; 103 | for (UICollectionViewLayoutAttributes *attributes in attributesArray) { 104 | if (!CGRectIntersectsRect(visibleRect, attributes.frame)) { 105 | continue; 106 | } 107 | if (_delegateFlags.applyTransformToAttributes) { 108 | [_delegate pageViewTransformLayout:self applyTransformToAttributes:attributes]; 109 | }else { 110 | [self applyTransformToAttributes:attributes layoutType:_layout.layoutType]; 111 | } 112 | } 113 | return attributesArray; 114 | } 115 | return [super layoutAttributesForElementsInRect:rect]; 116 | } 117 | 118 | - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { 119 | UICollectionViewLayoutAttributes *attributes = [super layoutAttributesForItemAtIndexPath:indexPath]; 120 | if (_delegateFlags.initializeTransformAttributes) { 121 | [_delegate pageViewTransformLayout:self initializeTransformAttributes:attributes]; 122 | }else if(_layout.layoutType != SYACyclePageTransformLayoutNormal){ 123 | [self initializeTransformAttributes:attributes layoutType:_layout.layoutType]; 124 | } 125 | return attributes; 126 | } 127 | 128 | #pragma mark - transform 129 | 130 | - (void)initializeTransformAttributes:(UICollectionViewLayoutAttributes *)attributes layoutType:(SYACyclePageTransformLayoutType)layoutType { 131 | switch (layoutType) { 132 | case SYAyclePageTransformLayoutLinear: 133 | [self applyLinearTransformToAttributes:attributes scale:_layout.minimumScale alpha:_layout.minimumAlpha]; 134 | break; 135 | case SYACyclePageTransformLayoutCoverflow: 136 | { 137 | [self applyCoverflowTransformToAttributes:attributes angle:_layout.maximumAngle alpha:_layout.minimumAlpha]; 138 | break; 139 | } 140 | default: 141 | break; 142 | } 143 | } 144 | 145 | - (void)applyTransformToAttributes:(UICollectionViewLayoutAttributes *)attributes layoutType:(SYACyclePageTransformLayoutType)layoutType { 146 | switch (layoutType) { 147 | case SYAyclePageTransformLayoutLinear: 148 | [self applyLinearTransformToAttributes:attributes]; 149 | break; 150 | case SYACyclePageTransformLayoutCoverflow: 151 | [self applyCoverflowTransformToAttributes:attributes]; 152 | break; 153 | default: 154 | break; 155 | } 156 | } 157 | 158 | #pragma mark - LinearTransform 159 | 160 | - (void)applyLinearTransformToAttributes:(UICollectionViewLayoutAttributes *)attributes { 161 | CGFloat collectionViewWidth = self.collectionView.frame.size.width; 162 | if (collectionViewWidth <= 0) { 163 | return; 164 | } 165 | CGFloat centetX = self.collectionView.contentOffset.x + collectionViewWidth/2; 166 | CGFloat delta = ABS(attributes.center.x - centetX); 167 | CGFloat scale = MAX(1 - delta/collectionViewWidth*_layout.rateOfChange, _layout.minimumScale); 168 | CGFloat alpha = MAX(1 - delta/collectionViewWidth, _layout.minimumAlpha); 169 | [self applyLinearTransformToAttributes:attributes scale:scale alpha:alpha]; 170 | } 171 | 172 | - (void)applyLinearTransformToAttributes:(UICollectionViewLayoutAttributes *)attributes scale:(CGFloat)scale alpha:(CGFloat)alpha { 173 | CGAffineTransform transform = CGAffineTransformMakeScale(scale, scale); 174 | if (_layout.adjustSpacingWhenScroling) { 175 | SYATransformLayoutItemDirection direction = [self directionWithCenterX:attributes.center.x]; 176 | CGFloat translate = 0; 177 | switch (direction) { 178 | case SYATransformLayoutItemLeft: 179 | translate = 1.15 * attributes.size.width*(1-scale)/2; 180 | break; 181 | case SYATransformLayoutItemRight: 182 | translate = -1.15 * attributes.size.width*(1-scale)/2; 183 | break; 184 | default: 185 | // center 186 | scale = 1.0; 187 | alpha = 1.0; 188 | break; 189 | } 190 | transform = CGAffineTransformTranslate(transform,translate, 0); 191 | } 192 | attributes.transform = transform; 193 | attributes.alpha = alpha; 194 | } 195 | 196 | #pragma mark - CoverflowTransform 197 | 198 | - (void)applyCoverflowTransformToAttributes:(UICollectionViewLayoutAttributes *)attributes{ 199 | CGFloat collectionViewWidth = self.collectionView.frame.size.width; 200 | if (collectionViewWidth <= 0) { 201 | return; 202 | } 203 | CGFloat centetX = self.collectionView.contentOffset.x + collectionViewWidth/2; 204 | CGFloat delta = ABS(attributes.center.x - centetX); 205 | CGFloat angle = MIN(delta/collectionViewWidth*(1-_layout.rateOfChange), _layout.maximumAngle); 206 | CGFloat alpha = MAX(1 - delta/collectionViewWidth, _layout.minimumAlpha); 207 | [self applyCoverflowTransformToAttributes:attributes angle:angle alpha:alpha]; 208 | } 209 | 210 | - (void)applyCoverflowTransformToAttributes:(UICollectionViewLayoutAttributes *)attributes angle:(CGFloat)angle alpha:(CGFloat)alpha { 211 | SYATransformLayoutItemDirection direction = [self directionWithCenterX:attributes.center.x]; 212 | CATransform3D transform3D = CATransform3DIdentity; 213 | transform3D.m34 = -0.002; 214 | CGFloat translate = 0; 215 | switch (direction) { 216 | case SYATransformLayoutItemLeft: 217 | translate = (1-cos(angle*1.2*M_PI))*attributes.size.width; 218 | break; 219 | case SYATransformLayoutItemRight: 220 | translate = -(1-cos(angle*1.2*M_PI))*attributes.size.width; 221 | angle = -angle; 222 | break; 223 | default: 224 | // center 225 | angle = 0; 226 | alpha = 1; 227 | break; 228 | } 229 | 230 | transform3D = CATransform3DRotate(transform3D, M_PI*angle, 0, 1, 0); 231 | if (_layout.adjustSpacingWhenScroling) { 232 | transform3D = CATransform3DTranslate(transform3D, translate, 0, 0); 233 | } 234 | attributes.transform3D = transform3D; 235 | attributes.alpha = alpha; 236 | 237 | } 238 | @end 239 | 240 | 241 | @implementation SYACyclePageViewLayout 242 | 243 | - (instancetype)init { 244 | if (self = [super init]) { 245 | _itemVerticalCenter = YES; 246 | _minimumScale = 0.8; 247 | _minimumAlpha = 1.0; 248 | _maximumAngle = 0.2; 249 | _rateOfChange = 0.4; 250 | _adjustSpacingWhenScroling = YES; 251 | } 252 | return self; 253 | } 254 | 255 | #pragma mark - getter 256 | 257 | - (UIEdgeInsets)onlyOneSectionInset { 258 | CGFloat leftSpace = _pageView && !_isInfiniteLoop && _itemHorizontalCenter ? (CGRectGetWidth(_pageView.frame) - _itemSize.width)/2 : _sectionInset.left; 259 | CGFloat rightSpace = _pageView && !_isInfiniteLoop && _itemHorizontalCenter ? (CGRectGetWidth(_pageView.frame) - _itemSize.width)/2 : _sectionInset.right; 260 | if (_itemVerticalCenter) { 261 | CGFloat verticalSpace = (CGRectGetHeight(_pageView.frame) - _itemSize.height)/2; 262 | return UIEdgeInsetsMake(verticalSpace, leftSpace, verticalSpace, rightSpace); 263 | } 264 | return UIEdgeInsetsMake(_sectionInset.top, leftSpace, _sectionInset.bottom, rightSpace); 265 | } 266 | 267 | - (UIEdgeInsets)firstSectionInset { 268 | if (_itemVerticalCenter) { 269 | CGFloat verticalSpace = (CGRectGetHeight(_pageView.frame) - _itemSize.height)/2; 270 | return UIEdgeInsetsMake(verticalSpace, _sectionInset.left, verticalSpace, _itemSpacing); 271 | } 272 | return UIEdgeInsetsMake(_sectionInset.top, _sectionInset.left, _sectionInset.bottom, _itemSpacing); 273 | } 274 | 275 | - (UIEdgeInsets)lastSectionInset { 276 | if (_itemVerticalCenter) { 277 | CGFloat verticalSpace = (CGRectGetHeight(_pageView.frame) - _itemSize.height)/2; 278 | return UIEdgeInsetsMake(verticalSpace, 0, verticalSpace, _sectionInset.right); 279 | } 280 | return UIEdgeInsetsMake(_sectionInset.top, 0, _sectionInset.bottom, _sectionInset.right); 281 | } 282 | 283 | - (UIEdgeInsets)middleSectionInset { 284 | if (_itemVerticalCenter) { 285 | CGFloat verticalSpace = (CGRectGetHeight(_pageView.frame) - _itemSize.height)/2; 286 | return UIEdgeInsetsMake(verticalSpace, 0, verticalSpace, _itemSpacing); 287 | } 288 | return _sectionInset; 289 | } 290 | @end 291 | -------------------------------------------------------------------------------- /CyclePageView/SYACyclePageView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SYACyclePageView.h 3 | // SYACyclePageViewDemo 4 | // 5 | // Created by zq on 2017/12/13. 6 | // Copyright © 2017年 www.lejiakeji.com. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "SYACyclePageTransformLayout.h" 11 | NS_ASSUME_NONNULL_BEGIN 12 | typedef NS_ENUM(NSUInteger, SYAPageScrollDirection) { 13 | SYAPageScrollDirectionLeft, 14 | SYAPageScrollDirectionRight, 15 | }; 16 | @class SYACyclePageView; 17 | @protocol SYACyclePagerViewDataSource 18 | 19 | - (NSInteger)numberOfItemsInPagerView:(SYACyclePageView *)pageView; 20 | 21 | - (__kindof UICollectionViewCell *)pagerView:(SYACyclePageView *)pagerView cellForItemAtIndex:(NSInteger)index; 22 | 23 | - (SYACyclePageViewLayout *)layoutForPagerView:(SYACyclePageView *)pageView; 24 | 25 | @end 26 | 27 | @protocol SYACyclePageViewDelegate 28 | 29 | @optional 30 | 31 | - (void)pagerView:(SYACyclePageView *)pageView didScrollFromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex; 32 | 33 | - (void)pagerView:(SYACyclePageView *)pageView didSelectedItemCell:(__kindof UICollectionViewCell *)cell atIndex:(NSInteger)index; 34 | 35 | - (void)pagerView:(SYACyclePageView *)pageView initializeTransformAttributes:(UICollectionViewLayoutAttributes *)attributes; 36 | 37 | - (void)pagerView:(SYACyclePageView *)pageView applyTransformToAttributes:(UICollectionViewLayoutAttributes *)attributes; 38 | 39 | 40 | // scrollViewDelegate 41 | 42 | - (void)pagerViewDidScroll:(SYACyclePageView *)pageView; 43 | 44 | - (void)pagerViewWillBeginDragging:(SYACyclePageView *)pageView; 45 | 46 | - (void)pagerViewDidEndDragging:(SYACyclePageView *)pageView willDecelerate:(BOOL)decelerate; 47 | 48 | - (void)pagerViewWillBeginDecelerating:(SYACyclePageView *)pageView; 49 | 50 | - (void)pagerViewDidEndDecelerating:(SYACyclePageView *)pageView; 51 | 52 | - (void)pagerViewWillBeginScrollingAnimation:(SYACyclePageView *)pageView; 53 | 54 | - (void)pagerViewDidEndScrollingAnimation:(SYACyclePageView *)pageView; 55 | 56 | @end 57 | @interface SYACyclePageView : UIView 58 | 59 | @property (nonatomic, strong, nullable) UIView *backgroundView; 60 | 61 | @property (nonatomic, weak, nullable) id dataSource; 62 | 63 | @property (nonatomic, weak, nullable) id delegate; 64 | 65 | @property (nonatomic, weak, readonly) UICollectionView *collectionView; 66 | 67 | @property (nonatomic, strong, readonly) SYACyclePageViewLayout *layout; 68 | 69 | @property (nonatomic, assign) BOOL isInfiniteLoop; 70 | 71 | @property (nonatomic, assign) CGFloat autoScrollInterval; 72 | 73 | @property (nonatomic, assign, readonly) NSInteger curIndex; 74 | 75 | @property (nonatomic, assign, readonly) CGPoint contentOffset; 76 | 77 | @property (nonatomic, assign, readonly) BOOL tracking; 78 | 79 | @property (nonatomic, assign, readonly) BOOL dragging; 80 | 81 | @property (nonatomic, assign, readonly) BOOL decelerating; 82 | 83 | - (void)reloadData; 84 | 85 | - (void)updateData; 86 | 87 | - (void)setNeedUpdateLayout; 88 | 89 | - (void)setNeedClearLayout; 90 | 91 | - (__kindof UICollectionViewCell * _Nullable)curIndexCell; 92 | 93 | - (NSArray<__kindof UICollectionViewCell *> *_Nullable)visibleCells; 94 | 95 | - (NSArray *)visibleIndexs; 96 | 97 | - (void)scrollToItemAtIndex:(NSInteger)index animate:(BOOL)animate; 98 | 99 | - (void)scrollToNearlyIndexAtDirection:(SYAPageScrollDirection)direction animate:(BOOL)animate; 100 | 101 | - (void)registerClass:(Class)Class forCellWithReuseIdentifier:(NSString *)identifier; 102 | 103 | - (void)registerNib:(UINib *)nib forCellWithReuseIdentifier:(NSString *)identifier; 104 | 105 | - (__kindof UICollectionViewCell *)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndex:(NSInteger)index; 106 | @end 107 | NS_ASSUME_NONNULL_END 108 | -------------------------------------------------------------------------------- /CyclePageView/SYACyclePageView.m: -------------------------------------------------------------------------------- 1 | // 2 | // SYACyclePageView.m 3 | // SYACyclePageViewDemo 4 | // 5 | // Created by zq on 2017/12/13. 6 | // Copyright © 2017年 www.lejiakeji.com. All rights reserved. 7 | // 8 | 9 | #import "SYACyclePageView.h" 10 | typedef struct { 11 | NSInteger index; 12 | NSInteger section; 13 | }SYAIndexSection; 14 | 15 | NS_INLINE BOOL SYAEqualIndexSection(SYAIndexSection indexSection1,SYAIndexSection indexSection2) { 16 | return indexSection1.index == indexSection2.index && indexSection1.section == indexSection2.section; 17 | } 18 | 19 | NS_INLINE SYAIndexSection SYAMakeIndexSection(NSInteger index, NSInteger section) { 20 | SYAIndexSection indexSection; 21 | indexSection.index = index; 22 | indexSection.section = section; 23 | return indexSection; 24 | } 25 | 26 | @interface SYACyclePageView () { 27 | struct { 28 | unsigned int pagerViewDidScroll :1; 29 | unsigned int didScrollFromIndexToNewIndex :1; 30 | unsigned int initializeTransformAttributes :1; 31 | unsigned int applyTransformToAttributes :1; 32 | }_delegateFlags; 33 | struct { 34 | unsigned int cellForItemAtIndex :1; 35 | unsigned int layoutForPagerView :1; 36 | }_dataSourceFlags; 37 | } 38 | 39 | @property (nonatomic, weak) UICollectionView *collectionView; 40 | @property (nonatomic, strong) SYACyclePageViewLayout *layout; 41 | @property (nonatomic, strong) NSTimer *timer; 42 | 43 | @property (nonatomic, assign) NSInteger numberOfItems; 44 | 45 | @property (nonatomic, assign) SYAIndexSection indexSection; // current index 46 | @property (nonatomic, assign) NSInteger dequeueSection; 47 | @property (nonatomic, assign) SYAIndexSection beginDragIndexSection; 48 | 49 | @property (nonatomic, assign) BOOL needClearLayout; 50 | @property (nonatomic, assign) BOOL didReloadData; 51 | @property (nonatomic, assign) BOOL didLayout; 52 | @end 53 | #define kPageViewMaxSectionCount 200 54 | #define kPageViewMinSectionCount 18 55 | @implementation SYACyclePageView 56 | 57 | - (instancetype)initWithFrame:(CGRect)frame { 58 | if (self = [super initWithFrame:frame]) { 59 | [self configureProperty]; 60 | 61 | [self addCollectionView]; 62 | } 63 | return self; 64 | } 65 | 66 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 67 | if (self = [super initWithCoder:aDecoder]) { 68 | [self configureProperty]; 69 | 70 | [self addCollectionView]; 71 | } 72 | return self; 73 | } 74 | 75 | - (void)configureProperty { 76 | _didReloadData = NO; 77 | _didLayout = NO; 78 | _autoScrollInterval = 0; 79 | _isInfiniteLoop = YES; 80 | _beginDragIndexSection.index = 0; 81 | _beginDragIndexSection.section = 0; 82 | _indexSection.index = -1; 83 | _indexSection.section = -1; 84 | } 85 | 86 | - (void)addCollectionView { 87 | SYACyclePageTransformLayout *layout = [[SYACyclePageTransformLayout alloc]init]; 88 | UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; 89 | layout.delegate = _delegateFlags.applyTransformToAttributes ? self : nil;; 90 | collectionView.backgroundColor = [UIColor clearColor]; 91 | collectionView.dataSource = self; 92 | collectionView.delegate = self; 93 | collectionView.pagingEnabled = NO; 94 | collectionView.decelerationRate = 1-0.0076; 95 | if ([collectionView respondsToSelector:@selector(setPrefetchingEnabled:)]) { 96 | collectionView.prefetchingEnabled = NO; 97 | } 98 | collectionView.showsHorizontalScrollIndicator = NO; 99 | collectionView.showsVerticalScrollIndicator = NO; 100 | [self addSubview:collectionView]; 101 | _collectionView = collectionView; 102 | } 103 | 104 | - (void)willMoveToSuperview:(UIView *)newSuperview { 105 | if (!newSuperview) { 106 | [self removeTimer]; 107 | }else { 108 | [self removeTimer]; 109 | if (_autoScrollInterval > 0) { 110 | [self addTimer]; 111 | } 112 | } 113 | } 114 | 115 | #pragma mark - timer 116 | 117 | - (void)addTimer { 118 | if (_timer) { 119 | return; 120 | } 121 | _timer = [NSTimer timerWithTimeInterval:_autoScrollInterval target:self selector:@selector(timerFired:) userInfo:nil repeats:YES]; 122 | [[NSRunLoop mainRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes]; 123 | } 124 | 125 | - (void)removeTimer { 126 | if (!_timer) { 127 | return; 128 | } 129 | [_timer invalidate]; 130 | _timer = nil; 131 | } 132 | 133 | - (void)timerFired:(NSTimer *)timer { 134 | if (!self.superview || !self.window || _numberOfItems == 0 || self.tracking) { 135 | return; 136 | } 137 | 138 | [self scrollToNearlyIndexAtDirection:SYAPageScrollDirectionRight animate:YES]; 139 | } 140 | 141 | #pragma mark - getter 142 | 143 | - (SYACyclePageViewLayout *)layout { 144 | if (!_layout) { 145 | if (_dataSourceFlags.layoutForPagerView) { 146 | _layout = [_dataSource layoutForPagerView:self]; 147 | _layout.isInfiniteLoop = _isInfiniteLoop; 148 | } 149 | if (_layout.itemSize.width <= 0 || _layout.itemSize.height <= 0) { 150 | _layout = nil; 151 | } 152 | } 153 | return _layout; 154 | } 155 | 156 | - (NSInteger)curIndex { 157 | return _indexSection.index; 158 | } 159 | 160 | - (CGPoint)contentOffset { 161 | return _collectionView.contentOffset; 162 | } 163 | 164 | - (BOOL)tracking { 165 | return _collectionView.tracking; 166 | } 167 | 168 | - (BOOL)dragging { 169 | return _collectionView.dragging; 170 | } 171 | 172 | - (BOOL)decelerating { 173 | return _collectionView.decelerating; 174 | } 175 | 176 | - (UIView *)backgroundView { 177 | return _collectionView.backgroundView; 178 | } 179 | 180 | - (__kindof UICollectionViewCell *)curIndexCell { 181 | return [_collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:_indexSection.index inSection:_indexSection.section]]; 182 | } 183 | 184 | - (NSArray<__kindof UICollectionViewCell *> *)visibleCells { 185 | return _collectionView.visibleCells; 186 | } 187 | 188 | - (NSArray *)visibleIndexs { 189 | NSMutableArray *indexs = [NSMutableArray array]; 190 | for (NSIndexPath *indexPath in _collectionView.indexPathsForVisibleItems) { 191 | [indexs addObject:@(indexPath.item)]; 192 | } 193 | return [indexs copy]; 194 | } 195 | 196 | #pragma mark - setter 197 | 198 | - (void)setBackgroundView:(UIView *)backgroundView { 199 | [_collectionView setBackgroundView:backgroundView]; 200 | } 201 | 202 | - (void)setAutoScrollInterval:(CGFloat)autoScrollInterval { 203 | _autoScrollInterval = autoScrollInterval; 204 | [self removeTimer]; 205 | if (autoScrollInterval > 0 && self.superview) { 206 | [self addTimer]; 207 | } 208 | } 209 | 210 | - (void)setDelegate:(id)delegate { 211 | _delegate = delegate; 212 | _delegateFlags.pagerViewDidScroll = [delegate respondsToSelector:@selector(pagerViewDidScroll:)]; 213 | _delegateFlags.didScrollFromIndexToNewIndex = [delegate respondsToSelector:@selector(pagerView:didScrollFromIndex:toIndex:)]; 214 | _delegateFlags.initializeTransformAttributes = [delegate respondsToSelector:@selector(pagerView:initializeTransformAttributes:)]; 215 | _delegateFlags.applyTransformToAttributes = [delegate respondsToSelector:@selector(pagerView:applyTransformToAttributes:)]; 216 | if (self.collectionView && self.collectionView.collectionViewLayout) { 217 | ((SYACyclePageTransformLayout *)self.collectionView.collectionViewLayout).delegate = _delegateFlags.applyTransformToAttributes ? self : nil; 218 | } 219 | } 220 | 221 | - (void)setDataSource:(id)dataSource { 222 | _dataSource = dataSource; 223 | _dataSourceFlags.cellForItemAtIndex = [dataSource respondsToSelector:@selector(pagerView:cellForItemAtIndex:)]; 224 | _dataSourceFlags.layoutForPagerView = [dataSource respondsToSelector:@selector(layoutForPagerView:)]; 225 | } 226 | 227 | #pragma mark - public 228 | 229 | - (void)reloadData { 230 | _didReloadData = YES; 231 | [self setNeedClearLayout]; 232 | [self clearLayout]; 233 | [self updateData]; 234 | } 235 | 236 | // not clear layout 237 | - (void)updateData { 238 | [self updateLayout]; 239 | _numberOfItems = [_dataSource numberOfItemsInPagerView:self]; 240 | [_collectionView reloadData]; 241 | if (!_didLayout && !CGRectIsEmpty(self.frame) && _indexSection.index < 0) { 242 | _didLayout = YES; 243 | } 244 | [self resetPagerViewAtIndex:_indexSection.index < 0 && !CGRectIsEmpty(self.frame) ? 0 :_indexSection.index]; 245 | } 246 | 247 | - (void)scrollToNearlyIndexAtDirection:(SYAPageScrollDirection)direction animate:(BOOL)animate { 248 | SYAIndexSection indexSection = [self nearlyIndexPathAtDirection:direction]; 249 | [self scrollToItemAtIndexSection:indexSection animate:animate]; 250 | } 251 | 252 | - (void)scrollToItemAtIndex:(NSInteger)index animate:(BOOL)animate { 253 | if (!_isInfiniteLoop) { 254 | [self scrollToItemAtIndexSection:SYAMakeIndexSection(index, 0) animate:animate]; 255 | return; 256 | } 257 | 258 | [self scrollToItemAtIndexSection:SYAMakeIndexSection(index, index >= self.curIndex ? _indexSection.section : _indexSection.section+1) animate:YES]; 259 | } 260 | 261 | - (void)scrollToItemAtIndexSection:(SYAIndexSection)indexSection animate:(BOOL)animate { 262 | if (_numberOfItems <= 0 || ![self isValidIndexSection:indexSection]) { 263 | return; 264 | } 265 | 266 | if (animate && [_delegate respondsToSelector:@selector(pagerViewWillBeginScrollingAnimation:)]) { 267 | [_delegate pagerViewWillBeginScrollingAnimation:self]; 268 | } 269 | CGFloat offset = [self caculateOffsetXAtIndexSection:indexSection]; 270 | [_collectionView setContentOffset:CGPointMake(offset, _collectionView.contentOffset.y) animated:animate]; 271 | } 272 | 273 | - (void)registerClass:(Class)Class forCellWithReuseIdentifier:(NSString *)identifier { 274 | [_collectionView registerClass:Class forCellWithReuseIdentifier:identifier]; 275 | } 276 | 277 | - (void)registerNib:(UINib *)nib forCellWithReuseIdentifier:(NSString *)identifier { 278 | [_collectionView registerNib:nib forCellWithReuseIdentifier:identifier]; 279 | } 280 | 281 | - (__kindof UICollectionViewCell *)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndex:(NSInteger)index { 282 | UICollectionViewCell *cell = [_collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:[NSIndexPath indexPathForItem:index inSection:_dequeueSection]]; 283 | return cell; 284 | } 285 | 286 | #pragma mark - configure layout 287 | 288 | - (void)updateLayout { 289 | if (!self.layout) { 290 | return; 291 | } 292 | self.layout.isInfiniteLoop = _isInfiniteLoop; 293 | ((SYACyclePageTransformLayout *)_collectionView.collectionViewLayout).layout = self.layout; 294 | } 295 | 296 | - (void)clearLayout { 297 | if (_needClearLayout) { 298 | _layout = nil; 299 | _needClearLayout = NO; 300 | } 301 | } 302 | 303 | - (void)setNeedClearLayout { 304 | _needClearLayout = YES; 305 | } 306 | 307 | - (void)setNeedUpdateLayout { 308 | if (!self.layout) { 309 | return; 310 | } 311 | [self clearLayout]; 312 | [self updateLayout]; 313 | [_collectionView.collectionViewLayout invalidateLayout]; 314 | [self resetPagerViewAtIndex:_indexSection.index < 0 ? 0 :_indexSection.index]; 315 | } 316 | 317 | #pragma mark - pager index 318 | 319 | - (BOOL)isValidIndexSection:(SYAIndexSection)indexSection { 320 | return indexSection.index >= 0 && indexSection.index < _numberOfItems && indexSection.section >= 0 && indexSection.section < kPageViewMaxSectionCount; 321 | } 322 | 323 | - (SYAIndexSection)nearlyIndexPathAtDirection:(SYAPageScrollDirection)direction{ 324 | return [self nearlyIndexPathForIndexSection:_indexSection direction:direction]; 325 | } 326 | 327 | - (SYAIndexSection)nearlyIndexPathForIndexSection:(SYAIndexSection)indexSection direction:(SYAPageScrollDirection)direction { 328 | if (indexSection.index < 0 || indexSection.index >= _numberOfItems) { 329 | return indexSection; 330 | } 331 | 332 | if (!_isInfiniteLoop) { 333 | if (direction == SYAPageScrollDirectionRight && indexSection.index == _numberOfItems - 1) { 334 | return _autoScrollInterval > 0 ? SYAMakeIndexSection(0, 0) : indexSection; 335 | } else if (direction == SYAPageScrollDirectionRight) { 336 | return SYAMakeIndexSection(indexSection.index+1, 0); 337 | } 338 | 339 | if (indexSection.index == 0) { 340 | return _autoScrollInterval > 0 ? SYAMakeIndexSection(_numberOfItems - 1, 0) : indexSection; 341 | } 342 | return SYAMakeIndexSection(indexSection.index-1, 0); 343 | } 344 | 345 | if (direction == SYAPageScrollDirectionRight) { 346 | if (indexSection.index < _numberOfItems-1) { 347 | return SYAMakeIndexSection(indexSection.index+1, indexSection.section); 348 | } 349 | if (indexSection.section >= kPageViewMaxSectionCount-1) { 350 | return SYAMakeIndexSection(indexSection.index, kPageViewMaxSectionCount-1); 351 | } 352 | return SYAMakeIndexSection(0, indexSection.section+1); 353 | } 354 | 355 | if (indexSection.index > 0) { 356 | return SYAMakeIndexSection(indexSection.index-1, indexSection.section); 357 | } 358 | if (indexSection.section <= 0) { 359 | return SYAMakeIndexSection(indexSection.index, 0); 360 | } 361 | return SYAMakeIndexSection(_numberOfItems-1, indexSection.section-1); 362 | } 363 | 364 | - (SYAIndexSection)caculateIndexSectionWithOffsetX:(CGFloat)offsetX { 365 | if (_numberOfItems <= 0) { 366 | return SYAMakeIndexSection(0, 0); 367 | } 368 | UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)_collectionView.collectionViewLayout; 369 | CGFloat leftEdge = _isInfiniteLoop ? _layout.sectionInset.left : _layout.onlyOneSectionInset.left; 370 | CGFloat width = CGRectGetWidth(_collectionView.frame); 371 | CGFloat middleOffset = offsetX + width/2; 372 | CGFloat itemWidth = layout.itemSize.width + layout.minimumInteritemSpacing; 373 | NSInteger curIndex = 0; 374 | NSInteger curSection = 0; 375 | if (middleOffset - leftEdge >= 0) { 376 | NSInteger itemIndex = (middleOffset - leftEdge+layout.minimumInteritemSpacing/2)/itemWidth; 377 | if (itemIndex < 0) { 378 | itemIndex = 0; 379 | }else if (itemIndex >= _numberOfItems*kPageViewMaxSectionCount) { 380 | itemIndex = _numberOfItems*kPageViewMaxSectionCount-1; 381 | } 382 | curIndex = itemIndex%_numberOfItems; 383 | curSection = itemIndex/_numberOfItems; 384 | } 385 | return SYAMakeIndexSection(curIndex, curSection); 386 | } 387 | 388 | - (CGFloat)caculateOffsetXAtIndexSection:(SYAIndexSection)indexSection{ 389 | if (_numberOfItems == 0) { 390 | return 0; 391 | } 392 | UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)_collectionView.collectionViewLayout; 393 | CGFloat leftEdge = _isInfiniteLoop ? _layout.sectionInset.left : _layout.onlyOneSectionInset.left; 394 | CGFloat width = CGRectGetWidth(_collectionView.frame); 395 | CGFloat itemWidth = layout.itemSize.width + layout.minimumInteritemSpacing; 396 | CGFloat offsetX = leftEdge + itemWidth*(indexSection.index + indexSection.section*_numberOfItems) - layout.minimumInteritemSpacing/2 - (width - itemWidth)/2; 397 | return MAX(offsetX, 0); 398 | } 399 | 400 | - (void)resetPagerViewAtIndex:(NSInteger)index { 401 | if (index < 0) { 402 | return; 403 | } 404 | if (index >= _numberOfItems) { 405 | index = 0; 406 | } 407 | [self scrollToItemAtIndexSection:SYAMakeIndexSection(index, _isInfiniteLoop ? kPageViewMaxSectionCount/3 : 0) animate:NO]; 408 | if (!_isInfiniteLoop && _indexSection.index < 0) { 409 | [self scrollViewDidScroll:_collectionView]; 410 | } 411 | } 412 | 413 | - (void)recyclePagerViewIfNeed { 414 | if (!_isInfiniteLoop) { 415 | return; 416 | } 417 | if (_indexSection.section > kPageViewMaxSectionCount - kPageViewMinSectionCount || _indexSection.section < kPageViewMinSectionCount) { 418 | [self resetPagerViewAtIndex:_indexSection.index]; 419 | } 420 | } 421 | 422 | #pragma mark - UICollectionViewDataSource 423 | 424 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { 425 | return _isInfiniteLoop ? kPageViewMaxSectionCount : 1; 426 | } 427 | 428 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 429 | _numberOfItems = [_dataSource numberOfItemsInPagerView:self]; 430 | return _numberOfItems; 431 | } 432 | 433 | - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 434 | _dequeueSection = indexPath.section; 435 | if (_dataSourceFlags.cellForItemAtIndex) { 436 | return [_dataSource pagerView:self cellForItemAtIndex:indexPath.row]; 437 | } 438 | NSAssert(NO, @"pagerView cellForItemAtIndex: is nil!"); 439 | return nil; 440 | } 441 | 442 | #pragma mark - UICollectionViewDelegateFlowLayout 443 | 444 | - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { 445 | if (!_isInfiniteLoop) { 446 | return _layout.onlyOneSectionInset; 447 | } 448 | if (section == 0 ) { 449 | return _layout.firstSectionInset; 450 | }else if (section == kPageViewMaxSectionCount -1) { 451 | return _layout.lastSectionInset; 452 | } 453 | return _layout.middleSectionInset; 454 | } 455 | 456 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { 457 | UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath]; 458 | if ([_delegate respondsToSelector:@selector(pagerView:didSelectedItemCell:atIndex:)]) { 459 | [_delegate pagerView:self didSelectedItemCell:cell atIndex:indexPath.item]; 460 | } 461 | } 462 | 463 | #pragma mark - UIScrollViewDelegate 464 | 465 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView { 466 | if (!_didLayout) { 467 | return; 468 | } 469 | SYAIndexSection newIndexSection = [self caculateIndexSectionWithOffsetX:scrollView.contentOffset.x]; 470 | if (_numberOfItems <= 0 || ![self isValidIndexSection:newIndexSection]) { 471 | NSLog(@"inVlaidIndexSection:(%ld,%ld)!",(long)newIndexSection.index,(long)newIndexSection.section); 472 | return; 473 | } 474 | SYAIndexSection indexSection = _indexSection; 475 | _indexSection = newIndexSection; 476 | 477 | if (_delegateFlags.pagerViewDidScroll) { 478 | [_delegate pagerViewDidScroll:self]; 479 | } 480 | 481 | if (_delegateFlags.didScrollFromIndexToNewIndex && !SYAEqualIndexSection(_indexSection, indexSection)) { 482 | [_delegate pagerView:self didScrollFromIndex:MAX(indexSection.index, 0) toIndex:_indexSection.index]; 483 | } 484 | } 485 | 486 | - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { 487 | if (_autoScrollInterval > 0) { 488 | [self removeTimer]; 489 | } 490 | _beginDragIndexSection = [self caculateIndexSectionWithOffsetX:scrollView.contentOffset.x]; 491 | if ([_delegate respondsToSelector:@selector(pagerViewWillBeginDragging:)]) { 492 | [_delegate pagerViewWillBeginDragging:self]; 493 | } 494 | } 495 | 496 | - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { 497 | if (fabs(velocity.x) < 0.35 || !SYAEqualIndexSection(_beginDragIndexSection, _indexSection)) { 498 | targetContentOffset->x = [self caculateOffsetXAtIndexSection:_indexSection]; 499 | return; 500 | } 501 | SYAPageScrollDirection direction = SYAPageScrollDirectionRight; 502 | if ((scrollView.contentOffset.x < 0 && targetContentOffset->x <= 0) || (targetContentOffset->x < scrollView.contentOffset.x && scrollView.contentOffset.x < scrollView.contentSize.width - scrollView.frame.size.width)) { 503 | direction = SYAPageScrollDirectionLeft; 504 | } 505 | SYAIndexSection indexSection = [self nearlyIndexPathForIndexSection:_indexSection direction:direction]; 506 | targetContentOffset->x = [self caculateOffsetXAtIndexSection:indexSection]; 507 | } 508 | 509 | - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { 510 | if (_autoScrollInterval > 0) { 511 | [self addTimer]; 512 | } 513 | if ([_delegate respondsToSelector:@selector(pagerViewDidEndDragging:willDecelerate:)]) { 514 | [_delegate pagerViewDidEndDragging:self willDecelerate:decelerate]; 515 | } 516 | } 517 | 518 | - (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView { 519 | if ([_delegate respondsToSelector:@selector(pagerViewWillBeginDecelerating:)]) { 520 | [_delegate pagerViewWillBeginDecelerating:self]; 521 | } 522 | } 523 | 524 | - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { 525 | [self recyclePagerViewIfNeed]; 526 | if ([_delegate respondsToSelector:@selector(pagerViewDidEndDecelerating:)]) { 527 | [_delegate pagerViewDidEndDecelerating:self]; 528 | } 529 | } 530 | 531 | - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView { 532 | [self recyclePagerViewIfNeed]; 533 | if ([_delegate respondsToSelector:@selector(pagerViewDidEndScrollingAnimation:)]) { 534 | [_delegate pagerViewDidEndScrollingAnimation:self]; 535 | } 536 | } 537 | 538 | - (void)pageViewTransformLayout:(SYACyclePageTransformLayout *)pagerViewTransformLayout initializeTransformAttributes:(UICollectionViewLayoutAttributes *)attributes { 539 | if (_delegateFlags.initializeTransformAttributes) { 540 | [_delegate pagerView:self initializeTransformAttributes:attributes]; 541 | } 542 | } 543 | 544 | - (void)pageViewTransformLayout:(SYACyclePageTransformLayout *)pagerViewTransformLayout applyTransformToAttributes:(UICollectionViewLayoutAttributes *)attributes { 545 | if (_delegateFlags.applyTransformToAttributes) { 546 | [_delegate pagerView:self applyTransformToAttributes:attributes]; 547 | } 548 | } 549 | 550 | - (void)layoutSubviews { 551 | [super layoutSubviews]; 552 | BOOL needUpdateLayout = !CGRectEqualToRect(_collectionView.frame, self.bounds); 553 | _collectionView.frame = self.bounds; 554 | if ((_indexSection.section < 0 || needUpdateLayout) && (_numberOfItems > 0 || _didReloadData)) { 555 | _didLayout = YES; 556 | [self setNeedUpdateLayout]; 557 | } 558 | } 559 | 560 | - (void)dealloc { 561 | ((SYACyclePageTransformLayout *)_collectionView.collectionViewLayout).delegate = nil; 562 | _collectionView.delegate = nil; 563 | _collectionView.dataSource = nil; 564 | } 565 | 566 | @end 567 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## SYACyclePageView 2 | * 简单实现多种轮播效果,自定义程度高 3 | ## 集成方式 4 | * pod 'SYACyclePageView' 5 | ## 使用方式 6 | * 参考demo中的使用方式 7 | ## 效果展示 8 | 1. 正常轮播 9 | ![正常.gif](http://upload-images.jianshu.io/upload_images/1633901-05a4da998035ea9f.gif?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 10 | 2. 放缩轮播 11 | ![放缩.gif](http://upload-images.jianshu.io/upload_images/1633901-edc5d2fdc17b5057.gif?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 12 | 3. coverflow 13 | ![flow.gif](http://upload-images.jianshu.io/upload_images/1633901-d4637e7af627475a.gif?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 14 | ## 补充 15 | * 如果喜欢请star我,欢迎fork,让我们一起让他更完善 16 | -------------------------------------------------------------------------------- /SYACyclePageView.podspec: -------------------------------------------------------------------------------- 1 | 2 | Pod::Spec.new do |s| 3 | s.name = "SYACyclePageView" 4 | s.version = "0.0.1" 5 | s.summary = "iOS图片无限轮播框架,支持三种轮播方式" 6 | s.homepage = "https://github.com/geekGetup/SYACyclePageView" 7 | s.license = "MIT" 8 | s.author = { "geekGetup" => "1044212178@qq.com" } 9 | s.platform = :ios 10 | s.platform = :ios, "8.0" 11 | s.source = { :git => "https://github.com/geekGetup/SYACyclePageView.git", :tag => "#{s.version}" } 12 | s.source_files = "CyclePageView", "CyclePageView/**/*.{h,m}" 13 | s.framework = "UIKit" 14 | end 15 | -------------------------------------------------------------------------------- /SYACyclePageViewDemo/SYACyclePageViewDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1BD615661FE162AC0005C3F3 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1BD615651FE162AC0005C3F3 /* AppDelegate.m */; }; 11 | 1BD6156E1FE162AC0005C3F3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1BD6156D1FE162AC0005C3F3 /* Assets.xcassets */; }; 12 | 1BD615711FE162AC0005C3F3 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1BD6156F1FE162AC0005C3F3 /* LaunchScreen.storyboard */; }; 13 | 1BD615741FE162AC0005C3F3 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 1BD615731FE162AC0005C3F3 /* main.m */; }; 14 | 1BD615831FE166110005C3F3 /* SYACyclePageTransformLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 1BD615821FE166110005C3F3 /* SYACyclePageTransformLayout.m */; }; 15 | 1BD6158F1FE170EB0005C3F3 /* SYACyclePageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 1BD6158E1FE170EB0005C3F3 /* SYACyclePageView.m */; }; 16 | 1BD615921FE17E120005C3F3 /* TYCyclePagerViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 1BD615901FE17E110005C3F3 /* TYCyclePagerViewCell.m */; }; 17 | 1BD615951FE17EE50005C3F3 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1BD615931FE17EE50005C3F3 /* ViewController.m */; }; 18 | 1BD615971FE17EEB0005C3F3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1BD615961FE17EEB0005C3F3 /* Main.storyboard */; }; 19 | 1BD6159A1FE17F110005C3F3 /* TYPageControl.m in Sources */ = {isa = PBXBuildFile; fileRef = 1BD615981FE17F110005C3F3 /* TYPageControl.m */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXFileReference section */ 23 | 1BD615611FE162AC0005C3F3 /* SYACyclePageViewDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SYACyclePageViewDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 24 | 1BD615641FE162AC0005C3F3 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 25 | 1BD615651FE162AC0005C3F3 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 26 | 1BD6156D1FE162AC0005C3F3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 27 | 1BD615701FE162AC0005C3F3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 28 | 1BD615721FE162AC0005C3F3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 29 | 1BD615731FE162AC0005C3F3 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 30 | 1BD615811FE166110005C3F3 /* SYACyclePageTransformLayout.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SYACyclePageTransformLayout.h; sourceTree = ""; }; 31 | 1BD615821FE166110005C3F3 /* SYACyclePageTransformLayout.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SYACyclePageTransformLayout.m; sourceTree = ""; }; 32 | 1BD6158D1FE170EB0005C3F3 /* SYACyclePageView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SYACyclePageView.h; sourceTree = ""; }; 33 | 1BD6158E1FE170EB0005C3F3 /* SYACyclePageView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SYACyclePageView.m; sourceTree = ""; }; 34 | 1BD615901FE17E110005C3F3 /* TYCyclePagerViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TYCyclePagerViewCell.m; sourceTree = ""; }; 35 | 1BD615911FE17E120005C3F3 /* TYCyclePagerViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TYCyclePagerViewCell.h; sourceTree = ""; }; 36 | 1BD615931FE17EE50005C3F3 /* ViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 37 | 1BD615941FE17EE50005C3F3 /* ViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 38 | 1BD615961FE17EEB0005C3F3 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 39 | 1BD615981FE17F110005C3F3 /* TYPageControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TYPageControl.m; sourceTree = ""; }; 40 | 1BD615991FE17F110005C3F3 /* TYPageControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TYPageControl.h; sourceTree = ""; }; 41 | /* End PBXFileReference section */ 42 | 43 | /* Begin PBXFrameworksBuildPhase section */ 44 | 1BD6155E1FE162AC0005C3F3 /* Frameworks */ = { 45 | isa = PBXFrameworksBuildPhase; 46 | buildActionMask = 2147483647; 47 | files = ( 48 | ); 49 | runOnlyForDeploymentPostprocessing = 0; 50 | }; 51 | /* End PBXFrameworksBuildPhase section */ 52 | 53 | /* Begin PBXGroup section */ 54 | 1BD615581FE162AB0005C3F3 = { 55 | isa = PBXGroup; 56 | children = ( 57 | 1BD615631FE162AC0005C3F3 /* SYACyclePageViewDemo */, 58 | 1BD615621FE162AC0005C3F3 /* Products */, 59 | ); 60 | sourceTree = ""; 61 | }; 62 | 1BD615621FE162AC0005C3F3 /* Products */ = { 63 | isa = PBXGroup; 64 | children = ( 65 | 1BD615611FE162AC0005C3F3 /* SYACyclePageViewDemo.app */, 66 | ); 67 | name = Products; 68 | sourceTree = ""; 69 | }; 70 | 1BD615631FE162AC0005C3F3 /* SYACyclePageViewDemo */ = { 71 | isa = PBXGroup; 72 | children = ( 73 | 1BD6157A1FE162CE0005C3F3 /* CyclePageView */, 74 | 1BD615641FE162AC0005C3F3 /* AppDelegate.h */, 75 | 1BD615651FE162AC0005C3F3 /* AppDelegate.m */, 76 | 1BD615991FE17F110005C3F3 /* TYPageControl.h */, 77 | 1BD615981FE17F110005C3F3 /* TYPageControl.m */, 78 | 1BD615941FE17EE50005C3F3 /* ViewController.h */, 79 | 1BD615931FE17EE50005C3F3 /* ViewController.m */, 80 | 1BD615961FE17EEB0005C3F3 /* Main.storyboard */, 81 | 1BD615911FE17E120005C3F3 /* TYCyclePagerViewCell.h */, 82 | 1BD615901FE17E110005C3F3 /* TYCyclePagerViewCell.m */, 83 | 1BD6156D1FE162AC0005C3F3 /* Assets.xcassets */, 84 | 1BD6156F1FE162AC0005C3F3 /* LaunchScreen.storyboard */, 85 | 1BD615721FE162AC0005C3F3 /* Info.plist */, 86 | 1BD615731FE162AC0005C3F3 /* main.m */, 87 | ); 88 | path = SYACyclePageViewDemo; 89 | sourceTree = ""; 90 | }; 91 | 1BD6157A1FE162CE0005C3F3 /* CyclePageView */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | 1BD615811FE166110005C3F3 /* SYACyclePageTransformLayout.h */, 95 | 1BD615821FE166110005C3F3 /* SYACyclePageTransformLayout.m */, 96 | 1BD6158D1FE170EB0005C3F3 /* SYACyclePageView.h */, 97 | 1BD6158E1FE170EB0005C3F3 /* SYACyclePageView.m */, 98 | ); 99 | path = CyclePageView; 100 | sourceTree = ""; 101 | }; 102 | /* End PBXGroup section */ 103 | 104 | /* Begin PBXNativeTarget section */ 105 | 1BD615601FE162AC0005C3F3 /* SYACyclePageViewDemo */ = { 106 | isa = PBXNativeTarget; 107 | buildConfigurationList = 1BD615771FE162AC0005C3F3 /* Build configuration list for PBXNativeTarget "SYACyclePageViewDemo" */; 108 | buildPhases = ( 109 | 1BD6155D1FE162AC0005C3F3 /* Sources */, 110 | 1BD6155E1FE162AC0005C3F3 /* Frameworks */, 111 | 1BD6155F1FE162AC0005C3F3 /* Resources */, 112 | ); 113 | buildRules = ( 114 | ); 115 | dependencies = ( 116 | ); 117 | name = SYACyclePageViewDemo; 118 | productName = SYACyclePageViewDemo; 119 | productReference = 1BD615611FE162AC0005C3F3 /* SYACyclePageViewDemo.app */; 120 | productType = "com.apple.product-type.application"; 121 | }; 122 | /* End PBXNativeTarget section */ 123 | 124 | /* Begin PBXProject section */ 125 | 1BD615591FE162AB0005C3F3 /* Project object */ = { 126 | isa = PBXProject; 127 | attributes = { 128 | CLASSPREFIX = SYA; 129 | LastUpgradeCheck = 0920; 130 | ORGANIZATIONNAME = www.lejiakeji.com; 131 | TargetAttributes = { 132 | 1BD615601FE162AC0005C3F3 = { 133 | CreatedOnToolsVersion = 9.2; 134 | ProvisioningStyle = Automatic; 135 | }; 136 | }; 137 | }; 138 | buildConfigurationList = 1BD6155C1FE162AB0005C3F3 /* Build configuration list for PBXProject "SYACyclePageViewDemo" */; 139 | compatibilityVersion = "Xcode 8.0"; 140 | developmentRegion = en; 141 | hasScannedForEncodings = 0; 142 | knownRegions = ( 143 | en, 144 | Base, 145 | ); 146 | mainGroup = 1BD615581FE162AB0005C3F3; 147 | productRefGroup = 1BD615621FE162AC0005C3F3 /* Products */; 148 | projectDirPath = ""; 149 | projectRoot = ""; 150 | targets = ( 151 | 1BD615601FE162AC0005C3F3 /* SYACyclePageViewDemo */, 152 | ); 153 | }; 154 | /* End PBXProject section */ 155 | 156 | /* Begin PBXResourcesBuildPhase section */ 157 | 1BD6155F1FE162AC0005C3F3 /* Resources */ = { 158 | isa = PBXResourcesBuildPhase; 159 | buildActionMask = 2147483647; 160 | files = ( 161 | 1BD615971FE17EEB0005C3F3 /* Main.storyboard in Resources */, 162 | 1BD615711FE162AC0005C3F3 /* LaunchScreen.storyboard in Resources */, 163 | 1BD6156E1FE162AC0005C3F3 /* Assets.xcassets in Resources */, 164 | ); 165 | runOnlyForDeploymentPostprocessing = 0; 166 | }; 167 | /* End PBXResourcesBuildPhase section */ 168 | 169 | /* Begin PBXSourcesBuildPhase section */ 170 | 1BD6155D1FE162AC0005C3F3 /* Sources */ = { 171 | isa = PBXSourcesBuildPhase; 172 | buildActionMask = 2147483647; 173 | files = ( 174 | 1BD6159A1FE17F110005C3F3 /* TYPageControl.m in Sources */, 175 | 1BD615951FE17EE50005C3F3 /* ViewController.m in Sources */, 176 | 1BD615741FE162AC0005C3F3 /* main.m in Sources */, 177 | 1BD615921FE17E120005C3F3 /* TYCyclePagerViewCell.m in Sources */, 178 | 1BD615831FE166110005C3F3 /* SYACyclePageTransformLayout.m in Sources */, 179 | 1BD615661FE162AC0005C3F3 /* AppDelegate.m in Sources */, 180 | 1BD6158F1FE170EB0005C3F3 /* SYACyclePageView.m in Sources */, 181 | ); 182 | runOnlyForDeploymentPostprocessing = 0; 183 | }; 184 | /* End PBXSourcesBuildPhase section */ 185 | 186 | /* Begin PBXVariantGroup section */ 187 | 1BD6156F1FE162AC0005C3F3 /* LaunchScreen.storyboard */ = { 188 | isa = PBXVariantGroup; 189 | children = ( 190 | 1BD615701FE162AC0005C3F3 /* Base */, 191 | ); 192 | name = LaunchScreen.storyboard; 193 | sourceTree = ""; 194 | }; 195 | /* End PBXVariantGroup section */ 196 | 197 | /* Begin XCBuildConfiguration section */ 198 | 1BD615751FE162AC0005C3F3 /* Debug */ = { 199 | isa = XCBuildConfiguration; 200 | buildSettings = { 201 | ALWAYS_SEARCH_USER_PATHS = NO; 202 | CLANG_ANALYZER_NONNULL = YES; 203 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 204 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 205 | CLANG_CXX_LIBRARY = "libc++"; 206 | CLANG_ENABLE_MODULES = YES; 207 | CLANG_ENABLE_OBJC_ARC = YES; 208 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 209 | CLANG_WARN_BOOL_CONVERSION = YES; 210 | CLANG_WARN_COMMA = YES; 211 | CLANG_WARN_CONSTANT_CONVERSION = YES; 212 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 213 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 214 | CLANG_WARN_EMPTY_BODY = YES; 215 | CLANG_WARN_ENUM_CONVERSION = YES; 216 | CLANG_WARN_INFINITE_RECURSION = YES; 217 | CLANG_WARN_INT_CONVERSION = YES; 218 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 219 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 220 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 221 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 222 | CLANG_WARN_STRICT_PROTOTYPES = YES; 223 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 224 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 225 | CLANG_WARN_UNREACHABLE_CODE = YES; 226 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 227 | CODE_SIGN_IDENTITY = "iPhone Developer"; 228 | COPY_PHASE_STRIP = NO; 229 | DEBUG_INFORMATION_FORMAT = dwarf; 230 | ENABLE_STRICT_OBJC_MSGSEND = YES; 231 | ENABLE_TESTABILITY = YES; 232 | GCC_C_LANGUAGE_STANDARD = gnu11; 233 | GCC_DYNAMIC_NO_PIC = NO; 234 | GCC_NO_COMMON_BLOCKS = YES; 235 | GCC_OPTIMIZATION_LEVEL = 0; 236 | GCC_PREPROCESSOR_DEFINITIONS = ( 237 | "DEBUG=1", 238 | "$(inherited)", 239 | ); 240 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 241 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 242 | GCC_WARN_UNDECLARED_SELECTOR = YES; 243 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 244 | GCC_WARN_UNUSED_FUNCTION = YES; 245 | GCC_WARN_UNUSED_VARIABLE = YES; 246 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 247 | MTL_ENABLE_DEBUG_INFO = YES; 248 | ONLY_ACTIVE_ARCH = YES; 249 | SDKROOT = iphoneos; 250 | }; 251 | name = Debug; 252 | }; 253 | 1BD615761FE162AC0005C3F3 /* Release */ = { 254 | isa = XCBuildConfiguration; 255 | buildSettings = { 256 | ALWAYS_SEARCH_USER_PATHS = NO; 257 | CLANG_ANALYZER_NONNULL = YES; 258 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 259 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 260 | CLANG_CXX_LIBRARY = "libc++"; 261 | CLANG_ENABLE_MODULES = YES; 262 | CLANG_ENABLE_OBJC_ARC = YES; 263 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 264 | CLANG_WARN_BOOL_CONVERSION = YES; 265 | CLANG_WARN_COMMA = YES; 266 | CLANG_WARN_CONSTANT_CONVERSION = YES; 267 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 268 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 269 | CLANG_WARN_EMPTY_BODY = YES; 270 | CLANG_WARN_ENUM_CONVERSION = YES; 271 | CLANG_WARN_INFINITE_RECURSION = YES; 272 | CLANG_WARN_INT_CONVERSION = YES; 273 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 274 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 275 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 276 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 277 | CLANG_WARN_STRICT_PROTOTYPES = YES; 278 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 279 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 280 | CLANG_WARN_UNREACHABLE_CODE = YES; 281 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 282 | CODE_SIGN_IDENTITY = "iPhone Developer"; 283 | COPY_PHASE_STRIP = NO; 284 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 285 | ENABLE_NS_ASSERTIONS = NO; 286 | ENABLE_STRICT_OBJC_MSGSEND = YES; 287 | GCC_C_LANGUAGE_STANDARD = gnu11; 288 | GCC_NO_COMMON_BLOCKS = YES; 289 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 290 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 291 | GCC_WARN_UNDECLARED_SELECTOR = YES; 292 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 293 | GCC_WARN_UNUSED_FUNCTION = YES; 294 | GCC_WARN_UNUSED_VARIABLE = YES; 295 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 296 | MTL_ENABLE_DEBUG_INFO = NO; 297 | SDKROOT = iphoneos; 298 | VALIDATE_PRODUCT = YES; 299 | }; 300 | name = Release; 301 | }; 302 | 1BD615781FE162AC0005C3F3 /* Debug */ = { 303 | isa = XCBuildConfiguration; 304 | buildSettings = { 305 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 306 | CODE_SIGN_STYLE = Automatic; 307 | DEVELOPMENT_TEAM = S27786LSHW; 308 | INFOPLIST_FILE = SYACyclePageViewDemo/Info.plist; 309 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 310 | PRODUCT_BUNDLE_IDENTIFIER = com.lejiakeji.SYACyclePageViewDemo; 311 | PRODUCT_NAME = "$(TARGET_NAME)"; 312 | TARGETED_DEVICE_FAMILY = "1,2"; 313 | }; 314 | name = Debug; 315 | }; 316 | 1BD615791FE162AC0005C3F3 /* Release */ = { 317 | isa = XCBuildConfiguration; 318 | buildSettings = { 319 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 320 | CODE_SIGN_STYLE = Automatic; 321 | DEVELOPMENT_TEAM = S27786LSHW; 322 | INFOPLIST_FILE = SYACyclePageViewDemo/Info.plist; 323 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 324 | PRODUCT_BUNDLE_IDENTIFIER = com.lejiakeji.SYACyclePageViewDemo; 325 | PRODUCT_NAME = "$(TARGET_NAME)"; 326 | TARGETED_DEVICE_FAMILY = "1,2"; 327 | }; 328 | name = Release; 329 | }; 330 | /* End XCBuildConfiguration section */ 331 | 332 | /* Begin XCConfigurationList section */ 333 | 1BD6155C1FE162AB0005C3F3 /* Build configuration list for PBXProject "SYACyclePageViewDemo" */ = { 334 | isa = XCConfigurationList; 335 | buildConfigurations = ( 336 | 1BD615751FE162AC0005C3F3 /* Debug */, 337 | 1BD615761FE162AC0005C3F3 /* Release */, 338 | ); 339 | defaultConfigurationIsVisible = 0; 340 | defaultConfigurationName = Release; 341 | }; 342 | 1BD615771FE162AC0005C3F3 /* Build configuration list for PBXNativeTarget "SYACyclePageViewDemo" */ = { 343 | isa = XCConfigurationList; 344 | buildConfigurations = ( 345 | 1BD615781FE162AC0005C3F3 /* Debug */, 346 | 1BD615791FE162AC0005C3F3 /* Release */, 347 | ); 348 | defaultConfigurationIsVisible = 0; 349 | defaultConfigurationName = Release; 350 | }; 351 | /* End XCConfigurationList section */ 352 | }; 353 | rootObject = 1BD615591FE162AB0005C3F3 /* Project object */; 354 | } 355 | -------------------------------------------------------------------------------- /SYACyclePageViewDemo/SYACyclePageViewDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SYACyclePageViewDemo/SYACyclePageViewDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // SYACyclePageViewDemo 4 | // 5 | // Created by zq on 2017/12/13. 6 | // Copyright © 2017年 www.lejiakeji.com. 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 | -------------------------------------------------------------------------------- /SYACyclePageViewDemo/SYACyclePageViewDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // SYACyclePageViewDemo 4 | // 5 | // Created by zq on 2017/12/13. 6 | // Copyright © 2017年 www.lejiakeji.com. 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 | -------------------------------------------------------------------------------- /SYACyclePageViewDemo/SYACyclePageViewDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /SYACyclePageViewDemo/SYACyclePageViewDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /SYACyclePageViewDemo/SYACyclePageViewDemo/CyclePageView/SYACyclePageTransformLayout.h: -------------------------------------------------------------------------------- 1 | // 2 | // SYACyclePageTransformLayout.h 3 | // SYACyclePageViewDemo 4 | // 5 | // Created by zq on 2017/12/13. 6 | // Copyright © 2017年 www.lejiakeji.com. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | typedef NS_ENUM(NSUInteger, SYACyclePageTransformLayoutType) { 14 | SYACyclePageTransformLayoutNormal, 15 | SYAyclePageTransformLayoutLinear, 16 | SYACyclePageTransformLayoutCoverflow, 17 | }; 18 | @class SYACyclePageTransformLayout; 19 | @protocol SYACyclePageTransformLayoutDelegate 20 | 21 | - (void)pageViewTransformLayout:(SYACyclePageTransformLayout *)pageViewTransformLayout initializeTransformAttributes:(UICollectionViewLayoutAttributes *)attributes; 22 | 23 | - (void)pageViewTransformLayout:(SYACyclePageTransformLayout *)pageViewTransformLayout applyTransformToAttributes:(UICollectionViewLayoutAttributes *)attributes; 24 | 25 | @end 26 | 27 | @interface SYACyclePageViewLayout : NSObject 28 | 29 | @property (nonatomic, assign) CGSize itemSize; 30 | 31 | @property (nonatomic, assign) CGFloat itemSpacing; 32 | 33 | @property (nonatomic, assign) UIEdgeInsets sectionInset; 34 | 35 | @property (nonatomic, assign) SYACyclePageTransformLayoutType layoutType; 36 | 37 | @property (nonatomic, assign) CGFloat minimumScale; 38 | 39 | @property (nonatomic, assign) CGFloat minimumAlpha; 40 | 41 | @property (nonatomic, assign) CGFloat maximumAngle; 42 | 43 | @property (nonatomic, assign) BOOL isInfiniteLoop; 44 | 45 | @property (nonatomic, assign) CGFloat rateOfChange; 46 | 47 | @property (nonatomic, assign) BOOL adjustSpacingWhenScroling; 48 | 49 | @property (nonatomic, assign) BOOL itemVerticalCenter; 50 | 51 | @property (nonatomic, assign) BOOL itemHorizontalCenter; 52 | 53 | // sectionInset 54 | @property (nonatomic, assign, readonly) UIEdgeInsets onlyOneSectionInset; 55 | @property (nonatomic, assign, readonly) UIEdgeInsets firstSectionInset; 56 | @property (nonatomic, assign, readonly) UIEdgeInsets lastSectionInset; 57 | @property (nonatomic, assign, readonly) UIEdgeInsets middleSectionInset; 58 | 59 | @end 60 | 61 | @interface SYACyclePageTransformLayout : UICollectionViewFlowLayout 62 | 63 | @property (nonatomic, strong) SYACyclePageViewLayout *layout; 64 | 65 | @property (nonatomic, weak) id delegate; 66 | 67 | @end 68 | 69 | NS_ASSUME_NONNULL_END 70 | 71 | -------------------------------------------------------------------------------- /SYACyclePageViewDemo/SYACyclePageViewDemo/CyclePageView/SYACyclePageTransformLayout.m: -------------------------------------------------------------------------------- 1 | // 2 | // SYACyclePageTransformLayout.m 3 | // SYACyclePageViewDemo 4 | // 5 | // Created by zq on 2017/12/13. 6 | // Copyright © 2017年 www.lejiakeji.com. All rights reserved. 7 | // 8 | 9 | #import "SYACyclePageTransformLayout.h" 10 | typedef NS_ENUM(NSUInteger, SYATransformLayoutItemDirection) { 11 | SYATransformLayoutItemLeft, 12 | SYATransformLayoutItemCenter, 13 | SYATransformLayoutItemRight, 14 | }; 15 | @interface SYACyclePageTransformLayout(){ 16 | struct { 17 | unsigned int applyTransformToAttributes :1; 18 | unsigned int initializeTransformAttributes :1; 19 | }_delegateFlags; 20 | } 21 | @property (nonatomic, assign) BOOL applyTransformToAttributesDelegate; 22 | 23 | @end 24 | 25 | @interface SYACyclePageViewLayout() 26 | 27 | @property (nonatomic, weak) UIView *pageView; 28 | 29 | @end 30 | 31 | @implementation SYACyclePageTransformLayout 32 | - (instancetype)init { 33 | if (self = [super init]) { 34 | self.scrollDirection = UICollectionViewScrollDirectionHorizontal; 35 | } 36 | return self; 37 | } 38 | 39 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 40 | if (self = [super initWithCoder:aDecoder]) { 41 | self.scrollDirection = UICollectionViewScrollDirectionHorizontal; 42 | } 43 | return self; 44 | } 45 | 46 | - (void)setDelegate:(id)delegate { 47 | _delegate = delegate; 48 | _delegateFlags.initializeTransformAttributes = [delegate respondsToSelector:@selector(pageViewTransformLayout:initializeTransformAttributes:)]; 49 | _delegateFlags.applyTransformToAttributes = [delegate respondsToSelector:@selector(pageViewTransformLayout:applyTransformToAttributes:)]; 50 | } 51 | 52 | - (void)setLayout:(SYACyclePageViewLayout *)layout { 53 | _layout = layout; 54 | _layout.pageView = self.collectionView; 55 | self.itemSize = _layout.itemSize; 56 | self.minimumInteritemSpacing = _layout.itemSpacing; 57 | self.minimumLineSpacing = _layout.itemSpacing; 58 | } 59 | 60 | - (CGSize)itemSize { 61 | if (!_layout) { 62 | return [super itemSize]; 63 | } 64 | return _layout.itemSize; 65 | } 66 | 67 | - (CGFloat)minimumLineSpacing { 68 | if (!_layout) { 69 | return [super minimumLineSpacing]; 70 | } 71 | return _layout.itemSpacing; 72 | } 73 | 74 | - (CGFloat)minimumInteritemSpacing { 75 | if (!_layout) { 76 | return [super minimumInteritemSpacing]; 77 | } 78 | return _layout.itemSpacing; 79 | } 80 | 81 | - (SYATransformLayoutItemDirection)directionWithCenterX:(CGFloat)centerX { 82 | SYATransformLayoutItemDirection direction= SYATransformLayoutItemRight; 83 | CGFloat contentCenterX = self.collectionView.contentOffset.x + CGRectGetWidth(self.collectionView.frame)/2; 84 | if (ABS(centerX - contentCenterX) < 0.5) { 85 | direction = SYATransformLayoutItemCenter; 86 | }else if (centerX - contentCenterX < 0) { 87 | direction = SYATransformLayoutItemLeft; 88 | } 89 | return direction; 90 | } 91 | 92 | #pragma mark - layout 93 | 94 | -(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds 95 | { 96 | return _layout.layoutType == SYACyclePageTransformLayoutNormal ? [super shouldInvalidateLayoutForBoundsChange:newBounds] : YES; 97 | } 98 | 99 | - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { 100 | if (_delegateFlags.applyTransformToAttributes || _layout.layoutType != SYACyclePageTransformLayoutNormal) { 101 | NSArray *attributesArray = [[NSArray alloc] initWithArray:[super layoutAttributesForElementsInRect:rect] copyItems:YES]; 102 | CGRect visibleRect = {self.collectionView.contentOffset,self.collectionView.bounds.size}; 103 | for (UICollectionViewLayoutAttributes *attributes in attributesArray) { 104 | if (!CGRectIntersectsRect(visibleRect, attributes.frame)) { 105 | continue; 106 | } 107 | if (_delegateFlags.applyTransformToAttributes) { 108 | [_delegate pageViewTransformLayout:self applyTransformToAttributes:attributes]; 109 | }else { 110 | [self applyTransformToAttributes:attributes layoutType:_layout.layoutType]; 111 | } 112 | } 113 | return attributesArray; 114 | } 115 | return [super layoutAttributesForElementsInRect:rect]; 116 | } 117 | 118 | - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { 119 | UICollectionViewLayoutAttributes *attributes = [super layoutAttributesForItemAtIndexPath:indexPath]; 120 | if (_delegateFlags.initializeTransformAttributes) { 121 | [_delegate pageViewTransformLayout:self initializeTransformAttributes:attributes]; 122 | }else if(_layout.layoutType != SYACyclePageTransformLayoutNormal){ 123 | [self initializeTransformAttributes:attributes layoutType:_layout.layoutType]; 124 | } 125 | return attributes; 126 | } 127 | 128 | #pragma mark - transform 129 | 130 | - (void)initializeTransformAttributes:(UICollectionViewLayoutAttributes *)attributes layoutType:(SYACyclePageTransformLayoutType)layoutType { 131 | switch (layoutType) { 132 | case SYAyclePageTransformLayoutLinear: 133 | [self applyLinearTransformToAttributes:attributes scale:_layout.minimumScale alpha:_layout.minimumAlpha]; 134 | break; 135 | case SYACyclePageTransformLayoutCoverflow: 136 | { 137 | [self applyCoverflowTransformToAttributes:attributes angle:_layout.maximumAngle alpha:_layout.minimumAlpha]; 138 | break; 139 | } 140 | default: 141 | break; 142 | } 143 | } 144 | 145 | - (void)applyTransformToAttributes:(UICollectionViewLayoutAttributes *)attributes layoutType:(SYACyclePageTransformLayoutType)layoutType { 146 | switch (layoutType) { 147 | case SYAyclePageTransformLayoutLinear: 148 | [self applyLinearTransformToAttributes:attributes]; 149 | break; 150 | case SYACyclePageTransformLayoutCoverflow: 151 | [self applyCoverflowTransformToAttributes:attributes]; 152 | break; 153 | default: 154 | break; 155 | } 156 | } 157 | 158 | #pragma mark - LinearTransform 159 | 160 | - (void)applyLinearTransformToAttributes:(UICollectionViewLayoutAttributes *)attributes { 161 | CGFloat collectionViewWidth = self.collectionView.frame.size.width; 162 | if (collectionViewWidth <= 0) { 163 | return; 164 | } 165 | CGFloat centetX = self.collectionView.contentOffset.x + collectionViewWidth/2; 166 | CGFloat delta = ABS(attributes.center.x - centetX); 167 | CGFloat scale = MAX(1 - delta/collectionViewWidth*_layout.rateOfChange, _layout.minimumScale); 168 | CGFloat alpha = MAX(1 - delta/collectionViewWidth, _layout.minimumAlpha); 169 | [self applyLinearTransformToAttributes:attributes scale:scale alpha:alpha]; 170 | } 171 | 172 | - (void)applyLinearTransformToAttributes:(UICollectionViewLayoutAttributes *)attributes scale:(CGFloat)scale alpha:(CGFloat)alpha { 173 | CGAffineTransform transform = CGAffineTransformMakeScale(scale, scale); 174 | if (_layout.adjustSpacingWhenScroling) { 175 | SYATransformLayoutItemDirection direction = [self directionWithCenterX:attributes.center.x]; 176 | CGFloat translate = 0; 177 | switch (direction) { 178 | case SYATransformLayoutItemLeft: 179 | translate = 1.15 * attributes.size.width*(1-scale)/2; 180 | break; 181 | case SYATransformLayoutItemRight: 182 | translate = -1.15 * attributes.size.width*(1-scale)/2; 183 | break; 184 | default: 185 | // center 186 | scale = 1.0; 187 | alpha = 1.0; 188 | break; 189 | } 190 | transform = CGAffineTransformTranslate(transform,translate, 0); 191 | } 192 | attributes.transform = transform; 193 | attributes.alpha = alpha; 194 | } 195 | 196 | #pragma mark - CoverflowTransform 197 | 198 | - (void)applyCoverflowTransformToAttributes:(UICollectionViewLayoutAttributes *)attributes{ 199 | CGFloat collectionViewWidth = self.collectionView.frame.size.width; 200 | if (collectionViewWidth <= 0) { 201 | return; 202 | } 203 | CGFloat centetX = self.collectionView.contentOffset.x + collectionViewWidth/2; 204 | CGFloat delta = ABS(attributes.center.x - centetX); 205 | CGFloat angle = MIN(delta/collectionViewWidth*(1-_layout.rateOfChange), _layout.maximumAngle); 206 | CGFloat alpha = MAX(1 - delta/collectionViewWidth, _layout.minimumAlpha); 207 | [self applyCoverflowTransformToAttributes:attributes angle:angle alpha:alpha]; 208 | } 209 | 210 | - (void)applyCoverflowTransformToAttributes:(UICollectionViewLayoutAttributes *)attributes angle:(CGFloat)angle alpha:(CGFloat)alpha { 211 | SYATransformLayoutItemDirection direction = [self directionWithCenterX:attributes.center.x]; 212 | CATransform3D transform3D = CATransform3DIdentity; 213 | transform3D.m34 = -0.002; 214 | CGFloat translate = 0; 215 | switch (direction) { 216 | case SYATransformLayoutItemLeft: 217 | translate = (1-cos(angle*1.2*M_PI))*attributes.size.width; 218 | break; 219 | case SYATransformLayoutItemRight: 220 | translate = -(1-cos(angle*1.2*M_PI))*attributes.size.width; 221 | angle = -angle; 222 | break; 223 | default: 224 | // center 225 | angle = 0; 226 | alpha = 1; 227 | break; 228 | } 229 | 230 | transform3D = CATransform3DRotate(transform3D, M_PI*angle, 0, 1, 0); 231 | if (_layout.adjustSpacingWhenScroling) { 232 | transform3D = CATransform3DTranslate(transform3D, translate, 0, 0); 233 | } 234 | attributes.transform3D = transform3D; 235 | attributes.alpha = alpha; 236 | 237 | } 238 | @end 239 | 240 | 241 | @implementation SYACyclePageViewLayout 242 | 243 | - (instancetype)init { 244 | if (self = [super init]) { 245 | _itemVerticalCenter = YES; 246 | _minimumScale = 0.8; 247 | _minimumAlpha = 1.0; 248 | _maximumAngle = 0.2; 249 | _rateOfChange = 0.4; 250 | _adjustSpacingWhenScroling = YES; 251 | } 252 | return self; 253 | } 254 | 255 | #pragma mark - getter 256 | 257 | - (UIEdgeInsets)onlyOneSectionInset { 258 | CGFloat leftSpace = _pageView && !_isInfiniteLoop && _itemHorizontalCenter ? (CGRectGetWidth(_pageView.frame) - _itemSize.width)/2 : _sectionInset.left; 259 | CGFloat rightSpace = _pageView && !_isInfiniteLoop && _itemHorizontalCenter ? (CGRectGetWidth(_pageView.frame) - _itemSize.width)/2 : _sectionInset.right; 260 | if (_itemVerticalCenter) { 261 | CGFloat verticalSpace = (CGRectGetHeight(_pageView.frame) - _itemSize.height)/2; 262 | return UIEdgeInsetsMake(verticalSpace, leftSpace, verticalSpace, rightSpace); 263 | } 264 | return UIEdgeInsetsMake(_sectionInset.top, leftSpace, _sectionInset.bottom, rightSpace); 265 | } 266 | 267 | - (UIEdgeInsets)firstSectionInset { 268 | if (_itemVerticalCenter) { 269 | CGFloat verticalSpace = (CGRectGetHeight(_pageView.frame) - _itemSize.height)/2; 270 | return UIEdgeInsetsMake(verticalSpace, _sectionInset.left, verticalSpace, _itemSpacing); 271 | } 272 | return UIEdgeInsetsMake(_sectionInset.top, _sectionInset.left, _sectionInset.bottom, _itemSpacing); 273 | } 274 | 275 | - (UIEdgeInsets)lastSectionInset { 276 | if (_itemVerticalCenter) { 277 | CGFloat verticalSpace = (CGRectGetHeight(_pageView.frame) - _itemSize.height)/2; 278 | return UIEdgeInsetsMake(verticalSpace, 0, verticalSpace, _sectionInset.right); 279 | } 280 | return UIEdgeInsetsMake(_sectionInset.top, 0, _sectionInset.bottom, _sectionInset.right); 281 | } 282 | 283 | - (UIEdgeInsets)middleSectionInset { 284 | if (_itemVerticalCenter) { 285 | CGFloat verticalSpace = (CGRectGetHeight(_pageView.frame) - _itemSize.height)/2; 286 | return UIEdgeInsetsMake(verticalSpace, 0, verticalSpace, _itemSpacing); 287 | } 288 | return _sectionInset; 289 | } 290 | @end 291 | -------------------------------------------------------------------------------- /SYACyclePageViewDemo/SYACyclePageViewDemo/CyclePageView/SYACyclePageView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SYACyclePageView.h 3 | // SYACyclePageViewDemo 4 | // 5 | // Created by zq on 2017/12/13. 6 | // Copyright © 2017年 www.lejiakeji.com. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "SYACyclePageTransformLayout.h" 11 | NS_ASSUME_NONNULL_BEGIN 12 | typedef NS_ENUM(NSUInteger, SYAPageScrollDirection) { 13 | SYAPageScrollDirectionLeft, 14 | SYAPageScrollDirectionRight, 15 | }; 16 | @class SYACyclePageView; 17 | @protocol SYACyclePagerViewDataSource 18 | 19 | - (NSInteger)numberOfItemsInPagerView:(SYACyclePageView *)pageView; 20 | 21 | - (__kindof UICollectionViewCell *)pagerView:(SYACyclePageView *)pagerView cellForItemAtIndex:(NSInteger)index; 22 | 23 | - (SYACyclePageViewLayout *)layoutForPagerView:(SYACyclePageView *)pageView; 24 | 25 | @end 26 | 27 | @protocol SYACyclePageViewDelegate 28 | 29 | @optional 30 | 31 | - (void)pagerView:(SYACyclePageView *)pageView didScrollFromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex; 32 | 33 | - (void)pagerView:(SYACyclePageView *)pageView didSelectedItemCell:(__kindof UICollectionViewCell *)cell atIndex:(NSInteger)index; 34 | 35 | - (void)pagerView:(SYACyclePageView *)pageView initializeTransformAttributes:(UICollectionViewLayoutAttributes *)attributes; 36 | 37 | - (void)pagerView:(SYACyclePageView *)pageView applyTransformToAttributes:(UICollectionViewLayoutAttributes *)attributes; 38 | 39 | 40 | // scrollViewDelegate 41 | 42 | - (void)pagerViewDidScroll:(SYACyclePageView *)pageView; 43 | 44 | - (void)pagerViewWillBeginDragging:(SYACyclePageView *)pageView; 45 | 46 | - (void)pagerViewDidEndDragging:(SYACyclePageView *)pageView willDecelerate:(BOOL)decelerate; 47 | 48 | - (void)pagerViewWillBeginDecelerating:(SYACyclePageView *)pageView; 49 | 50 | - (void)pagerViewDidEndDecelerating:(SYACyclePageView *)pageView; 51 | 52 | - (void)pagerViewWillBeginScrollingAnimation:(SYACyclePageView *)pageView; 53 | 54 | - (void)pagerViewDidEndScrollingAnimation:(SYACyclePageView *)pageView; 55 | 56 | @end 57 | @interface SYACyclePageView : UIView 58 | 59 | @property (nonatomic, strong, nullable) UIView *backgroundView; 60 | 61 | @property (nonatomic, weak, nullable) id dataSource; 62 | 63 | @property (nonatomic, weak, nullable) id delegate; 64 | 65 | @property (nonatomic, weak, readonly) UICollectionView *collectionView; 66 | 67 | @property (nonatomic, strong, readonly) SYACyclePageViewLayout *layout; 68 | 69 | @property (nonatomic, assign) BOOL isInfiniteLoop; 70 | 71 | @property (nonatomic, assign) CGFloat autoScrollInterval; 72 | 73 | @property (nonatomic, assign, readonly) NSInteger curIndex; 74 | 75 | @property (nonatomic, assign, readonly) CGPoint contentOffset; 76 | 77 | @property (nonatomic, assign, readonly) BOOL tracking; 78 | 79 | @property (nonatomic, assign, readonly) BOOL dragging; 80 | 81 | @property (nonatomic, assign, readonly) BOOL decelerating; 82 | 83 | - (void)reloadData; 84 | 85 | - (void)updateData; 86 | 87 | - (void)setNeedUpdateLayout; 88 | 89 | - (void)setNeedClearLayout; 90 | 91 | - (__kindof UICollectionViewCell * _Nullable)curIndexCell; 92 | 93 | - (NSArray<__kindof UICollectionViewCell *> *_Nullable)visibleCells; 94 | 95 | - (NSArray *)visibleIndexs; 96 | 97 | - (void)scrollToItemAtIndex:(NSInteger)index animate:(BOOL)animate; 98 | 99 | - (void)scrollToNearlyIndexAtDirection:(SYAPageScrollDirection)direction animate:(BOOL)animate; 100 | 101 | - (void)registerClass:(Class)Class forCellWithReuseIdentifier:(NSString *)identifier; 102 | 103 | - (void)registerNib:(UINib *)nib forCellWithReuseIdentifier:(NSString *)identifier; 104 | 105 | - (__kindof UICollectionViewCell *)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndex:(NSInteger)index; 106 | @end 107 | NS_ASSUME_NONNULL_END 108 | -------------------------------------------------------------------------------- /SYACyclePageViewDemo/SYACyclePageViewDemo/CyclePageView/SYACyclePageView.m: -------------------------------------------------------------------------------- 1 | // 2 | // SYACyclePageView.m 3 | // SYACyclePageViewDemo 4 | // 5 | // Created by zq on 2017/12/13. 6 | // Copyright © 2017年 www.lejiakeji.com. All rights reserved. 7 | // 8 | 9 | #import "SYACyclePageView.h" 10 | typedef struct { 11 | NSInteger index; 12 | NSInteger section; 13 | }SYAIndexSection; 14 | 15 | NS_INLINE BOOL SYAEqualIndexSection(SYAIndexSection indexSection1,SYAIndexSection indexSection2) { 16 | return indexSection1.index == indexSection2.index && indexSection1.section == indexSection2.section; 17 | } 18 | 19 | NS_INLINE SYAIndexSection SYAMakeIndexSection(NSInteger index, NSInteger section) { 20 | SYAIndexSection indexSection; 21 | indexSection.index = index; 22 | indexSection.section = section; 23 | return indexSection; 24 | } 25 | 26 | @interface SYACyclePageView () { 27 | struct { 28 | unsigned int pagerViewDidScroll :1; 29 | unsigned int didScrollFromIndexToNewIndex :1; 30 | unsigned int initializeTransformAttributes :1; 31 | unsigned int applyTransformToAttributes :1; 32 | }_delegateFlags; 33 | struct { 34 | unsigned int cellForItemAtIndex :1; 35 | unsigned int layoutForPagerView :1; 36 | }_dataSourceFlags; 37 | } 38 | 39 | @property (nonatomic, weak) UICollectionView *collectionView; 40 | @property (nonatomic, strong) SYACyclePageViewLayout *layout; 41 | @property (nonatomic, strong) NSTimer *timer; 42 | 43 | @property (nonatomic, assign) NSInteger numberOfItems; 44 | 45 | @property (nonatomic, assign) SYAIndexSection indexSection; // current index 46 | @property (nonatomic, assign) NSInteger dequeueSection; 47 | @property (nonatomic, assign) SYAIndexSection beginDragIndexSection; 48 | 49 | @property (nonatomic, assign) BOOL needClearLayout; 50 | @property (nonatomic, assign) BOOL didReloadData; 51 | @property (nonatomic, assign) BOOL didLayout; 52 | @end 53 | #define kPageViewMaxSectionCount 200 54 | #define kPageViewMinSectionCount 18 55 | @implementation SYACyclePageView 56 | 57 | - (instancetype)initWithFrame:(CGRect)frame { 58 | if (self = [super initWithFrame:frame]) { 59 | [self configureProperty]; 60 | 61 | [self addCollectionView]; 62 | } 63 | return self; 64 | } 65 | 66 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 67 | if (self = [super initWithCoder:aDecoder]) { 68 | [self configureProperty]; 69 | 70 | [self addCollectionView]; 71 | } 72 | return self; 73 | } 74 | 75 | - (void)configureProperty { 76 | _didReloadData = NO; 77 | _didLayout = NO; 78 | _autoScrollInterval = 0; 79 | _isInfiniteLoop = YES; 80 | _beginDragIndexSection.index = 0; 81 | _beginDragIndexSection.section = 0; 82 | _indexSection.index = -1; 83 | _indexSection.section = -1; 84 | } 85 | 86 | - (void)addCollectionView { 87 | SYACyclePageTransformLayout *layout = [[SYACyclePageTransformLayout alloc]init]; 88 | UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; 89 | layout.delegate = _delegateFlags.applyTransformToAttributes ? self : nil;; 90 | collectionView.backgroundColor = [UIColor clearColor]; 91 | collectionView.dataSource = self; 92 | collectionView.delegate = self; 93 | collectionView.pagingEnabled = NO; 94 | collectionView.decelerationRate = 1-0.0076; 95 | if ([collectionView respondsToSelector:@selector(setPrefetchingEnabled:)]) { 96 | collectionView.prefetchingEnabled = NO; 97 | } 98 | collectionView.showsHorizontalScrollIndicator = NO; 99 | collectionView.showsVerticalScrollIndicator = NO; 100 | [self addSubview:collectionView]; 101 | _collectionView = collectionView; 102 | } 103 | 104 | - (void)willMoveToSuperview:(UIView *)newSuperview { 105 | if (!newSuperview) { 106 | [self removeTimer]; 107 | }else { 108 | [self removeTimer]; 109 | if (_autoScrollInterval > 0) { 110 | [self addTimer]; 111 | } 112 | } 113 | } 114 | 115 | #pragma mark - timer 116 | 117 | - (void)addTimer { 118 | if (_timer) { 119 | return; 120 | } 121 | _timer = [NSTimer timerWithTimeInterval:_autoScrollInterval target:self selector:@selector(timerFired:) userInfo:nil repeats:YES]; 122 | [[NSRunLoop mainRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes]; 123 | } 124 | 125 | - (void)removeTimer { 126 | if (!_timer) { 127 | return; 128 | } 129 | [_timer invalidate]; 130 | _timer = nil; 131 | } 132 | 133 | - (void)timerFired:(NSTimer *)timer { 134 | if (!self.superview || !self.window || _numberOfItems == 0 || self.tracking) { 135 | return; 136 | } 137 | 138 | [self scrollToNearlyIndexAtDirection:SYAPageScrollDirectionRight animate:YES]; 139 | } 140 | 141 | #pragma mark - getter 142 | 143 | - (SYACyclePageViewLayout *)layout { 144 | if (!_layout) { 145 | if (_dataSourceFlags.layoutForPagerView) { 146 | _layout = [_dataSource layoutForPagerView:self]; 147 | _layout.isInfiniteLoop = _isInfiniteLoop; 148 | } 149 | if (_layout.itemSize.width <= 0 || _layout.itemSize.height <= 0) { 150 | _layout = nil; 151 | } 152 | } 153 | return _layout; 154 | } 155 | 156 | - (NSInteger)curIndex { 157 | return _indexSection.index; 158 | } 159 | 160 | - (CGPoint)contentOffset { 161 | return _collectionView.contentOffset; 162 | } 163 | 164 | - (BOOL)tracking { 165 | return _collectionView.tracking; 166 | } 167 | 168 | - (BOOL)dragging { 169 | return _collectionView.dragging; 170 | } 171 | 172 | - (BOOL)decelerating { 173 | return _collectionView.decelerating; 174 | } 175 | 176 | - (UIView *)backgroundView { 177 | return _collectionView.backgroundView; 178 | } 179 | 180 | - (__kindof UICollectionViewCell *)curIndexCell { 181 | return [_collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForItem:_indexSection.index inSection:_indexSection.section]]; 182 | } 183 | 184 | - (NSArray<__kindof UICollectionViewCell *> *)visibleCells { 185 | return _collectionView.visibleCells; 186 | } 187 | 188 | - (NSArray *)visibleIndexs { 189 | NSMutableArray *indexs = [NSMutableArray array]; 190 | for (NSIndexPath *indexPath in _collectionView.indexPathsForVisibleItems) { 191 | [indexs addObject:@(indexPath.item)]; 192 | } 193 | return [indexs copy]; 194 | } 195 | 196 | #pragma mark - setter 197 | 198 | - (void)setBackgroundView:(UIView *)backgroundView { 199 | [_collectionView setBackgroundView:backgroundView]; 200 | } 201 | 202 | - (void)setAutoScrollInterval:(CGFloat)autoScrollInterval { 203 | _autoScrollInterval = autoScrollInterval; 204 | [self removeTimer]; 205 | if (autoScrollInterval > 0 && self.superview) { 206 | [self addTimer]; 207 | } 208 | } 209 | 210 | - (void)setDelegate:(id)delegate { 211 | _delegate = delegate; 212 | _delegateFlags.pagerViewDidScroll = [delegate respondsToSelector:@selector(pagerViewDidScroll:)]; 213 | _delegateFlags.didScrollFromIndexToNewIndex = [delegate respondsToSelector:@selector(pagerView:didScrollFromIndex:toIndex:)]; 214 | _delegateFlags.initializeTransformAttributes = [delegate respondsToSelector:@selector(pagerView:initializeTransformAttributes:)]; 215 | _delegateFlags.applyTransformToAttributes = [delegate respondsToSelector:@selector(pagerView:applyTransformToAttributes:)]; 216 | if (self.collectionView && self.collectionView.collectionViewLayout) { 217 | ((SYACyclePageTransformLayout *)self.collectionView.collectionViewLayout).delegate = _delegateFlags.applyTransformToAttributes ? self : nil; 218 | } 219 | } 220 | 221 | - (void)setDataSource:(id)dataSource { 222 | _dataSource = dataSource; 223 | _dataSourceFlags.cellForItemAtIndex = [dataSource respondsToSelector:@selector(pagerView:cellForItemAtIndex:)]; 224 | _dataSourceFlags.layoutForPagerView = [dataSource respondsToSelector:@selector(layoutForPagerView:)]; 225 | } 226 | 227 | #pragma mark - public 228 | 229 | - (void)reloadData { 230 | _didReloadData = YES; 231 | [self setNeedClearLayout]; 232 | [self clearLayout]; 233 | [self updateData]; 234 | } 235 | 236 | // not clear layout 237 | - (void)updateData { 238 | [self updateLayout]; 239 | _numberOfItems = [_dataSource numberOfItemsInPagerView:self]; 240 | [_collectionView reloadData]; 241 | if (!_didLayout && !CGRectIsEmpty(self.frame) && _indexSection.index < 0) { 242 | _didLayout = YES; 243 | } 244 | [self resetPagerViewAtIndex:_indexSection.index < 0 && !CGRectIsEmpty(self.frame) ? 0 :_indexSection.index]; 245 | } 246 | 247 | - (void)scrollToNearlyIndexAtDirection:(SYAPageScrollDirection)direction animate:(BOOL)animate { 248 | SYAIndexSection indexSection = [self nearlyIndexPathAtDirection:direction]; 249 | [self scrollToItemAtIndexSection:indexSection animate:animate]; 250 | } 251 | 252 | - (void)scrollToItemAtIndex:(NSInteger)index animate:(BOOL)animate { 253 | if (!_isInfiniteLoop) { 254 | [self scrollToItemAtIndexSection:SYAMakeIndexSection(index, 0) animate:animate]; 255 | return; 256 | } 257 | 258 | [self scrollToItemAtIndexSection:SYAMakeIndexSection(index, index >= self.curIndex ? _indexSection.section : _indexSection.section+1) animate:YES]; 259 | } 260 | 261 | - (void)scrollToItemAtIndexSection:(SYAIndexSection)indexSection animate:(BOOL)animate { 262 | if (_numberOfItems <= 0 || ![self isValidIndexSection:indexSection]) { 263 | return; 264 | } 265 | 266 | if (animate && [_delegate respondsToSelector:@selector(pagerViewWillBeginScrollingAnimation:)]) { 267 | [_delegate pagerViewWillBeginScrollingAnimation:self]; 268 | } 269 | CGFloat offset = [self caculateOffsetXAtIndexSection:indexSection]; 270 | [_collectionView setContentOffset:CGPointMake(offset, _collectionView.contentOffset.y) animated:animate]; 271 | } 272 | 273 | - (void)registerClass:(Class)Class forCellWithReuseIdentifier:(NSString *)identifier { 274 | [_collectionView registerClass:Class forCellWithReuseIdentifier:identifier]; 275 | } 276 | 277 | - (void)registerNib:(UINib *)nib forCellWithReuseIdentifier:(NSString *)identifier { 278 | [_collectionView registerNib:nib forCellWithReuseIdentifier:identifier]; 279 | } 280 | 281 | - (__kindof UICollectionViewCell *)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndex:(NSInteger)index { 282 | UICollectionViewCell *cell = [_collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:[NSIndexPath indexPathForItem:index inSection:_dequeueSection]]; 283 | return cell; 284 | } 285 | 286 | #pragma mark - configure layout 287 | 288 | - (void)updateLayout { 289 | if (!self.layout) { 290 | return; 291 | } 292 | self.layout.isInfiniteLoop = _isInfiniteLoop; 293 | ((SYACyclePageTransformLayout *)_collectionView.collectionViewLayout).layout = self.layout; 294 | } 295 | 296 | - (void)clearLayout { 297 | if (_needClearLayout) { 298 | _layout = nil; 299 | _needClearLayout = NO; 300 | } 301 | } 302 | 303 | - (void)setNeedClearLayout { 304 | _needClearLayout = YES; 305 | } 306 | 307 | - (void)setNeedUpdateLayout { 308 | if (!self.layout) { 309 | return; 310 | } 311 | [self clearLayout]; 312 | [self updateLayout]; 313 | [_collectionView.collectionViewLayout invalidateLayout]; 314 | [self resetPagerViewAtIndex:_indexSection.index < 0 ? 0 :_indexSection.index]; 315 | } 316 | 317 | #pragma mark - pager index 318 | 319 | - (BOOL)isValidIndexSection:(SYAIndexSection)indexSection { 320 | return indexSection.index >= 0 && indexSection.index < _numberOfItems && indexSection.section >= 0 && indexSection.section < kPageViewMaxSectionCount; 321 | } 322 | 323 | - (SYAIndexSection)nearlyIndexPathAtDirection:(SYAPageScrollDirection)direction{ 324 | return [self nearlyIndexPathForIndexSection:_indexSection direction:direction]; 325 | } 326 | 327 | - (SYAIndexSection)nearlyIndexPathForIndexSection:(SYAIndexSection)indexSection direction:(SYAPageScrollDirection)direction { 328 | if (indexSection.index < 0 || indexSection.index >= _numberOfItems) { 329 | return indexSection; 330 | } 331 | 332 | if (!_isInfiniteLoop) { 333 | if (direction == SYAPageScrollDirectionRight && indexSection.index == _numberOfItems - 1) { 334 | return _autoScrollInterval > 0 ? SYAMakeIndexSection(0, 0) : indexSection; 335 | } else if (direction == SYAPageScrollDirectionRight) { 336 | return SYAMakeIndexSection(indexSection.index+1, 0); 337 | } 338 | 339 | if (indexSection.index == 0) { 340 | return _autoScrollInterval > 0 ? SYAMakeIndexSection(_numberOfItems - 1, 0) : indexSection; 341 | } 342 | return SYAMakeIndexSection(indexSection.index-1, 0); 343 | } 344 | 345 | if (direction == SYAPageScrollDirectionRight) { 346 | if (indexSection.index < _numberOfItems-1) { 347 | return SYAMakeIndexSection(indexSection.index+1, indexSection.section); 348 | } 349 | if (indexSection.section >= kPageViewMaxSectionCount-1) { 350 | return SYAMakeIndexSection(indexSection.index, kPageViewMaxSectionCount-1); 351 | } 352 | return SYAMakeIndexSection(0, indexSection.section+1); 353 | } 354 | 355 | if (indexSection.index > 0) { 356 | return SYAMakeIndexSection(indexSection.index-1, indexSection.section); 357 | } 358 | if (indexSection.section <= 0) { 359 | return SYAMakeIndexSection(indexSection.index, 0); 360 | } 361 | return SYAMakeIndexSection(_numberOfItems-1, indexSection.section-1); 362 | } 363 | 364 | - (SYAIndexSection)caculateIndexSectionWithOffsetX:(CGFloat)offsetX { 365 | if (_numberOfItems <= 0) { 366 | return SYAMakeIndexSection(0, 0); 367 | } 368 | UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)_collectionView.collectionViewLayout; 369 | CGFloat leftEdge = _isInfiniteLoop ? _layout.sectionInset.left : _layout.onlyOneSectionInset.left; 370 | CGFloat width = CGRectGetWidth(_collectionView.frame); 371 | CGFloat middleOffset = offsetX + width/2; 372 | CGFloat itemWidth = layout.itemSize.width + layout.minimumInteritemSpacing; 373 | NSInteger curIndex = 0; 374 | NSInteger curSection = 0; 375 | if (middleOffset - leftEdge >= 0) { 376 | NSInteger itemIndex = (middleOffset - leftEdge+layout.minimumInteritemSpacing/2)/itemWidth; 377 | if (itemIndex < 0) { 378 | itemIndex = 0; 379 | }else if (itemIndex >= _numberOfItems*kPageViewMaxSectionCount) { 380 | itemIndex = _numberOfItems*kPageViewMaxSectionCount-1; 381 | } 382 | curIndex = itemIndex%_numberOfItems; 383 | curSection = itemIndex/_numberOfItems; 384 | } 385 | return SYAMakeIndexSection(curIndex, curSection); 386 | } 387 | 388 | - (CGFloat)caculateOffsetXAtIndexSection:(SYAIndexSection)indexSection{ 389 | if (_numberOfItems == 0) { 390 | return 0; 391 | } 392 | UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)_collectionView.collectionViewLayout; 393 | CGFloat leftEdge = _isInfiniteLoop ? _layout.sectionInset.left : _layout.onlyOneSectionInset.left; 394 | CGFloat width = CGRectGetWidth(_collectionView.frame); 395 | CGFloat itemWidth = layout.itemSize.width + layout.minimumInteritemSpacing; 396 | CGFloat offsetX = leftEdge + itemWidth*(indexSection.index + indexSection.section*_numberOfItems) - layout.minimumInteritemSpacing/2 - (width - itemWidth)/2; 397 | return MAX(offsetX, 0); 398 | } 399 | 400 | - (void)resetPagerViewAtIndex:(NSInteger)index { 401 | if (index < 0) { 402 | return; 403 | } 404 | if (index >= _numberOfItems) { 405 | index = 0; 406 | } 407 | [self scrollToItemAtIndexSection:SYAMakeIndexSection(index, _isInfiniteLoop ? kPageViewMaxSectionCount/3 : 0) animate:NO]; 408 | if (!_isInfiniteLoop && _indexSection.index < 0) { 409 | [self scrollViewDidScroll:_collectionView]; 410 | } 411 | } 412 | 413 | - (void)recyclePagerViewIfNeed { 414 | if (!_isInfiniteLoop) { 415 | return; 416 | } 417 | if (_indexSection.section > kPageViewMaxSectionCount - kPageViewMinSectionCount || _indexSection.section < kPageViewMinSectionCount) { 418 | [self resetPagerViewAtIndex:_indexSection.index]; 419 | } 420 | } 421 | 422 | #pragma mark - UICollectionViewDataSource 423 | 424 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { 425 | return _isInfiniteLoop ? kPageViewMaxSectionCount : 1; 426 | } 427 | 428 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 429 | _numberOfItems = [_dataSource numberOfItemsInPagerView:self]; 430 | return _numberOfItems; 431 | } 432 | 433 | - (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 434 | _dequeueSection = indexPath.section; 435 | if (_dataSourceFlags.cellForItemAtIndex) { 436 | return [_dataSource pagerView:self cellForItemAtIndex:indexPath.row]; 437 | } 438 | NSAssert(NO, @"pagerView cellForItemAtIndex: is nil!"); 439 | return nil; 440 | } 441 | 442 | #pragma mark - UICollectionViewDelegateFlowLayout 443 | 444 | - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { 445 | if (!_isInfiniteLoop) { 446 | return _layout.onlyOneSectionInset; 447 | } 448 | if (section == 0 ) { 449 | return _layout.firstSectionInset; 450 | }else if (section == kPageViewMaxSectionCount -1) { 451 | return _layout.lastSectionInset; 452 | } 453 | return _layout.middleSectionInset; 454 | } 455 | 456 | - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { 457 | UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath]; 458 | if ([_delegate respondsToSelector:@selector(pagerView:didSelectedItemCell:atIndex:)]) { 459 | [_delegate pagerView:self didSelectedItemCell:cell atIndex:indexPath.item]; 460 | } 461 | } 462 | 463 | #pragma mark - UIScrollViewDelegate 464 | 465 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView { 466 | if (!_didLayout) { 467 | return; 468 | } 469 | SYAIndexSection newIndexSection = [self caculateIndexSectionWithOffsetX:scrollView.contentOffset.x]; 470 | if (_numberOfItems <= 0 || ![self isValidIndexSection:newIndexSection]) { 471 | NSLog(@"inVlaidIndexSection:(%ld,%ld)!",(long)newIndexSection.index,(long)newIndexSection.section); 472 | return; 473 | } 474 | SYAIndexSection indexSection = _indexSection; 475 | _indexSection = newIndexSection; 476 | 477 | if (_delegateFlags.pagerViewDidScroll) { 478 | [_delegate pagerViewDidScroll:self]; 479 | } 480 | 481 | if (_delegateFlags.didScrollFromIndexToNewIndex && !SYAEqualIndexSection(_indexSection, indexSection)) { 482 | [_delegate pagerView:self didScrollFromIndex:MAX(indexSection.index, 0) toIndex:_indexSection.index]; 483 | } 484 | } 485 | 486 | - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { 487 | if (_autoScrollInterval > 0) { 488 | [self removeTimer]; 489 | } 490 | _beginDragIndexSection = [self caculateIndexSectionWithOffsetX:scrollView.contentOffset.x]; 491 | if ([_delegate respondsToSelector:@selector(pagerViewWillBeginDragging:)]) { 492 | [_delegate pagerViewWillBeginDragging:self]; 493 | } 494 | } 495 | 496 | - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { 497 | if (fabs(velocity.x) < 0.35 || !SYAEqualIndexSection(_beginDragIndexSection, _indexSection)) { 498 | targetContentOffset->x = [self caculateOffsetXAtIndexSection:_indexSection]; 499 | return; 500 | } 501 | SYAPageScrollDirection direction = SYAPageScrollDirectionRight; 502 | if ((scrollView.contentOffset.x < 0 && targetContentOffset->x <= 0) || (targetContentOffset->x < scrollView.contentOffset.x && scrollView.contentOffset.x < scrollView.contentSize.width - scrollView.frame.size.width)) { 503 | direction = SYAPageScrollDirectionLeft; 504 | } 505 | SYAIndexSection indexSection = [self nearlyIndexPathForIndexSection:_indexSection direction:direction]; 506 | targetContentOffset->x = [self caculateOffsetXAtIndexSection:indexSection]; 507 | } 508 | 509 | - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { 510 | if (_autoScrollInterval > 0) { 511 | [self addTimer]; 512 | } 513 | if ([_delegate respondsToSelector:@selector(pagerViewDidEndDragging:willDecelerate:)]) { 514 | [_delegate pagerViewDidEndDragging:self willDecelerate:decelerate]; 515 | } 516 | } 517 | 518 | - (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView { 519 | if ([_delegate respondsToSelector:@selector(pagerViewWillBeginDecelerating:)]) { 520 | [_delegate pagerViewWillBeginDecelerating:self]; 521 | } 522 | } 523 | 524 | - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { 525 | [self recyclePagerViewIfNeed]; 526 | if ([_delegate respondsToSelector:@selector(pagerViewDidEndDecelerating:)]) { 527 | [_delegate pagerViewDidEndDecelerating:self]; 528 | } 529 | } 530 | 531 | - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView { 532 | [self recyclePagerViewIfNeed]; 533 | if ([_delegate respondsToSelector:@selector(pagerViewDidEndScrollingAnimation:)]) { 534 | [_delegate pagerViewDidEndScrollingAnimation:self]; 535 | } 536 | } 537 | 538 | - (void)pageViewTransformLayout:(SYACyclePageTransformLayout *)pagerViewTransformLayout initializeTransformAttributes:(UICollectionViewLayoutAttributes *)attributes { 539 | if (_delegateFlags.initializeTransformAttributes) { 540 | [_delegate pagerView:self initializeTransformAttributes:attributes]; 541 | } 542 | } 543 | 544 | - (void)pageViewTransformLayout:(SYACyclePageTransformLayout *)pagerViewTransformLayout applyTransformToAttributes:(UICollectionViewLayoutAttributes *)attributes { 545 | if (_delegateFlags.applyTransformToAttributes) { 546 | [_delegate pagerView:self applyTransformToAttributes:attributes]; 547 | } 548 | } 549 | 550 | - (void)layoutSubviews { 551 | [super layoutSubviews]; 552 | BOOL needUpdateLayout = !CGRectEqualToRect(_collectionView.frame, self.bounds); 553 | _collectionView.frame = self.bounds; 554 | if ((_indexSection.section < 0 || needUpdateLayout) && (_numberOfItems > 0 || _didReloadData)) { 555 | _didLayout = YES; 556 | [self setNeedUpdateLayout]; 557 | } 558 | } 559 | 560 | - (void)dealloc { 561 | ((SYACyclePageTransformLayout *)_collectionView.collectionViewLayout).delegate = nil; 562 | _collectionView.delegate = nil; 563 | _collectionView.dataSource = nil; 564 | } 565 | 566 | @end 567 | -------------------------------------------------------------------------------- /SYACyclePageViewDemo/SYACyclePageViewDemo/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 | -------------------------------------------------------------------------------- /SYACyclePageViewDemo/SYACyclePageViewDemo/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 | 49 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 79 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 98 | 104 | 111 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | -------------------------------------------------------------------------------- /SYACyclePageViewDemo/SYACyclePageViewDemo/TYCyclePagerViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // TYCyclePagerViewCell.h 3 | // TYCyclePagerViewDemo 4 | // 5 | // Created by tany on 2017/6/14. 6 | // Copyright © 2017年 tany. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TYCyclePagerViewCell : UICollectionViewCell 12 | 13 | @property (nonatomic, weak, readonly) UILabel *label; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /SYACyclePageViewDemo/SYACyclePageViewDemo/TYCyclePagerViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // TYCyclePagerViewCell.m 3 | // TYCyclePagerViewDemo 4 | // 5 | // Created by tany on 2017/6/14. 6 | // Copyright © 2017年 tany. All rights reserved. 7 | // 8 | 9 | #import "TYCyclePagerViewCell.h" 10 | 11 | @interface TYCyclePagerViewCell () 12 | @property (nonatomic, weak) UILabel *label; 13 | @end 14 | 15 | @implementation TYCyclePagerViewCell 16 | 17 | - (instancetype)initWithFrame:(CGRect)frame { 18 | if (self = [super initWithFrame:frame]) { 19 | self.backgroundColor = [UIColor clearColor]; 20 | [self addLabel]; 21 | } 22 | return self; 23 | } 24 | 25 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 26 | if (self = [super initWithCoder:aDecoder]) { 27 | self.backgroundColor = [UIColor clearColor]; 28 | [self addLabel]; 29 | } 30 | return self; 31 | } 32 | 33 | 34 | - (void)addLabel { 35 | UILabel *label = [[UILabel alloc]init]; 36 | label.textAlignment = NSTextAlignmentCenter; 37 | label.textColor = [UIColor whiteColor]; 38 | label.font = [UIFont systemFontOfSize:18]; 39 | [self addSubview:label]; 40 | _label = label; 41 | } 42 | 43 | - (void)layoutSubviews { 44 | [super layoutSubviews]; 45 | _label.frame = self.bounds; 46 | } 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /SYACyclePageViewDemo/SYACyclePageViewDemo/TYPageControl.h: -------------------------------------------------------------------------------- 1 | // 2 | // TYPageControl.h 3 | // TYCyclePagerViewDemo 4 | // 5 | // Created by tany on 2017/6/20. 6 | // Copyright © 2017年 tany. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface TYPageControl : UIControl 14 | 15 | @property (nonatomic, assign) NSInteger numberOfPages; // default is 0 16 | @property (nonatomic, assign) NSInteger currentPage; // default is 0. value pinned to 0..numberOfPages-1 17 | 18 | @property (nonatomic, assign) BOOL hidesForSinglePage; // hide the the indicator if there is only one page. default is NO 19 | 20 | @property (nonatomic, assign) CGFloat pageIndicatorSpaing; 21 | @property (nonatomic, assign) UIEdgeInsets contentInset; // center will ignore this 22 | @property (nonatomic, assign ,readonly) CGSize contentSize; // real content size 23 | 24 | // override super 25 | //@property (nonatomic, assign) UIControlContentVerticalAlignment contentVerticalAlignment; // how to position content vertically inside control. default is center 26 | //@property (nonatomic, assign) UIControlContentHorizontalAlignment contentHorizontalAlignment; // how to position content hozontally inside control. default is center 27 | 28 | // indicatorTint color 29 | @property (nullable, nonatomic,strong) UIColor *pageIndicatorTintColor; 30 | @property (nullable, nonatomic,strong) UIColor *currentPageIndicatorTintColor; 31 | 32 | // indicator image 33 | @property (nullable, nonatomic,strong) UIImage *pageIndicatorImage; 34 | @property (nullable, nonatomic,strong) UIImage *currentPageIndicatorImage; 35 | 36 | @property (nonatomic, assign) UIViewContentMode indicatorImageContentMode; // default is UIViewContentModeCenter 37 | 38 | @property (nonatomic, assign) CGSize pageIndicatorSize; // indicator size 39 | @property (nonatomic, assign) CGSize currentPageIndicatorSize; // default pageIndicatorSize 40 | 41 | @property (nonatomic, assign) CGFloat animateDuring; // default 0.3 42 | 43 | - (void)setCurrentPage:(NSInteger)currentPage animate:(BOOL)animate; 44 | 45 | @end 46 | 47 | NS_ASSUME_NONNULL_END 48 | -------------------------------------------------------------------------------- /SYACyclePageViewDemo/SYACyclePageViewDemo/TYPageControl.m: -------------------------------------------------------------------------------- 1 | // 2 | // TYPageControl.m 3 | // TYCyclePagerViewDemo 4 | // 5 | // Created by tany on 2017/6/20. 6 | // Copyright © 2017年 tany. All rights reserved. 7 | // 8 | 9 | #import "TYPageControl.h" 10 | 11 | @interface TYPageControl () 12 | // UI 13 | @property (nonatomic, strong) NSArray *indicatorViews; 14 | 15 | // Data 16 | @property (nonatomic, assign) BOOL forceUpdate; 17 | 18 | @end 19 | 20 | @implementation TYPageControl 21 | 22 | #pragma mark - life cycle 23 | 24 | - (instancetype)initWithFrame:(CGRect)frame { 25 | if (self = [super initWithFrame:frame]) { 26 | [self configurePropertys]; 27 | } 28 | return self; 29 | } 30 | 31 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 32 | if (self = [super initWithCoder:aDecoder]) { 33 | [self configurePropertys]; 34 | } 35 | return self; 36 | } 37 | 38 | - (void)configurePropertys { 39 | self.userInteractionEnabled = NO; 40 | _forceUpdate = NO; 41 | _animateDuring = 0.3; 42 | _pageIndicatorSpaing = 10; 43 | _indicatorImageContentMode = UIViewContentModeCenter; 44 | _pageIndicatorSize = CGSizeMake(6,6); 45 | _currentPageIndicatorSize = _pageIndicatorSize; 46 | _pageIndicatorTintColor = [UIColor colorWithRed:128/255. green:128/255. blue:128/255. alpha:1]; 47 | _currentPageIndicatorTintColor = [UIColor whiteColor]; 48 | } 49 | 50 | - (void)willMoveToSuperview:(UIView *)newSuperview { 51 | [super willMoveToSuperview:newSuperview]; 52 | if (newSuperview) { 53 | _forceUpdate = YES; 54 | [self updateIndicatorViews]; 55 | _forceUpdate = NO; 56 | } 57 | } 58 | 59 | #pragma mark - getter setter 60 | 61 | - (CGSize)contentSize { 62 | CGFloat width = (_indicatorViews.count - 1) * (_pageIndicatorSize.width + _pageIndicatorSpaing) + _pageIndicatorSize.width + _contentInset.left +_contentInset.right; 63 | CGFloat height = _currentPageIndicatorSize.height + _contentInset.top + _contentInset.bottom; 64 | return CGSizeMake(width, height); 65 | } 66 | 67 | - (void)setNumberOfPages:(NSInteger)numberOfPages { 68 | if (numberOfPages == _numberOfPages) { 69 | return; 70 | } 71 | _numberOfPages = numberOfPages; 72 | if (_currentPage >= numberOfPages) { 73 | _currentPage = 0; 74 | } 75 | [self updateIndicatorViews]; 76 | if (_indicatorViews.count > 0) { 77 | [self setNeedsLayout]; 78 | } 79 | } 80 | 81 | - (void)setCurrentPage:(NSInteger)currentPage { 82 | if (_currentPage == currentPage || _indicatorViews.count <= currentPage) { 83 | return; 84 | } 85 | _currentPage = currentPage; 86 | if (!CGSizeEqualToSize(_currentPageIndicatorSize, _pageIndicatorSize)) { 87 | [self setNeedsLayout]; 88 | } 89 | [self updateIndicatorViewsBehavior]; 90 | if (self.userInteractionEnabled) { 91 | [self sendActionsForControlEvents:UIControlEventValueChanged]; 92 | } 93 | } 94 | 95 | - (void)setCurrentPage:(NSInteger)currentPage animate:(BOOL)animate { 96 | if (animate) { 97 | [UIView animateWithDuration:_animateDuring animations:^{ 98 | [self setCurrentPage:currentPage]; 99 | }]; 100 | }else { 101 | [self setCurrentPage:currentPage]; 102 | } 103 | } 104 | 105 | - (void)setPageIndicatorImage:(UIImage *)pageIndicatorImage { 106 | _pageIndicatorImage = pageIndicatorImage; 107 | [self updateIndicatorViewsBehavior]; 108 | } 109 | 110 | - (void)setCurrentPageIndicatorImage:(UIImage *)currentPageIndicatorImage { 111 | _currentPageIndicatorImage = currentPageIndicatorImage; 112 | [self updateIndicatorViewsBehavior]; 113 | } 114 | 115 | - (void)setPageIndicatorTintColor:(UIColor *)pageIndicatorTintColor { 116 | _pageIndicatorTintColor = pageIndicatorTintColor; 117 | [self updateIndicatorViewsBehavior]; 118 | } 119 | 120 | - (void)setCurrentPageIndicatorTintColor:(UIColor *)currentPageIndicatorTintColor { 121 | _currentPageIndicatorTintColor = currentPageIndicatorTintColor; 122 | [self updateIndicatorViewsBehavior]; 123 | } 124 | 125 | - (void)setPageIndicatorSize:(CGSize)pageIndicatorSize { 126 | if (CGSizeEqualToSize(_pageIndicatorSize, pageIndicatorSize)) { 127 | return; 128 | } 129 | _pageIndicatorSize = pageIndicatorSize; 130 | if (CGSizeEqualToSize(_currentPageIndicatorSize, CGSizeZero) || (_currentPageIndicatorSize.width < pageIndicatorSize.width && _currentPageIndicatorSize.height < pageIndicatorSize.height)) { 131 | _currentPageIndicatorSize = pageIndicatorSize; 132 | } 133 | if (_indicatorViews.count > 0) { 134 | [self setNeedsLayout]; 135 | } 136 | } 137 | 138 | - (void)setPageIndicatorSpaing:(CGFloat)pageIndicatorSpaing { 139 | _pageIndicatorSpaing = pageIndicatorSpaing; 140 | if (_indicatorViews.count > 0) { 141 | [self setNeedsLayout]; 142 | } 143 | } 144 | 145 | - (void)setCurrentPageIndicatorSize:(CGSize)currentPageIndicatorSize { 146 | if (CGSizeEqualToSize(_currentPageIndicatorSize, currentPageIndicatorSize)) { 147 | return; 148 | } 149 | _currentPageIndicatorSize = currentPageIndicatorSize; 150 | if (_indicatorViews.count > 0) { 151 | [self setNeedsLayout]; 152 | } 153 | } 154 | 155 | - (void)setContentHorizontalAlignment:(UIControlContentHorizontalAlignment)contentHorizontalAlignment { 156 | [super setContentHorizontalAlignment:contentHorizontalAlignment]; 157 | if (_indicatorViews.count > 0) { 158 | [self setNeedsLayout]; 159 | } 160 | } 161 | 162 | - (void)setContentVerticalAlignment:(UIControlContentVerticalAlignment)contentVerticalAlignment { 163 | [super setContentVerticalAlignment:contentVerticalAlignment]; 164 | if (_indicatorViews.count > 0) { 165 | [self setNeedsLayout]; 166 | } 167 | } 168 | 169 | #pragma mark - update indicator 170 | 171 | - (void)updateIndicatorViews { 172 | if (!self.superview && !_forceUpdate) { 173 | return; 174 | } 175 | if (_indicatorViews.count == _numberOfPages) { 176 | [self updateIndicatorViewsBehavior]; 177 | return; 178 | } 179 | NSMutableArray *indicatorViews = _indicatorViews ? [_indicatorViews mutableCopy] :[NSMutableArray array]; 180 | if (indicatorViews.count < _numberOfPages) { 181 | for (NSInteger idx = indicatorViews.count; idx < _numberOfPages; ++idx) { 182 | UIImageView *indicatorView = [[UIImageView alloc]init]; 183 | indicatorView.contentMode = _indicatorImageContentMode; 184 | [self addSubview:indicatorView]; 185 | [indicatorViews addObject:indicatorView]; 186 | } 187 | }else if (indicatorViews.count > _numberOfPages) { 188 | for (NSInteger idx = indicatorViews.count - 1; idx >= _numberOfPages; --idx) { 189 | UIImageView *indicatorView = indicatorViews[idx]; 190 | [indicatorView removeFromSuperview]; 191 | [indicatorViews removeObjectAtIndex:idx]; 192 | } 193 | } 194 | _indicatorViews = [indicatorViews copy]; 195 | [self updateIndicatorViewsBehavior]; 196 | } 197 | 198 | - (void)updateIndicatorViewsBehavior { 199 | if (_indicatorViews.count == 0 || (!self.superview && !_forceUpdate)) { 200 | return; 201 | } 202 | if (_hidesForSinglePage && _indicatorViews.count == 1) { 203 | UIImageView *indicatorView = _indicatorViews.lastObject; 204 | indicatorView.hidden = YES; 205 | } 206 | NSInteger index = 0; 207 | for (UIImageView *indicatorView in _indicatorViews) { 208 | if (_pageIndicatorImage) { 209 | indicatorView.contentMode = _indicatorImageContentMode; 210 | indicatorView.image = _currentPage == index ? _currentPageIndicatorImage : _pageIndicatorImage; 211 | }else { 212 | indicatorView.image = nil; 213 | indicatorView.backgroundColor = _currentPage == index ? _currentPageIndicatorTintColor : _pageIndicatorTintColor; 214 | } 215 | indicatorView.hidden = NO; 216 | ++index; 217 | } 218 | } 219 | 220 | #pragma mark - layout 221 | 222 | - (void)layoutIndicatorViews { 223 | if (_indicatorViews.count == 0) { 224 | return; 225 | } 226 | CGFloat orignX = 0; 227 | CGFloat centerY = 0; 228 | CGFloat pageIndicatorSpaing = _pageIndicatorSpaing; 229 | switch (self.contentHorizontalAlignment) { 230 | case UIControlContentHorizontalAlignmentCenter: 231 | // ignore contentInset 232 | orignX = (CGRectGetWidth(self.frame) - (_indicatorViews.count - 1) * (_pageIndicatorSize.width + _pageIndicatorSpaing) - _pageIndicatorSize.width)/2; 233 | break; 234 | case UIControlContentHorizontalAlignmentLeft: 235 | orignX = _contentInset.left; 236 | break; 237 | case UIControlContentHorizontalAlignmentRight: 238 | orignX = CGRectGetWidth(self.frame) - ((_indicatorViews.count - 1) * (_pageIndicatorSize.width + _pageIndicatorSpaing) - _pageIndicatorSize.width) - _contentInset.right; 239 | break; 240 | case UIControlContentHorizontalAlignmentFill: 241 | orignX = _contentInset.left; 242 | if (_indicatorViews.count > 1) { 243 | pageIndicatorSpaing = (CGRectGetWidth(self.frame) - _contentInset.left - _contentInset.right - _pageIndicatorSize.width - (_indicatorViews.count - 1) * _pageIndicatorSize.width)/(_indicatorViews.count - 1); 244 | } 245 | break; 246 | default: 247 | break; 248 | } 249 | switch (self.contentVerticalAlignment) { 250 | case UIControlContentVerticalAlignmentCenter: 251 | centerY = CGRectGetHeight(self.frame)/2; 252 | break; 253 | case UIControlContentVerticalAlignmentTop: 254 | centerY = _contentInset.top + _currentPageIndicatorSize.height/2; 255 | break; 256 | case UIControlContentVerticalAlignmentBottom: 257 | centerY = CGRectGetHeight(self.frame) - _currentPageIndicatorSize.height/2 - _contentInset.bottom; 258 | break; 259 | case UIControlContentVerticalAlignmentFill: 260 | centerY = (CGRectGetHeight(self.frame) - _contentInset.top - _contentInset.bottom)/2 + _contentInset.top; 261 | break; 262 | default: 263 | break; 264 | } 265 | NSInteger index = 0; 266 | for (UIImageView *indicatorView in _indicatorViews) { 267 | if (_pageIndicatorImage) { 268 | indicatorView.layer.cornerRadius = 0; 269 | }else { 270 | indicatorView.layer.cornerRadius = _currentPage == index ? _currentPageIndicatorSize.width/2 : _pageIndicatorSize.width/2; 271 | } 272 | CGSize size = index == _currentPage ? _currentPageIndicatorSize : _pageIndicatorSize; 273 | indicatorView.frame = CGRectMake(orignX - (size.width - _pageIndicatorSize.width)/2, centerY - size.height/2, size.width, size.height); 274 | orignX += _pageIndicatorSize.width + pageIndicatorSpaing; 275 | ++index; 276 | } 277 | } 278 | 279 | - (void)layoutSubviews { 280 | [super layoutSubviews]; 281 | [self layoutIndicatorViews]; 282 | } 283 | 284 | @end 285 | -------------------------------------------------------------------------------- /SYACyclePageViewDemo/SYACyclePageViewDemo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // TYCyclePagerViewDemo 4 | // 5 | // Created by tany on 2017/6/14. 6 | // Copyright © 2017年 tany. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /SYACyclePageViewDemo/SYACyclePageViewDemo/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // TYCyclePagerViewDemo 4 | // 5 | // Created by tany on 2017/6/14. 6 | // Copyright © 2017年 tany. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "SYACyclePageView.h" 11 | #import "TYPageControl.h" 12 | #import "TYCyclePagerViewCell.h" 13 | 14 | @interface ViewController () 15 | 16 | @property (nonatomic, strong) SYACyclePageView *pagerView; 17 | @property (nonatomic, strong) TYPageControl *pageControl; 18 | @property (nonatomic, strong) NSArray *datas; 19 | 20 | @end 21 | 22 | @implementation ViewController 23 | 24 | - (void)viewDidLoad { 25 | [super viewDidLoad]; 26 | // Do any additional setup after loading the view, typically from a nib. 27 | self.title = @"TYCyclePagerView"; 28 | [self addPagerView]; 29 | [self addPageControl]; 30 | 31 | [self loadData]; 32 | } 33 | 34 | - (void)addPagerView { 35 | SYACyclePageView *pagerView = [[SYACyclePageView alloc]init]; 36 | pagerView.layer.borderWidth = 1; 37 | pagerView.isInfiniteLoop = YES; 38 | pagerView.autoScrollInterval = 3.0; 39 | pagerView.dataSource = self; 40 | pagerView.delegate = self; 41 | pagerView.layout.layoutType = SYAyclePageTransformLayoutLinear; 42 | // registerClass or registerNib 43 | [pagerView registerClass:[TYCyclePagerViewCell class] forCellWithReuseIdentifier:@"cellId"]; 44 | [self.view addSubview:pagerView]; 45 | _pagerView = pagerView; 46 | } 47 | 48 | - (void)addPageControl { 49 | TYPageControl *pageControl = [[TYPageControl alloc]init]; 50 | //pageControl.numberOfPages = _datas.count; 51 | pageControl.currentPageIndicatorSize = CGSizeMake(8, 8); 52 | // pageControl.pageIndicatorImage = [UIImage imageNamed:@"Dot"]; 53 | // pageControl.currentPageIndicatorImage = [UIImage imageNamed:@"DotSelected"]; 54 | // pageControl.contentInset = UIEdgeInsetsMake(0, 20, 0, 20); 55 | // pageControl.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter; 56 | // pageControl.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter; 57 | // [pageControl addTarget:self action:@selector(pageControlValueChangeAction:) forControlEvents:UIControlEventValueChanged]; 58 | [_pagerView addSubview:pageControl]; 59 | _pageControl = pageControl; 60 | } 61 | 62 | - (void)viewWillLayoutSubviews { 63 | [super viewWillLayoutSubviews]; 64 | _pagerView.frame = CGRectMake(0, 64, CGRectGetWidth(self.view.frame), 200); 65 | _pageControl.frame = CGRectMake(0, CGRectGetHeight(_pagerView.frame) - 26, CGRectGetWidth(_pagerView.frame), 26); 66 | } 67 | 68 | - (void)loadData { 69 | NSMutableArray *datas = [NSMutableArray array]; 70 | for (int i = 0; i < 7; ++i) { 71 | if (i == 0) { 72 | [datas addObject:[UIColor redColor]]; 73 | continue; 74 | } 75 | [datas addObject:[UIColor colorWithRed:arc4random()%255/255.0 green:arc4random()%255/255.0 blue:arc4random()%255/255.0 alpha:arc4random()%255/255.0]]; 76 | } 77 | _datas = [datas copy]; 78 | _pageControl.numberOfPages = _datas.count; 79 | [_pagerView reloadData]; 80 | } 81 | 82 | //- (void)loadData { 83 | // dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 84 | // NSMutableArray *datas = [NSMutableArray array]; 85 | // for (int i = 0; i < 5; ++i) { 86 | // if (i == 0) { 87 | // [datas addObject:[UIColor redColor]]; 88 | // continue; 89 | // } 90 | // [datas addObject:[UIColor colorWithRed:arc4random()%255/255.0 green:arc4random()%255/255.0 blue:arc4random()%255/255.0 alpha:arc4random()%255/255.0]]; 91 | // } 92 | // _datas = [datas copy]; 93 | // _pageControl.numberOfPages = _datas.count; 94 | // [_pagerView reloadData]; 95 | // }); 96 | //} 97 | 98 | #pragma mark - TYCyclePagerViewDataSource 99 | 100 | - (NSInteger)numberOfItemsInPagerView:(SYACyclePageView *)pageView { 101 | return _datas.count; 102 | } 103 | 104 | - (UICollectionViewCell *)pagerView:(SYACyclePageView *)pagerView cellForItemAtIndex:(NSInteger)index { 105 | TYCyclePagerViewCell *cell = [pagerView dequeueReusableCellWithReuseIdentifier:@"cellId" forIndex:index]; 106 | // cell.backgroundColor = _datas[index]; 107 | 108 | cell.backgroundColor = [UIColor orangeColor]; 109 | 110 | cell.label.text = [NSString stringWithFormat:@"index->%ld",index]; 111 | 112 | cell.layer.shadowColor = [UIColor blackColor].CGColor; 113 | cell.layer.shadowOffset = CGSizeMake(2,2); 114 | cell.layer.shadowRadius = 2.0f; 115 | cell.layer.shadowOpacity = 0.4f; 116 | cell.layer.masksToBounds = NO; 117 | cell.layer.shadowPath = [UIBezierPath bezierPathWithRoundedRect:cell.bounds cornerRadius:cell.contentView.layer.cornerRadius].CGPath; 118 | 119 | cell.layer.cornerRadius = 7; 120 | cell.contentView.layer.cornerRadius = 7.0f; 121 | cell.contentView.layer.borderWidth = 0.7f; 122 | cell.contentView.layer.borderColor = [UIColor clearColor].CGColor; 123 | cell.contentView.layer.masksToBounds = YES; 124 | return cell; 125 | } 126 | 127 | - (SYACyclePageViewLayout *)layoutForPagerView:(SYACyclePageView *)pageView { 128 | SYACyclePageViewLayout *layout = [[SYACyclePageViewLayout alloc]init]; 129 | layout.itemSize = CGSizeMake(CGRectGetWidth(pageView.frame)*0.8, CGRectGetHeight(pageView.frame)*0.8); 130 | layout.itemSpacing = 15; 131 | //layout.minimumAlpha = 0.3; 132 | layout.itemHorizontalCenter = YES; 133 | return layout; 134 | } 135 | 136 | - (void)pagerView:(SYACyclePageView *)pageView didScrollFromIndex:(NSInteger)fromIndex toIndex:(NSInteger)toIndex { 137 | _pageControl.currentPage = toIndex; 138 | //[_pageControl setCurrentPage:newIndex animate:YES]; 139 | NSLog(@"%ld -> %ld",fromIndex,toIndex); 140 | } 141 | 142 | #pragma mark - action 143 | 144 | - (IBAction)switchValueChangeAction:(UISwitch *)sender { 145 | if (sender.tag == 0) { 146 | _pagerView.isInfiniteLoop = sender.isOn; 147 | [_pagerView updateData]; 148 | }else if (sender.tag == 1) { 149 | _pagerView.autoScrollInterval = sender.isOn ? 3.0:0; 150 | }else if (sender.tag == 2) { 151 | _pagerView.layout.itemHorizontalCenter = sender.isOn; 152 | [UIView animateWithDuration:0.3 animations:^{ 153 | [_pagerView setNeedUpdateLayout]; 154 | }]; 155 | } 156 | } 157 | 158 | - (IBAction)sliderValueChangeAction:(UISlider *)sender { 159 | if (sender.tag == 0) { 160 | _pagerView.layout.itemSize = CGSizeMake(CGRectGetWidth(_pagerView.frame)*sender.value, CGRectGetHeight(_pagerView.frame)*sender.value); 161 | [_pagerView setNeedUpdateLayout]; 162 | }else if (sender.tag == 1) { 163 | _pagerView.layout.itemSpacing = 30*sender.value; 164 | [_pagerView setNeedUpdateLayout]; 165 | }else if (sender.tag == 2) { 166 | _pageControl.pageIndicatorSize = CGSizeMake(6*(1+sender.value), 6*(1+sender.value)); 167 | _pageControl.currentPageIndicatorSize = CGSizeMake(8*(1+sender.value), 8*(1+sender.value)); 168 | _pageControl.pageIndicatorSpaing = (1+sender.value)*10; 169 | } 170 | } 171 | 172 | - (IBAction)buttonAction:(UIButton *)sender { 173 | _pagerView.layout.layoutType = sender.tag; 174 | [_pagerView setNeedUpdateLayout]; 175 | } 176 | 177 | - (void)pageControlValueChangeAction:(TYPageControl *)sender { 178 | NSLog(@"pageControlValueChangeAction: %ld",sender.currentPage); 179 | } 180 | 181 | - (void)didReceiveMemoryWarning { 182 | [super didReceiveMemoryWarning]; 183 | // Dispose of any resources that can be recreated. 184 | } 185 | 186 | 187 | @end 188 | -------------------------------------------------------------------------------- /SYACyclePageViewDemo/SYACyclePageViewDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // SYACyclePageViewDemo 4 | // 5 | // Created by zq on 2017/12/13. 6 | // Copyright © 2017年 www.lejiakeji.com. 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 | --------------------------------------------------------------------------------