diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..747a47f --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2015 + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index 72eb3aa..edb53a7 100644 --- a/README.md +++ b/README.md @@ -1,252 +1,24 @@ # WKPagesCollectionView -我尝试想做一个类似 iOS7 下的 safari tabs 页面那样的效果。 +An implementation of the tab switcher as seen in Safari for iOS (> 7.0) using UICollectionViews. -* 有页面翻转的效果; -* 点击一个页面变成正常的显示状态; -* 往左划动会删除这个cell; -* 可以在底部添加新的cell; +Features: +* Page flipping tranform +* Tap on a page to select it +* Swipe the cell to the left to delete it +* New cells can be added at the bottom -[点击查看效果视频](http://v.youku.com/v_show/id_XNzAzNDg4OTQ4.html) +[Video](http://v.youku.com/v_show/id_XNzAzNDg4OTQ4.html) -![效果视频](http://farm4.staticflickr.com/3829/11171831814_9c5972bbe6_z.jpg)] +## Usage +### CocoaPods + pod 'WKPagesCollectionView', :git => 'git@github.com:NextFaze/WKPagesCollectionView.git' +### Manually +Add `WKPagesCollectionView` folder and the following files inside it to project :`WK.h`, `WKPagesCollectionView.h`, `WKPagesCollectionView.m`, `WKPagesCollectionViewCell.h`, `WKPagesCollectionViewCell.m`, `WKPagesCollectionViewFlowLayout.h`, `WKPagesCollectionViewFlowLayout.m`; -##使用 -* 项目中添加 WKPagesCollectionView目录以及下面的`WK.h`, `WKPagesCollectionView.h`, `WKPagesCollectionView.m`, `WKPagesCollectionViewCell.h`, `WKPagesCollectionViewCell.m`, `WKPagesCollectionViewFlowLayout.h`, `WKPagesCollectionViewFlowLayout.m`; -* 引用 `WKPagesCollectionView`; -* 准备数据 +## License - _array=[[NSMutableArray alloc]init]; - for (int a=0; a<=30; a++) { - [_array addObject:[NSString stringWithFormat:@"button %d",a]]; - } +WKPagesCollectionView is licensed under the terms of the [Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html). Please see the [LICENSE](/LICENSE.txt) file for full details. -* 创建collectionView - - _collectionView=[[[WKPagesCollectionView alloc]initWithPagesFlowLayoutAndFrame:CGRectMake(0.0f, 0.0f, self.view.frame.size.width, self.view.frame.size.height)] autorelease]; - _collectionView.dataSource=self; - _collectionView.delegate=self; - [_collectionView registerClass:[WKPagesCollectionViewCell class] forCellWithReuseIdentifier:@"cell"]; - [self.view addSubview:_collectionView]; - _collectionView.maskShow=YES; - - -* 完成WKPagesCollectionViewDataSource(继承自UICollectionViewDataSource) 和 WKPagesCollectionViewDelegate(继承自UICollectionViewDelegate), 除了要完成UICollectionViewDataSource中提供数据的方法外,还要完成追加数据的方法`-(void)willAppendItemInCollectionView:(WKPagesCollectionView *)collectionView` -和删除数据的方法` --(void)collectionView:(WKPagesCollectionView *)collectionView willRemoveCellAtNSIndexPath:(NSIndexPath *)indexPath` - - #pragma mark - UICollectionViewDataSource and UICollectionViewDelegate - -(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{ - return _array.count; - } - -(UICollectionViewCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{ - // NSLog(@"cellForItemAtIndexPath:%d",indexPath.row); - static NSString* identity=@"cell"; - WKPagesCollectionViewCell* cell=(WKPagesCollectionViewCell*)[collectionView dequeueReusableCellWithReuseIdentifier:identity forIndexPath:indexPath]; - cell.collectionView=collectionView; - UIImageView* imageView=[[[UIImageView alloc]initWithImage:[UIImage imageNamed:@"image-0"]] autorelease]; - imageView.frame=self.view.bounds; - [cell.cellContentView addSubview:imageView]; - UIButton* button=[UIButton buttonWithType:UIButtonTypeCustom]; - button.frame=CGRectMake(0, (indexPath.row+1)*10+100, 320, 50.0f); - button.backgroundColor=[UIColor whiteColor]; - [button setTitleColor:[UIColor darkGrayColor] forState:UIControlStateNormal]; - [button setTitle:_array[indexPath.row] forState:UIControlStateNormal]; - [button addTarget:self action:@selector(onButtonTitle:) forControlEvents:UIControlEventTouchUpInside]; - [cell.cellContentView addSubview:button]; - return cell; - } - #pragma mark WKPagesCollectionViewDataSource - ///追加数据 - -(void)willAppendItemInCollectionView:(WKPagesCollectionView *)collectionView{ - [_array addObject:@"new button"]; - } - ///删除数据 - -(void)collectionView:(WKPagesCollectionView *)collectionView willRemoveCellAtNSIndexPath:(NSIndexPath *)indexPath{ - [_array removeObjectAtIndex:indexPath.row]; - } - -* 添加一个页面时的操作 - - -(IBAction)onButtonAdd:(id)sender{ - [_collectionView appendItem]; - } - -* 展开显示一个具体的页面,这个在点击页面的时候会自动实现,也可以像下面这样代码调用; - - [_collectionView showCellToHighLightAtIndexPath:indexPath completion:^(BOOL finished) { - NSLog(@"highlight completed"); - }]; - -* 停止展开,回到普通的滚动模式 - - -(IBAction)onButtonTitle:(id)sender{ - NSLog(@"button"); - [_collectionView dismissFromHightLightWithCompletion:^(BOOL finished) { - NSLog(@"dismiss completed"); - }]; - } - -##TODO -* `bug` ~~每滚动几个时候顶上的那一个就会先看不见然后又突然出现了,还没想到原因~~ 这个问题已经解决,@Nikolay Abelyashev 修复了这个bug,原因是由于WKPagesCollectionView的高度太小,而UICollectionView会把屏幕外的cell不在显示,在WKPagesCollectionView中,屏幕外的3个cell会被取消显示,解决的办法是修改WKPagesCollectionView的frame,使他高出window(这里添加了 topOfScreen:120.0f),然后把frame.origin.y也往上移动了那么多距离,这样WKPagesCollectionView其实就更大; - - - -##实现的方式 -###实现滚动 - -我使用了UICollectionView来实现,本质上是一个垂直的列表,而主要的工作是来创造一个CollectionViewLayout, (我定义为WKPagesCollectionViewFlowLayout)每一个cell其实是和当前屏幕一样大小的,也就是说其实就是有一堆和屏幕一样大的cell错开折叠在一起,他们之间的间隔设置为`self.minimumLineSpacing=-1*(self.itemSize.height-160.0f);`。 -![不翻转cell时](http://farm6.staticflickr.com/5521/11171968153_7a7aeb5893_z.jpg) - -为了实现翻转的效果,我在WKPagesCollectionViewFlowLayout中的 -`-(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect` 中修改了transform3D。 - -看上去貌似比较简单,而如果所有的页面都是固定的角度的话,的确也没问题,但是我想像safari里那样在滚动时有点视差的效果,所以就为每个页面设置了不同的翻转角度了,其实就是在layoutAttributesForElementsInRect 中根据每个cell的位置来计算角度使得他们在滚动条滚动时有点不同的角度,下面这个是设置角度的方法: - - - -(void)makeRotateTransformForAttributes:(UICollectionViewLayoutAttributes*)attributes{ - attributes.zIndex=attributes.indexPath.row;///要设置zIndex,否则遮挡顺序会有编号 - CGFloat distance=attributes.frame.origin.y-self.collectionView.contentOffset.y; - CGFloat normalizedDistance = distance / self.collectionView.frame.size.height; - normalizedDistance=fmaxf(normalizedDistance, 0.0f); - CGFloat rotate=RotateDegree+20.0f*normalizedDistance; - //CGFloat rotate=RotateDegree; - NSLog(@"makeRotateTransformForAttributes:row:%d,normalizedDistance:%f,rotate:%f", - attributes.indexPath.row,normalizedDistance,rotate); - ///角度大的会和角度小的cell交叉,即使设置zIndex也没有用,这里设置底部的cell角度越来越大 - CATransform3D rotateTransform=WKFlipCATransform3DPerspectSimpleWithRotate(rotate); - attributes.transform3D=rotateTransform; - - } - -``` --(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)path -{ - NSLog(@"layoutAttributesForItemAtIndexPath:%d",path.row); - UICollectionViewLayoutAttributes* attributes=[super layoutAttributesForItemAtIndexPath:path]; - [self makeRotateTransformForAttributes:attributes]; - return attributes; -} --(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect -{ - NSLog(@"layoutAttributesForElementsInRect:%@",NSStringFromCGRect(rect)); - NSArray* array = [super layoutAttributesForElementsInRect:rect]; - for (UICollectionViewLayoutAttributes* attributes in array) { - [self makeRotateTransformForAttributes:attributes]; - } - return array; -} -``` - -现在运行的时候,滚动起来就和想要的效果差不多了,虽然不如safari那么细节完美,大概的意思是达到了。 - -##实现删除 - -后面我想做出safari那样按住其中一个cell往左滑动就删除的效果,在每个cell里面添加一个scrollView,然后scrollViewDidEndDragging 中达到一定距离的时候就触发删除好了,而UICollectionView中的performBatchUpdates就可以很好的完成删除的动画了。 - -![删除时的效果](http://farm4.staticflickr.com/3831/11171811316_c681d80cc2_z.jpg) - - -(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{ - if (self.showingState==WKPagesCollectionViewCellShowingStateNormal){ - if (scrollView.contentOffset.x>=90.0f){ - NSIndexPath* indexPath=[self.collectionView indexPathForCell:self]; - NSLog(@"delete cell at %d",indexPath.row); - //self.alpha=0.0f; - ///删除数据 - id pagesDataSource=(id)self.collectionView.dataSource; - [pagesDataSource collectionView:(WKPagesCollectionView*)self.collectionView willRemoveCellAtNSIndexPath:indexPath]; - ///动画 - [self.collectionView performBatchUpdates:^{ - [self.collectionView deleteItemsAtIndexPaths:@[indexPath,]]; - } completion:^(BOOL finished) { - - }]; - } - } - } - -为了添加和删除的时候动画的好看一点,我们还得修改 -`(UICollectionViewLayoutAttributes*)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath` 和 `-(UICollectionViewLayoutAttributes*)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath` - -我在WKPagesCollectionViewFlowLayout 中添加了insertIndexPaths 和 deleteIndexPaths 来记录用来添加和删除的位置,因为这个两个回调在添加和删除时会被调用,而且不仅仅是针对正在添加或者删除的NSIndexPath,其他行也会被调用,而我们这里只要处理正在添加和删除的NSIndexPath; - - -(UICollectionViewLayoutAttributes*)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath{ - UICollectionViewLayoutAttributes* attributes=[super initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath]; - NSLog(@"initialLayoutAttributesForAppearingItemAtIndexPath:%d",itemIndexPath.row); - if ([self.insertIndexPaths containsObject:itemIndexPath]){ - if (!attributes) - attributes=[self layoutAttributesForItemAtIndexPath:itemIndexPath]; - CATransform3D rotateTransform=WKFlipCATransform3DPerspectSimpleWithRotate(-90.0f); - attributes.transform3D=rotateTransform; - } - return attributes; - } - -(UICollectionViewLayoutAttributes*)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath{ - NSLog(@"finalLayoutAttributesForDisappearingItemAtIndexPath:%d",itemIndexPath.row); - UICollectionViewLayoutAttributes* attributes=[super finalLayoutAttributesForDisappearingItemAtIndexPath:itemIndexPath]; - if ([self.deleteIndexPaths containsObject:itemIndexPath]){ - if (!attributes){ - attributes=[self layoutAttributesForItemAtIndexPath:itemIndexPath]; - } - CATransform3D moveTransform=CATransform3DMakeTranslation(-320.0f, 0.0f, 0.0f); - attributes.transform3D=CATransform3DConcat(attributes.transform3D, moveTransform); - } - return attributes; - - } - - -开始的时候会发生一些意想不到的动画效果,滚动到底部,然后在模拟器下按下command+t打开慢速动画的时候就看的很清楚了,往左滑动来删除最后两个cell,在删除时一开始的效果是正常的,而几乎在完成之后,会看到一闪,出现了两个cell在很奇怪的位置,然后又动画慢慢回到预订的位置。 - -现在的问题就是,如果我删除带有button-0,button-1,button-2,button-3这样的cell,动画是正常的,但是如果我删除最后几个cell,带有button-6,button-7,button-8的,就会出现意想不到的动画。 - -而且我发现如果我的cell的翻转角度如果是全部固定的,那在删除cell时是不会发生奇怪的动画的,我的makeRotateTransformForAttributes2中是指定了一个固定的角度。 - -####Fixed - -后来终于知道问题在哪里了,只是由于contentSize计算错误导致内容区域不够所以在删除cell后又自动滚动时产生了奇怪的行为,所以`-(CGSize)collectionViewContentSize`中的高度一定要正确。 - -###实现添加页面 - -现在页面有两种状态,一种就是这种普通的滚动页面的状态,当点击某一个页面的时候,他会翻转到全屏显示,这时我称作是`highlight`。如果只是在普通的滚动状态下,会先滚动到屏幕地步,然后添加一个页面,之后又会把这个页面展开到highLight显示状态。而如果现在整个collectionView本身就已经有一个页面在hightLight了,那应该先退回到普通状态,再重复之前的添加页面过程。 - -下面是在WKPagesCollectionView中的添加页面的方法; - - ///追加一个页面 - -(void)appendItem{ - if (self.isHighLight){ - [self dismissFromHightLightWithCompletion:^(BOOL finished) { - [self _addNewPage]; - }]; - } - else{ - [self _addNewPage]; - } - } - ///添加一页 - -(void)_addNewPage{ - int total=[self numberOfItemsInSection:0]; - [self scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:total-1 inSection:0] atScrollPosition:UICollectionViewScrollPositionBottom animated:YES]; - double delayInSeconds = 0.3f; - dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); - dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ - ///添加数据 - [(id)self.dataSource willAppendItemInCollectionView:self]; - int lastRow=total; - NSIndexPath* insertIndexPath=[NSIndexPath indexPathForItem:lastRow inSection:0]; - [self performBatchUpdates:^{ - [self insertItemsAtIndexPaths:@[insertIndexPath]]; - } completion:^(BOOL finished) { - double delayInSeconds = 0.3f; - dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); - dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ - [self showCellToHighLightAtIndexPath:insertIndexPath completion:^(BOOL finished) { - - }]; - }); - - }]; - }); - } diff --git a/WKPagesCollectionView.podspec b/WKPagesCollectionView.podspec new file mode 100644 index 0000000..77cc84f --- /dev/null +++ b/WKPagesCollectionView.podspec @@ -0,0 +1,15 @@ +Pod::Spec.new do |s| + s.name = 'WKPagesCollectionView' + s.version = '0.1.0' + s.summary = 'A collection view that mimics the tab switcher in Safari for iOS 7.' + s.description = 'WKPagesCollectionView mimics the transformed list of tabs as seen in Safari for iOS 7. It has support for animating in and out the tabs, as well as adding and deleting tabs.' + s.homepage = 'https://github.com/adow/WKPagesCollectionView' + s.screenshots = 'https://camo.githubusercontent.com/1fe6654948c6aa9a68d80c65ba3019344e25e071/687474703a2f2f6661726d342e737461746963666c69636b722e636f6d2f333832392f31313137313833313831345f396335393732626265365f7a2e6a7067' + s.license = 'unknown' + s.author = { 'adow' => 'reynoldqin@gmail.com' } + s.platform = :ios, '7.0' + s.source = { :git => 'https://github.com/adow/WKPagesCollectionView.git' } + s.source_files = 'WKPagesScrollView/WKPagesCollectionView/*.{h,m}' + s.frameworks = 'QuartzCore', 'CoreGraphics' + s.requires_arc = false +end diff --git a/WKPagesCollectionView.xcodeproj/project.pbxproj b/WKPagesCollectionView.xcodeproj/project.pbxproj index 22f2173..3b8640a 100644 --- a/WKPagesCollectionView.xcodeproj/project.pbxproj +++ b/WKPagesCollectionView.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 58C2EC3E1A2F223100583CDE /* WKCloseButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 58C2EC3D1A2F223100583CDE /* WKCloseButton.m */; }; 68A1CF27184CD0DB00346DF8 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 68A1CF26184CD0DB00346DF8 /* README.md */; }; 68BDCE7C18432CCA00DB81A2 /* WKPagesCollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 68BDCE7718432CCA00DB81A2 /* WKPagesCollectionView.m */; }; 68BDCE7D18432CCA00DB81A2 /* WKPagesCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 68BDCE7918432CCA00DB81A2 /* WKPagesCollectionViewCell.m */; }; @@ -20,23 +21,12 @@ 68D7C1E618347A08006B418C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 68D7C1E418347A08006B418C /* Main.storyboard */; }; 68D7C1E918347A08006B418C /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 68D7C1E818347A08006B418C /* ViewController.m */; }; 68D7C1EB18347A08006B418C /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 68D7C1EA18347A08006B418C /* Images.xcassets */; }; - 68D7C1F218347A08006B418C /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 68D7C1F118347A08006B418C /* XCTest.framework */; }; - 68D7C1F318347A08006B418C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 68D7C1D218347A08006B418C /* Foundation.framework */; }; - 68D7C1F418347A08006B418C /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 68D7C1D618347A08006B418C /* UIKit.framework */; }; 68D7C20E18349B4C006B418C /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 68D7C20D18349B4C006B418C /* QuartzCore.framework */; }; /* End PBXBuildFile section */ -/* Begin PBXContainerItemProxy section */ - 68D7C1F518347A08006B418C /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 68D7C1C718347A08006B418C /* Project object */; - proxyType = 1; - remoteGlobalIDString = 68D7C1CE18347A08006B418C; - remoteInfo = WKPagesScrollView; - }; -/* End PBXContainerItemProxy section */ - /* Begin PBXFileReference section */ + 58C2EC3C1A2F223100583CDE /* WKCloseButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WKCloseButton.h; sourceTree = ""; }; + 58C2EC3D1A2F223100583CDE /* WKCloseButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WKCloseButton.m; sourceTree = ""; }; 68A1CF26184CD0DB00346DF8 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README.md; sourceTree = SOURCE_ROOT; }; 68BDCE7518432CCA00DB81A2 /* WK.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WK.h; sourceTree = ""; }; 68BDCE7618432CCA00DB81A2 /* WKPagesCollectionView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WKPagesCollectionView.h; sourceTree = ""; }; @@ -59,7 +49,6 @@ 68D7C1E718347A08006B418C /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 68D7C1E818347A08006B418C /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 68D7C1EA18347A08006B418C /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; - 68D7C1F018347A08006B418C /* WKPagesCollectionViewTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = WKPagesCollectionViewTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 68D7C1F118347A08006B418C /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 68D7C20D18349B4C006B418C /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ @@ -76,16 +65,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 68D7C1ED18347A08006B418C /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 68D7C1F218347A08006B418C /* XCTest.framework in Frameworks */, - 68D7C1F418347A08006B418C /* UIKit.framework in Frameworks */, - 68D7C1F318347A08006B418C /* Foundation.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -99,6 +78,8 @@ 68BDCE7918432CCA00DB81A2 /* WKPagesCollectionViewCell.m */, 68BDCE7A18432CCA00DB81A2 /* WKPagesCollectionViewFlowLayout.h */, 68BDCE7B18432CCA00DB81A2 /* WKPagesCollectionViewFlowLayout.m */, + 58C2EC3C1A2F223100583CDE /* WKCloseButton.h */, + 58C2EC3D1A2F223100583CDE /* WKCloseButton.m */, ); path = WKPagesCollectionView; sourceTree = ""; @@ -106,6 +87,7 @@ 68D7C1C618347A08006B418C = { isa = PBXGroup; children = ( + 68A1CF26184CD0DB00346DF8 /* README.md */, 68D7C1D818347A08006B418C /* WKPagesCollectionView */, 68D7C1D118347A08006B418C /* Frameworks */, 68D7C1D018347A08006B418C /* Products */, @@ -116,7 +98,6 @@ isa = PBXGroup; children = ( 68D7C1CF18347A08006B418C /* WKPagesCollectionView.app */, - 68D7C1F018347A08006B418C /* WKPagesCollectionViewTests.xctest */, ); name = Products; sourceTree = ""; @@ -136,7 +117,6 @@ 68D7C1D818347A08006B418C /* WKPagesCollectionView */ = { isa = PBXGroup; children = ( - 68A1CF26184CD0DB00346DF8 /* README.md */, 68D7C1E118347A08006B418C /* AppDelegate.h */, 68D7C1E218347A08006B418C /* AppDelegate.m */, 68D7C1E418347A08006B418C /* Main.storyboard */, @@ -181,37 +161,14 @@ productReference = 68D7C1CF18347A08006B418C /* WKPagesCollectionView.app */; productType = "com.apple.product-type.application"; }; - 68D7C1EF18347A08006B418C /* WKPagesCollectionViewTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 68D7C20418347A08006B418C /* Build configuration list for PBXNativeTarget "WKPagesCollectionViewTests" */; - buildPhases = ( - 68D7C1EC18347A08006B418C /* Sources */, - 68D7C1ED18347A08006B418C /* Frameworks */, - 68D7C1EE18347A08006B418C /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 68D7C1F618347A08006B418C /* PBXTargetDependency */, - ); - name = WKPagesCollectionViewTests; - productName = WKPagesScrollViewTests; - productReference = 68D7C1F018347A08006B418C /* WKPagesCollectionViewTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 68D7C1C718347A08006B418C /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0500; + LastUpgradeCheck = 0610; ORGANIZATIONNAME = "秦 道平"; - TargetAttributes = { - 68D7C1EF18347A08006B418C = { - TestTargetID = 68D7C1CE18347A08006B418C; - }; - }; }; buildConfigurationList = 68D7C1CA18347A08006B418C /* Build configuration list for PBXProject "WKPagesCollectionView" */; compatibilityVersion = "Xcode 3.2"; @@ -227,7 +184,6 @@ projectRoot = ""; targets = ( 68D7C1CE18347A08006B418C /* WKPagesCollectionView */, - 68D7C1EF18347A08006B418C /* WKPagesCollectionViewTests */, ); }; /* End PBXProject section */ @@ -244,13 +200,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 68D7C1EE18347A08006B418C /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -261,29 +210,15 @@ 68D7C1E918347A08006B418C /* ViewController.m in Sources */, 68D7C1E318347A08006B418C /* AppDelegate.m in Sources */, 68BDCE7C18432CCA00DB81A2 /* WKPagesCollectionView.m in Sources */, + 58C2EC3E1A2F223100583CDE /* WKCloseButton.m in Sources */, 68D7C1DF18347A08006B418C /* main.m in Sources */, 68BDCE7E18432CCA00DB81A2 /* WKPagesCollectionViewFlowLayout.m in Sources */, 68BDCE7D18432CCA00DB81A2 /* WKPagesCollectionViewCell.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - 68D7C1EC18347A08006B418C /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXSourcesBuildPhase section */ -/* Begin PBXTargetDependency section */ - 68D7C1F618347A08006B418C /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 68D7C1CE18347A08006B418C /* WKPagesCollectionView */; - targetProxy = 68D7C1F518347A08006B418C /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - /* Begin PBXVariantGroup section */ 68D7C1DB18347A08006B418C /* InfoPlist.strings */ = { isa = PBXVariantGroup; @@ -308,7 +243,6 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; @@ -347,7 +281,6 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; @@ -381,7 +314,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; - CLANG_ENABLE_OBJC_ARC = NO; + CLANG_ENABLE_OBJC_ARC = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "WKPagesScrollView/WKPagesCollectionView-Prefix.pch"; INFOPLIST_FILE = "WKPagesScrollView/WKPagesCollectionView-Info.plist"; @@ -395,7 +328,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; - CLANG_ENABLE_OBJC_ARC = NO; + CLANG_ENABLE_OBJC_ARC = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "WKPagesScrollView/WKPagesCollectionView-Prefix.pch"; INFOPLIST_FILE = "WKPagesScrollView/WKPagesCollectionView-Info.plist"; @@ -404,48 +337,6 @@ }; name = Release; }; - 68D7C20518347A08006B418C /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; - BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/WKPagesScrollView.app/WKPagesScrollView"; - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - "$(DEVELOPER_FRAMEWORKS_DIR)", - ); - GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "WKPagesScrollView/WKPagesScrollView-Prefix.pch"; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - INFOPLIST_FILE = "WKPagesScrollViewTests/WKPagesScrollViewTests-Info.plist"; - PRODUCT_NAME = WKPagesCollectionViewTests; - TEST_HOST = "$(BUNDLE_LOADER)"; - WRAPPER_EXTENSION = xctest; - }; - name = Debug; - }; - 68D7C20618347A08006B418C /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; - BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/WKPagesScrollView.app/WKPagesScrollView"; - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - "$(DEVELOPER_FRAMEWORKS_DIR)", - ); - GCC_PRECOMPILE_PREFIX_HEADER = YES; - GCC_PREFIX_HEADER = "WKPagesScrollView/WKPagesScrollView-Prefix.pch"; - INFOPLIST_FILE = "WKPagesScrollViewTests/WKPagesScrollViewTests-Info.plist"; - PRODUCT_NAME = WKPagesCollectionViewTests; - TEST_HOST = "$(BUNDLE_LOADER)"; - WRAPPER_EXTENSION = xctest; - }; - name = Release; - }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -467,15 +358,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 68D7C20418347A08006B418C /* Build configuration list for PBXNativeTarget "WKPagesCollectionViewTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 68D7C20518347A08006B418C /* Debug */, - 68D7C20618347A08006B418C /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; /* End XCConfigurationList section */ }; rootObject = 68D7C1C718347A08006B418C /* Project object */; diff --git a/WKPagesScrollView/AppDelegate.h b/WKPagesScrollView/AppDelegate.h index 6dde7d4..cd3115f 100644 --- a/WKPagesScrollView/AppDelegate.h +++ b/WKPagesScrollView/AppDelegate.h @@ -8,7 +8,6 @@ #import - @interface AppDelegate : UIResponder @property (strong, nonatomic) UIWindow *window; diff --git a/WKPagesScrollView/ViewController.h b/WKPagesScrollView/ViewController.h index bffa093..3422918 100644 --- a/WKPagesScrollView/ViewController.h +++ b/WKPagesScrollView/ViewController.h @@ -8,8 +8,7 @@ #import #import "WKPagesCollectionView.h" -@interface ViewController : UIViewController{ - -} + +@interface ViewController : UIViewController @end diff --git a/WKPagesScrollView/ViewController.m b/WKPagesScrollView/ViewController.m index 27c1768..f446c2c 100644 --- a/WKPagesScrollView/ViewController.m +++ b/WKPagesScrollView/ViewController.m @@ -8,10 +8,12 @@ #import "ViewController.h" -@interface ViewController (){ - WKPagesCollectionView* _collectionView; - NSMutableArray* _array; -} +static NSString *CellReuseIdentifier = @"CellReuseIdentifier"; + +@interface ViewController () + +@property (nonatomic, strong) WKPagesCollectionView *collectionView; +@property (nonatomic, strong) NSMutableArray *items; @end @@ -20,80 +22,97 @@ @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; - // Do any additional setup after loading the view, typically from a nib. - _array=[[NSMutableArray alloc]init]; - for (int a=0; a<=30; a++) { - [_array addObject:[NSString stringWithFormat:@"button %d",a]]; + + self.items = [[NSMutableArray alloc] init]; + + for (NSUInteger a = 0; a <= 30; a++) { + [self.items addObject:[NSString stringWithFormat:@"button %lu",(unsigned long)a]]; } - _collectionView=[[[WKPagesCollectionView alloc]initWithFrame:CGRectMake(0.0f, 0.0f, self.view.frame.size.width, self.view.frame.size.height)] autorelease]; - _collectionView.dataSource=self; - _collectionView.delegate=self; - [_collectionView registerClass:[WKPagesCollectionViewCell class] forCellWithReuseIdentifier:@"cell"]; - [self.view addSubview:_collectionView]; - _collectionView.maskShow=YES; + self.collectionView = [[WKPagesCollectionView alloc] initWithFrame:self.view.bounds]; + self.collectionView.dataSource=self; + self.collectionView.delegate=self; + [self.collectionView registerClass:[WKPagesCollectionViewCell class] forCellWithReuseIdentifier:CellReuseIdentifier]; + [self.view addSubview:self.collectionView]; + self.collectionView.maskShow = YES; - UIToolbar* toolBar=[[[UIToolbar alloc]initWithFrame:CGRectMake(0.0f, self.view.frame.size.height-50.0f, self.view.frame.size.width, 50.0f)] autorelease]; - toolBar.barStyle=UIBarStyleBlackTranslucent; - toolBar.translucent=YES; - toolBar.tintColor=[UIColor whiteColor]; + UIToolbar *toolBar = [[UIToolbar alloc] initWithFrame:CGRectMake(0.0, self.view.frame.size.height - 50.0, self.view.frame.size.width, 50.0)]; + toolBar.barStyle = UIBarStyleBlackTranslucent; + toolBar.translucent = YES; + toolBar.tintColor = [UIColor whiteColor]; [self.view addSubview:toolBar]; - - UIBarButtonItem* addButtonItem=[[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(onButtonAdd:)] autorelease]; - toolBar.items=@[ - [[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil] autorelease], - addButtonItem, - [[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil] autorelease],]; + UIBarButtonItem *addButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(onButtonAdd:)]; + UIBarButtonItem *flexibleSpaceItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]; + toolBar.items = @[flexibleSpaceItem, + addButtonItem, + flexibleSpaceItem]; } -- (void)didReceiveMemoryWarning +- (IBAction)onButtonTitle:(id)sender { - [super didReceiveMemoryWarning]; - // Dispose of any resources that can be recreated. -} --(void)dealloc{ - [_array release]; - [_collectionView release]; - [super dealloc]; -} --(IBAction)onButtonTitle:(id)sender{ - NSLog(@"button"); - [_collectionView dismissFromHightLightWithCompletion:^(BOOL finished) { - NSLog(@"dismiss completed"); - }]; + UIView *thisButton = (UIView *)sender; + if (!self.collectionView.isHighLight) { + NSArray *visibleIndexPaths = [self.collectionView indexPathsForVisibleItems]; + for (NSIndexPath *indexPath in visibleIndexPaths) { + WKPagesCollectionViewCell *cell = (WKPagesCollectionViewCell *)[self.collectionView cellForItemAtIndexPath:indexPath]; + BOOL doesContain = [cell.cellContentView.subviews containsObject:thisButton]; + if (doesContain) { + [self.collectionView showCellToHighLightAtIndexPath:indexPath completion:^(BOOL finished) { + NSLog(@"highlight completed"); + }]; + break; + } + } + } else { + [self.collectionView dismissFromHightLightWithCompletion:^(BOOL finished) { + NSLog(@"dismiss completed"); + }]; + } } --(IBAction)onButtonAdd:(id)sender{ - [_collectionView appendItem]; + +- (IBAction)onButtonAdd:(id)sender +{ + [self.collectionView appendItem]; } -#pragma mark - UICollectionViewDataSource and UICollectionViewDelegate --(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{ - return _array.count; + +#pragma mark - UICollectionViewDataSource + +- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section +{ + return self.items.count; } --(UICollectionViewCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{ -// NSLog(@"cellForItemAtIndexPath:%d",indexPath.row); - static NSString* identity=@"cell"; - WKPagesCollectionViewCell* cell=(WKPagesCollectionViewCell*)[collectionView dequeueReusableCellWithReuseIdentifier:identity forIndexPath:indexPath]; - cell.collectionView=collectionView; - UIImageView* imageView=[[[UIImageView alloc]initWithImage:[UIImage imageNamed:@"image-0"]] autorelease]; - imageView.frame=self.view.bounds; + +- (UICollectionViewCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath +{ + WKPagesCollectionViewCell *cell = (WKPagesCollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:CellReuseIdentifier forIndexPath:indexPath]; + cell.collectionView = collectionView; + + UIImageView* imageView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"image-0"]]; + imageView.frame = self.view.bounds; [cell.cellContentView addSubview:imageView]; - UIButton* button=[UIButton buttonWithType:UIButtonTypeCustom]; - button.frame=CGRectMake(0, (indexPath.row+1)*10+100, 320, 50.0f); - button.backgroundColor=[UIColor whiteColor]; + + UIButton* button = [UIButton buttonWithType:UIButtonTypeCustom]; + button.frame = CGRectMake(0.0, 100.0, self.view.bounds.size.width, 50.0); + button.backgroundColor = [UIColor whiteColor]; [button setTitleColor:[UIColor darkGrayColor] forState:UIControlStateNormal]; - [button setTitle:_array[indexPath.row] forState:UIControlStateNormal]; + [button setTitle:self.items[indexPath.row] forState:UIControlStateNormal]; [button addTarget:self action:@selector(onButtonTitle:) forControlEvents:UIControlEventTouchUpInside]; [cell.cellContentView addSubview:button]; + return cell; } -#pragma mark WKPagesCollectionViewDataSource -///追加数据 --(void)willAppendItemInCollectionView:(WKPagesCollectionView *)collectionView{ - [_array addObject:@"new button"]; + +#pragma mark - WKPagesCollectionViewDataSource + +- (void)willAppendItemInCollectionView:(WKPagesCollectionView *)collectionView +{ + [self.items addObject:@"new button"]; } -///删除数据 --(void)collectionView:(WKPagesCollectionView *)collectionView willRemoveCellAtIndexPath:(NSIndexPath *)indexPath{ - [_array removeObjectAtIndex:indexPath.row]; + +- (void)collectionView:(WKPagesCollectionView *)collectionView willRemoveCellAtIndexPath:(NSIndexPath *)indexPath +{ + [self.items removeObjectAtIndex:indexPath.row]; } -@end + +@end \ No newline at end of file diff --git a/WKPagesScrollView/WKPagesCollectionView/WK.h b/WKPagesScrollView/WKPagesCollectionView/WK.h index 9f3dba5..139d35d 100644 --- a/WKPagesScrollView/WKPagesCollectionView/WK.h +++ b/WKPagesScrollView/WKPagesCollectionView/WK.h @@ -19,27 +19,18 @@ static inline CATransform3D WKFlipCATransform3DMakePerspective(CGPoint center, f scale.m34 = -1.0f/disZ; return CATransform3DConcat(CATransform3DConcat(transToCenter, scale), transBack); } + static inline CATransform3D WKFlipCATransform3DPerspect(CATransform3D t, CGPoint center, float disZ) { return CATransform3DConcat(t, WKFlipCATransform3DMakePerspective(center, disZ)); } + static inline CATransform3D WKFlipCATransform3DPerspectSimple(CATransform3D t){ return WKFlipCATransform3DPerspect(t, CGPointMake(0, 0), 1500.0f); } + static inline CATransform3D WKFlipCATransform3DPerspectSimpleWithRotate(CGFloat degree){ return WKFlipCATransform3DPerspectSimple(CATransform3DMakeRotation((M_PI*degree/180.0f), 1.0, 0.0, 0.0)); } -static inline UIImage* makeImageForView(UIView*view){ - double startTime=CFAbsoluteTimeGetCurrent(); - if(UIGraphicsBeginImageContextWithOptions != NULL){ - UIGraphicsBeginImageContextWithOptions(view.frame.size, NO, 0.0); - } else { - UIGraphicsBeginImageContext(view.frame.size); - } - [view.layer renderInContext:UIGraphicsGetCurrentContext()]; - UIImage* image=UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - NSLog(@"makeImage duration:%f", CFAbsoluteTimeGetCurrent()-startTime); - return image; -} + #endif diff --git a/WKPagesScrollView/WKPagesCollectionView/WKCloseButton.h b/WKPagesScrollView/WKPagesCollectionView/WKCloseButton.h new file mode 100644 index 0000000..ec8440d --- /dev/null +++ b/WKPagesScrollView/WKPagesCollectionView/WKCloseButton.h @@ -0,0 +1,17 @@ +// +// WKCloseButton.h +// WKPagesCollectionView +// +// Created by Xu Zhao on 3/12/14. +// Copyright (c) 2014 秦 道平. All rights reserved. +// + +#import + +#define HighLightRotateAngle -10.0f +#define CloseButtonWidth 120 +#define CloseButtonHeight 120 + +@interface WKCloseButton : UIButton + +@end diff --git a/WKPagesScrollView/WKPagesCollectionView/WKCloseButton.m b/WKPagesScrollView/WKPagesCollectionView/WKCloseButton.m new file mode 100644 index 0000000..b0aa147 --- /dev/null +++ b/WKPagesScrollView/WKPagesCollectionView/WKCloseButton.m @@ -0,0 +1,44 @@ +// +// WKCloseButton.m +// WKPagesCollectionView +// +// Created by Xu Zhao on 3/12/14. +// Copyright (c) 2014 秦 道平. All rights reserved. +// + +#import "WKCloseButton.h" +#import + +@implementation WKCloseButton + +- (id)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + if (self){ + [self drawCloseButton]; + } + return self; +} + +- (void)drawCloseButton +{ + self.backgroundColor = [UIColor clearColor]; + CGFloat xOffset = 12; + CGFloat yOffset = 15; + CGFloat width = 11; + CGFloat height = 16; + UIBezierPath *path = [UIBezierPath bezierPath]; + [path moveToPoint:CGPointMake(xOffset, yOffset)]; + [path addLineToPoint:CGPointMake(width + xOffset, height + yOffset)]; + [path moveToPoint:CGPointMake(width + xOffset, yOffset)]; + [path addLineToPoint:CGPointMake(xOffset, height + yOffset)]; + + CAShapeLayer *shapeLayer = [CAShapeLayer layer]; + [shapeLayer setPath:[path CGPath]]; + [shapeLayer setStrokeColor:[[UIColor blackColor] CGColor]]; + [shapeLayer setLineWidth:1.0f]; + + [[self layer] addSublayer:shapeLayer]; +} + +@end diff --git a/WKPagesScrollView/WKPagesCollectionView/WKPagesCollectionView.h b/WKPagesScrollView/WKPagesCollectionView/WKPagesCollectionView.h index 537683a..a1ad728 100644 --- a/WKPagesScrollView/WKPagesCollectionView/WKPagesCollectionView.h +++ b/WKPagesScrollView/WKPagesCollectionView/WKPagesCollectionView.h @@ -6,44 +6,40 @@ // Copyright (c) 2013年 秦 道平. All rights reserved. // - - #import #import "WKPagesCollectionViewFlowLayout.h" #import "WKPagesCollectionViewCell.h" + @class WKPagesCollectionView; -@protocol WKPagesCollectionViewDataSource + +@protocol WKPagesCollectionViewDataSource ///When you delete a cell is used to delete data --(void)collectionView:(WKPagesCollectionView*)collectionView willRemoveCellAtIndexPath:(NSIndexPath*)indexPath; +-(void)collectionView:(WKPagesCollectionView *)collectionView willRemoveCellAtIndexPath:(NSIndexPath *)indexPath; ///Called when additional data --(void)willAppendItemInCollectionView:(WKPagesCollectionView*)collectionView; +-(void)willAppendItemInCollectionView:(WKPagesCollectionView *)collectionView; + @end + @protocol WKPagesCollectionViewDelegate + @optional ///When displaying a callback --(void)collectionView:(WKPagesCollectionView*)collectionView didShownToHightlightAtIndexPath:(NSIndexPath*)indexPath; +-(void)collectionView:(WKPagesCollectionView *)collectionView didShownToHightlightAtIndexPath:(NSIndexPath *)indexPath; ///Return to the original state when the callback --(void)didDismissFromHightlightOnCollectionView:(WKPagesCollectionView*)collectionView; +-(void)didDismissFromHightlightOnCollectionView:(WKPagesCollectionView *)collectionView; + @end -@interface WKPagesCollectionView : UICollectionView{ - UIImageView* _maskImageView; - BOOL _maskShow; -} -///Can I delete? -@property (nonatomic,assign) BOOL canRemove; -///show mask -@property (nonatomic,assign) BOOL maskShow; -///are showing -@property (readonly,nonatomic,assign) BOOL isHighLight; - -///top offscreen margin -@property (nonatomic,assign) CGFloat topOffScreenMargin; -///duration from normal to highlight state,default is 0.5 second -@property (nonatomic,assign) CGFloat highLightAnimationDuration; -///duration from highlight to nomral state, default is 0.5 second -@property (nonatomic,assign) CGFloat dismisalAnimationDuration; -#pragma mark - Action -///show + +@interface WKPagesCollectionView : UICollectionView + +@property (nonatomic, assign) BOOL canRemove; +@property (nonatomic, assign) BOOL maskShow; +@property (readonly, nonatomic, assign) BOOL isHighLight; +@property (nonatomic, assign) BOOL isAddingNewPage; +@property (nonatomic, assign) CGFloat topOffScreenMargin; +@property (nonatomic, assign) CGFloat highLightAnimationDuration; +@property (nonatomic, assign) CGFloat dismisalAnimationDuration; + -(void)showCellToHighLightAtIndexPath:(NSIndexPath*)indexPath completion:(void(^)(BOOL finished))completion; ///No animation display state, there will be didShowCellToHightLight callback -(void)showCellToHighLightAtIndexPath:(NSIndexPath*)indexPath; @@ -51,4 +47,5 @@ -(void)dismissFromHightLightWithCompletion:(void(^)(BOOL finished))completion; ///Additional content -(void)appendItem; + @end diff --git a/WKPagesScrollView/WKPagesCollectionView/WKPagesCollectionView.m b/WKPagesScrollView/WKPagesCollectionView/WKPagesCollectionView.m index 119a9f2..69a6eb1 100644 --- a/WKPagesScrollView/WKPagesCollectionView/WKPagesCollectionView.m +++ b/WKPagesScrollView/WKPagesCollectionView/WKPagesCollectionView.m @@ -10,63 +10,73 @@ CGFloat const TOP_OFFSCREEN_MARGIN = 120; -@implementation WKPagesCollectionView -@dynamic maskShow; +@interface WKPagesCollectionView () +@property (nonatomic, strong) UIImageView *maskImageView; -- (CGFloat)topOffScreenMargin +@end + +@implementation WKPagesCollectionView + +- (id)initWithFrame:(CGRect)frame { - if (!_topOffScreenMargin) { - _topOffScreenMargin = TOP_OFFSCREEN_MARGIN; - } - return _topOffScreenMargin; -} --(id)initWithFrame:(CGRect)frame{ - WKPagesCollectionViewFlowLayout* flowLayout=[[[WKPagesCollectionViewFlowLayout alloc ] init] autorelease]; + WKPagesCollectionViewFlowLayout *flowLayout=[[WKPagesCollectionViewFlowLayout alloc ] init]; + flowLayout.itemSize = [UIScreen mainScreen].bounds.size; + CGRect realFrame = CGRectMake(frame.origin.x, frame.origin.y-self.topOffScreenMargin, frame.size.width, frame.size.height + self.topOffScreenMargin); + self = [super initWithFrame:realFrame collectionViewLayout:flowLayout]; - if (self){ - self.contentInset=UIEdgeInsetsMake(20.0f, 0.0f, 0.0f, 0.0f); - self.highLightAnimationDuration=0.5f; - self.dismisalAnimationDuration=0.5f; + if (self) { + self.contentInset = UIEdgeInsetsMake(20.0, 0.0, 0.0, 0.0); + self.highLightAnimationDuration = 0.3; + self.dismisalAnimationDuration = 0.3; } + return self; } --(void)dealloc{ - [_maskImageView release]; - [super dealloc]; + +- (CGFloat)topOffScreenMargin +{ + if (!_topOffScreenMargin) { + _topOffScreenMargin = TOP_OFFSCREEN_MARGIN; + } + return _topOffScreenMargin; } --(void)setHidden:(BOOL)hidden{ + +- (void)setHidden:(BOOL)hidden +{ [super setHidden:hidden]; + if (hidden){ - if (_maskImageView){ + if (_maskImageView) { [_maskImageView removeFromSuperview]; - _maskImageView=nil; + _maskImageView = nil; } } - else{ - [self setMaskShow:_maskShow]; + else { + [self setMaskShow:self.maskShow]; } } -#pragma mark - Mask --(void)setMaskShow:(BOOL)maskShow{ - _maskShow=maskShow; + +- (void)setMaskShow:(BOOL)maskShow +{ + _maskShow = maskShow; + if (maskShow){ if (!_maskImageView){ - _maskImageView=[[UIImageView alloc]initWithImage:[self makeGradientImage]]; + _maskImageView = [[UIImageView alloc]initWithImage:[self makeGradientImage]]; [self.superview addSubview:_maskImageView]; } - _maskImageView.hidden=NO; - } - else{ - _maskImageView.hidden=YES; + _maskImageView.hidden = NO; + + } else { + _maskImageView.hidden = YES; } } --(BOOL)maskShow{ - return _maskShow; -} --(UIImage*)makeGradientImage{ + +- (UIImage *)makeGradientImage +{ UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 1.0f); CGContextRef context=UIGraphicsGetCurrentContext(); CGContextSaveGState(context); @@ -97,158 +107,196 @@ -(UIImage*)makeGradientImage{ UIGraphicsEndImageContext(); return image; } + #pragma mark - Actions -///Display status --(void)showCellToHighLightAtIndexPath:(NSIndexPath *)indexPath completion:(void (^)(BOOL))completion{ - NSLog(@"row:%d",indexPath.row); + +- (void)showCellToHighLightAtIndexPath:(NSIndexPath *)indexPath completion:(void (^)(BOOL))completion +{ + NSLog(@"row:%ld",(long)indexPath.row); + + if (indexPath.row >= [self numberOfItemsInSection:indexPath.section]){ + return; + } + if (_isHighLight){ return; } - ///如果在可视范围内就不要滚动了 - BOOL no_scroll=NO; + + BOOL noScroll = NO; NSArray* visibleIndexPaths=[self indexPathsForVisibleItems]; for (NSIndexPath* indexPath in visibleIndexPaths) { if (indexPath.row==indexPath.row){ - no_scroll=YES; + noScroll = YES; } } - if (!no_scroll){ + if (!noScroll){ [self scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:YES]; } + double delayInSeconds = 0.3f; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ - self.maskShow=NO; - self.scrollEnabled=NO; + self.maskShow = YES; + _maskImageView.hidden = NO; + _maskImageView.alpha = 1.0; + self.scrollEnabled = NO; [UIView animateWithDuration:self.highLightAnimationDuration delay:0.0f options:UIViewAnimationOptionCurveEaseOut animations:^{ [self.visibleCells enumerateObjectsUsingBlock:^(WKPagesCollectionViewCell* cell, NSUInteger idx, BOOL *stop) { NSIndexPath* visibleIndexPath=[self indexPathForCell:cell]; if (visibleIndexPath.row==indexPath.row){ - cell.showingState=WKPagesCollectionViewCellShowingStateHightlight; - } - else if (visibleIndexPath.rowindexPath.row){ - NSLog(@"indexPath:%d,visibleIndexPath:%d",indexPath.row,visibleIndexPath.row); - cell.showingState=WKPagesCollectionViewCellShowingStateBackToBottom; - } - else{ - cell.showingState=WKPagesCollectionViewCellShowingStateNormal; + cell.state=WKPagesCollectionViewCellStateHightlight; + + } else if (visibleIndexPath.rowindexPath.row) { + NSLog(@"indexPath:%ld,visibleIndexPath:%ld",(long)indexPath.row,(long)visibleIndexPath.row); + cell.state=WKPagesCollectionViewCellStateBackToBottom; + + } else { + cell.state = WKPagesCollectionViewCellStateNormal; } + _maskImageView.alpha = 0.0; }]; + } completion:^(BOOL finished) { - _isHighLight=YES; - completion(finished); + _isHighLight = YES; + self.maskShow = NO; + if (completion) { + completion(finished); + } + if ([self.delegate respondsToSelector:@selector(collectionView:didShownToHightlightAtIndexPath:)]){ [(id)self.delegate collectionView:self didShownToHightlightAtIndexPath:indexPath]; } }]; }); - } --(void)showCellToHighLightAtIndexPath:(NSIndexPath *)indexPath{ + +- (void)showCellToHighLightAtIndexPath:(NSIndexPath *)indexPath +{ + if (indexPath.row >= [self numberOfItemsInSection:indexPath.section]) { + return; + } + if (_isHighLight){ return; } - [self scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:NO]; + double delayInSeconds = 0.01f; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ - self.maskShow=NO; - self.scrollEnabled=NO; + self.maskShow = NO; + self.scrollEnabled = NO; [self.visibleCells enumerateObjectsUsingBlock:^(WKPagesCollectionViewCell* cell, NSUInteger idx, BOOL *stop) { - NSIndexPath* visibleIndexPath=[self indexPathForCell:cell]; - if (visibleIndexPath.row==indexPath.row){ - cell.showingState=WKPagesCollectionViewCellShowingStateHightlight; + NSIndexPath* visibleIndexPath = [self indexPathForCell:cell]; + if (visibleIndexPath.row == indexPath.row){ + cell.state = WKPagesCollectionViewCellStateHightlight; } else if (visibleIndexPath.rowindexPath.row){ - cell.showingState=WKPagesCollectionViewCellShowingStateBackToBottom; + cell.state = WKPagesCollectionViewCellStateBackToBottom; } else{ - cell.showingState=WKPagesCollectionViewCellShowingStateNormal; + cell.state = WKPagesCollectionViewCellStateNormal; } }]; _isHighLight=YES; }); } -///Back to the original state --(void)dismissFromHightLightWithCompletion:(void (^)(BOOL))completion{ - self.maskShow=YES; + +- (void)dismissFromHightLightWithCompletion:(void (^)(BOOL))completion +{ + self.maskShow = YES; + _maskImageView.alpha = 0.0; + if (!_isHighLight) return; - [UIView animateWithDuration:self.dismisalAnimationDuration delay:0.0f options:UIViewAnimationOptionCurveEaseOut animations:^{ - [self.visibleCells enumerateObjectsUsingBlock:^(WKPagesCollectionViewCell* cell, NSUInteger idx, BOOL *stop) { - cell.showingState=WKPagesCollectionViewCellShowingStateNormal; + + [UIView animateWithDuration:self.dismisalAnimationDuration delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{ + [self.visibleCells enumerateObjectsUsingBlock:^(WKPagesCollectionViewCell *cell, NSUInteger idx, BOOL *stop) { + cell.state = WKPagesCollectionViewCellStateNormal; + _maskImageView.alpha = 1.0; }]; + } completion:^(BOOL finished) { - self.scrollEnabled=YES; - _isHighLight=NO; - completion(finished); + self.scrollEnabled = YES; + _isHighLight = NO; + + if (completion != nil) { + completion(finished); + } + if ([self.delegate respondsToSelector:@selector(didDismissFromHightlightOnCollectionView:)]){ [(id)self.delegate didDismissFromHightlightOnCollectionView:self]; } }]; } -///Append a page --(void)appendItem{ + +- (void)appendItem +{ + if (self.isAddingNewPage) { + return; + } + + self.isAddingNewPage = YES; if (self.isHighLight){ [self dismissFromHightLightWithCompletion:^(BOOL finished) { - [self _addNewPage]; + [self addNewPage]; }]; } else{ - [self _addNewPage]; + [self addNewPage]; } } -///Adding a --(void)_addNewPage{ - int total=[self numberOfItemsInSection:0]; + +- (void)addNewPage +{ + NSInteger total = [self numberOfItemsInSection:0]; if (total > 0) { - [self scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:total-1 inSection:0] atScrollPosition:UICollectionViewScrollPositionBottom animated:YES]; + [self scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:total-1 inSection:0] atScrollPosition:UICollectionViewScrollPositionBottom animated:NO]; } - double delayInSeconds = 0.3f; + double delayInSeconds = 0.001f; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ ///Add Data [(id)self.dataSource willAppendItemInCollectionView:self]; - int lastRow=total; - NSIndexPath* insertIndexPath=[NSIndexPath indexPathForItem:lastRow inSection:0]; + NSInteger lastRow = total; + NSIndexPath *insertIndexPath=[NSIndexPath indexPathForItem:lastRow inSection:0]; [self performBatchUpdates:^{ [self insertItemsAtIndexPaths:@[insertIndexPath]]; } completion:^(BOOL finished) { - double delayInSeconds = 0.3f; + double delayInSeconds = 0.001f; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ [self showCellToHighLightAtIndexPath:insertIndexPath completion:^(BOOL finished) { - + self.isAddingNewPage = NO; }]; }); }]; }); } -#pragma mark - UIView and UICollectionView --(UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event{ - UIView* view=[super hitTest:point withEvent:event]; + +#pragma mark - + +- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{ + UIView *view = [super hitTest:point withEvent:event]; if (!view){ return nil; } - if (view==self){ - for (WKPagesCollectionViewCell* cell in self.visibleCells) { - if (cell.showingState==WKPagesCollectionViewCellShowingStateHightlight){ + if (view == self){ + for (WKPagesCollectionViewCell *cell in self.visibleCells) { + if (cell.state == WKPagesCollectionViewCellStateHightlight){ return cell.cellContentView;///Events should only be passed to this layer } } } return view; -// UIView* view=[super hitTest:point withEvent:event]; -// NSLog(@"%@,%d",NSStringFromClass([view class]),view.tag); -// return view; } + @end diff --git a/WKPagesScrollView/WKPagesCollectionView/WKPagesCollectionViewCell.h b/WKPagesScrollView/WKPagesCollectionView/WKPagesCollectionViewCell.h index 22db0d1..18d841b 100644 --- a/WKPagesScrollView/WKPagesCollectionView/WKPagesCollectionViewCell.h +++ b/WKPagesScrollView/WKPagesCollectionView/WKPagesCollectionViewCell.h @@ -7,26 +7,21 @@ // #import -typedef enum WKPagesCollectionViewCellShowingState:NSUInteger{ - WKPagesCollectionViewCellShowingStateNormal=0, - WKPagesCollectionViewCellShowingStateHightlight=1, - WKPagesCollectionViewCellShowingStateBackToTop=2, - WKPagesCollectionViewCellShowingStateBackToBottom=3, -} WKPagesCollectionViewCellShowingState; -@interface WKPagesCollectionViewCell : UICollectionViewCell{ - WKPagesCollectionViewCellShowingState _showingState; - UITapGestureRecognizer* _tapGesture; - UIScrollView* _scrollView; -// UIImageView* _maskImageView; -} -///Position the normal state -@property (nonatomic,assign) CATransform3D normalTransform; -///Position the normal state -@property (nonatomic,assign) CGRect normalFrame; -///Display status -@property (nonatomic,assign) WKPagesCollectionViewCellShowingState showingState; -///Quote collectionView -@property (nonatomic,assign) UICollectionView* collectionView; -@property (nonatomic,retain) UIView* cellContentView; --(UIImage*)makeGradientImage; +#import "WKCloseButton.h" + +typedef NS_ENUM(NSUInteger, WKPagesCollectionViewCellState) { + WKPagesCollectionViewCellStateNormal, + WKPagesCollectionViewCellStateHightlight, + WKPagesCollectionViewCellStateBackToTop, + WKPagesCollectionViewCellStateBackToBottom, +}; + +@interface WKPagesCollectionViewCell : UICollectionViewCell + +@property (nonatomic, assign) CATransform3D normalTransform; +@property (nonatomic, assign) CGRect normalFrame; +@property (nonatomic, assign) WKPagesCollectionViewCellState state; +@property (nonatomic, assign) UICollectionView *collectionView; // cell should not need a reference to the collection view (its parent) +@property (nonatomic, strong) UIView *cellContentView; + @end diff --git a/WKPagesScrollView/WKPagesCollectionView/WKPagesCollectionViewCell.m b/WKPagesScrollView/WKPagesCollectionView/WKPagesCollectionViewCell.m index 0b15da8..f1f5912 100644 --- a/WKPagesScrollView/WKPagesCollectionView/WKPagesCollectionViewCell.m +++ b/WKPagesScrollView/WKPagesCollectionView/WKPagesCollectionViewCell.m @@ -8,169 +8,167 @@ #import "WKPagesCollectionViewCell.h" #import "WKPagesCollectionView.h" +#import "WKCloseButton.h" + +@interface WKPagesCollectionViewCell () + +@property (nonatomic, strong) UITapGestureRecognizer *tapGestureRecognizer; +@property (nonatomic, strong) UIScrollView *scrollView; +@property (nonatomic, strong) WKCloseButton *closeButton; + +@end + @implementation WKPagesCollectionViewCell -@dynamic showingState; + - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { - // Initialization code - self.clipsToBounds=NO; - self.backgroundColor=[UIColor clearColor]; - self.contentView.tag=100; + self.clipsToBounds = NO; + self.backgroundColor = [UIColor clearColor]; + self.contentView.tag = 100; + CGRect rect=CGRectMake(0.0f, 0.0f, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height); - if (!_scrollView){ - _scrollView=[[UIScrollView alloc]initWithFrame:rect]; - _scrollView.autoresizingMask=UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; - _scrollView.clipsToBounds=NO; - _scrollView.backgroundColor=[UIColor clearColor]; - _scrollView.showsVerticalScrollIndicator=NO; - _scrollView.showsHorizontalScrollIndicator=YES; - _scrollView.contentSize=CGSizeMake(_scrollView.frame.size.width+1, _scrollView.frame.size.height); - _scrollView.delegate=self; - [self.contentView addSubview:_scrollView]; - _scrollView.tag=101; - } - if (!_cellContentView){ - _cellContentView=[[UIView alloc]initWithFrame:rect]; - _cellContentView.autoresizingMask=UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; - [_scrollView addSubview:_cellContentView]; - _cellContentView.tag=102; - } - if (!_tapGesture){ - _tapGesture=[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(onTapGesture:)]; - [_scrollView addGestureRecognizer:_tapGesture]; - } + self.scrollView = [[UIScrollView alloc] initWithFrame:rect]; + self.scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; + self.scrollView.clipsToBounds = NO; + self.scrollView.backgroundColor = [UIColor clearColor]; + self.scrollView.showsVerticalScrollIndicator = NO; + self.scrollView.showsHorizontalScrollIndicator = YES; + self.scrollView.contentSize = CGSizeMake(self.scrollView.frame.size.width + 1.0, self.scrollView.frame.size.height); + self.scrollView.delegate=self; + [self.contentView addSubview:_scrollView]; + self.scrollView.tag = 101; + + self.cellContentView = [[UIView alloc] initWithFrame:rect]; + self.cellContentView.autoresizingMask=UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight; + [self.scrollView addSubview:self.cellContentView]; + self.cellContentView.tag=102; + + self.tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGestureRecognized:)]; + [self.scrollView addGestureRecognizer:self.tapGestureRecognizer]; + + self.closeButton = [[WKCloseButton alloc] initWithFrame:CGRectMake(0.0, 0.0, CloseButtonWidth, CloseButtonHeight)]; + [self.closeButton addTarget:self action:@selector(closeButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; + [self.scrollView addSubview:self.closeButton]; } + return self; } -/* -// Only override drawRect: if you perform custom drawing. -// An empty implementation adversely affects performance during animation. -- (void)drawRect:(CGRect)rect +- (void)prepareForReuse { - // Drawing code -} -*/ --(void)dealloc{ - [_tapGesture release]; - [_cellContentView release]; - [_scrollView release]; - [super dealloc]; -} --(void)prepareForReuse{ [super prepareForReuse]; - for (UIView* view in _cellContentView.subviews) { + for (UIView *view in self.cellContentView.subviews) { [view removeFromSuperview]; } - } --(IBAction)onTapGesture:(UITapGestureRecognizer*)tapGesture{ - NSIndexPath* indexPath=[self.collectionView indexPathForCell:self]; -// NSLog(@"row:%d",indexPath.row); -// [self.collectionView.delegate collectionView:self.collectionView didSelectItemAtIndexPath:indexPath]; - [(WKPagesCollectionView*)self.collectionView showCellToHighLightAtIndexPath:indexPath completion:^(BOOL finished) { + +- (void)tapGestureRecognized:(UITapGestureRecognizer*)tapGesture +{ + NSIndexPath *indexPath = [self.collectionView indexPathForCell:self]; + [(WKPagesCollectionView *)self.collectionView showCellToHighLightAtIndexPath:indexPath completion:^(BOOL finished) { NSLog(@"highlight completed"); }]; } -#pragma mark - Properties --(void)setShowingState:(WKPagesCollectionViewCellShowingState)showingState{ - if (_showingState==showingState) + +-(void) closeButtonPressed:(id)sender +{ + [self removeCurrentCell]; + +} + +#pragma mark - + +- (void)setState:(WKPagesCollectionViewCellState)state +{ + if (_state == state) return; - _showingState=showingState; - WKPagesCollectionViewFlowLayout* collectionLayout=(WKPagesCollectionViewFlowLayout*)self.collectionView.collectionViewLayout; + + _state = state; + + WKPagesCollectionViewFlowLayout *collectionLayout = (WKPagesCollectionViewFlowLayout*)self.collectionView.collectionViewLayout; CGFloat pageHeight=collectionLayout.pageHeight; CGFloat topMargin=[(WKPagesCollectionView*)self.collectionView topOffScreenMargin]; - switch (showingState) { - case WKPagesCollectionViewCellShowingStateHightlight:{ - self.normalTransform=self.layer.transform;///The original location of the first record - _scrollView.scrollEnabled=NO; - NSIndexPath* indexPath=[self.collectionView indexPathForCell:self]; - CGFloat moveY=self.collectionView.contentOffset.y-(WKPagesCollectionViewPageSpacing)*indexPath.row +topMargin; - CATransform3D moveTransform=CATransform3DMakeTranslation(0.0f, moveY, 0.0f); - self.layer.transform=moveTransform; + + switch (state) { + case WKPagesCollectionViewCellStateHightlight: { + self.normalTransform = self.layer.transform; + _scrollView.scrollEnabled = NO; + _closeButton.hidden = YES; + NSIndexPath* indexPath = [self.collectionView indexPathForCell:self]; + CGFloat moveY = self.collectionView.contentOffset.y - (WKPagesCollectionViewPageSpacing)*indexPath.row + topMargin; + CATransform3D moveTransform = CATransform3DMakeTranslation(0.0, moveY, 0.0); + self.layer.transform = moveTransform; } break; - case WKPagesCollectionViewCellShowingStateBackToTop:{ - self.normalTransform=self.layer.transform;///The original location of the first record - _scrollView.scrollEnabled=NO; - CATransform3D moveTransform=CATransform3DMakeTranslation(0, -1*pageHeight-topMargin, 0); - self.layer.transform=CATransform3DConcat(CATransform3DIdentity, moveTransform); + case WKPagesCollectionViewCellStateBackToTop: { + self.normalTransform = self.layer.transform; + _scrollView.scrollEnabled = NO; + _closeButton.hidden = NO; + CATransform3D rotateTransform = WKFlipCATransform3DPerspectSimpleWithRotate(HighLightRotateAngle); + CATransform3D moveTransform = CATransform3DMakeTranslation(0, -1*pageHeight - topMargin, 0.0); + self.layer.transform=CATransform3DConcat(rotateTransform, moveTransform); } break; - case WKPagesCollectionViewCellShowingStateBackToBottom:{ - self.normalTransform=self.layer.transform;///The original location of the first record - _scrollView.scrollEnabled=NO; - CATransform3D moveTransform=CATransform3DMakeTranslation(0, pageHeight+topMargin, 0); - self.layer.transform=CATransform3DConcat(CATransform3DIdentity, moveTransform); + case WKPagesCollectionViewCellStateBackToBottom: { + self.normalTransform = self.layer.transform; + _scrollView.scrollEnabled = NO; + _closeButton.hidden = NO; + CATransform3D rotateTransform = WKFlipCATransform3DPerspectSimpleWithRotate(HighLightRotateAngle); + CATransform3D moveTransform = CATransform3DMakeTranslation(0.0, pageHeight + topMargin, 0.0); + self.layer.transform = CATransform3DConcat(rotateTransform, moveTransform); } break; - case WKPagesCollectionViewCellShowingStateNormal:{ - self.layer.transform=self.normalTransform; - _scrollView.scrollEnabled=YES; + case WKPagesCollectionViewCellStateNormal: { + self.layer.transform = self.normalTransform; + _scrollView.scrollEnabled = YES; + _closeButton.hidden = NO; } break; default: break; } - - - } --(WKPagesCollectionViewCellShowingState)showingState{ - return _showingState; + +- (void)removeCurrentCell +{ + NSIndexPath* indexPath = [self.collectionView indexPathForCell:self]; + NSLog(@"delete cell at %ld",(long)indexPath.row); + //Delete data + id pagesDataSource=(id)self.collectionView.dataSource; + [pagesDataSource collectionView:(WKPagesCollectionView*)self.collectionView willRemoveCellAtIndexPath:indexPath]; + //Animation + [self.collectionView performBatchUpdates:^{ + [self.collectionView deleteItemsAtIndexPaths:@[indexPath,]]; + } completion:^(BOOL finished) { + + }]; } + #pragma mark - UIScrollViewDelegate --(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{ + +- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView +{ } --(void)scrollViewDidScroll:(UIScrollView *)scrollView{ + +- (void)scrollViewDidScroll:(UIScrollView *)scrollView +{ } --(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{ - if (self.showingState==WKPagesCollectionViewCellShowingStateNormal){ - if (scrollView.contentOffset.x>=90.0f){ - NSIndexPath* indexPath=[self.collectionView indexPathForCell:self]; - NSLog(@"delete cell at %d",indexPath.row); - //self.alpha=0.0f; - ///Delete data - id pagesDataSource=(id)self.collectionView.dataSource; - [pagesDataSource collectionView:(WKPagesCollectionView*)self.collectionView willRemoveCellAtIndexPath:indexPath]; - ///Animation - [self.collectionView performBatchUpdates:^{ - [self.collectionView deleteItemsAtIndexPaths:@[indexPath,]]; - } completion:^(BOOL finished) { - - }]; + +- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate +{ + if (self.state == WKPagesCollectionViewCellStateNormal) { + CGFloat slideDistance = scrollView.frame.size.width / 6; + if (scrollView.contentOffset.x >= slideDistance) { + [self removeCurrentCell]; } } } -#pragma mark - Image --(UIImage*)makeGradientImage{ - UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 1.0f); - CGContextRef context=UIGraphicsGetCurrentContext(); - CGContextSaveGState(context); - - CGGradientRef myGradient; - CGColorSpaceRef myColorspace; - size_t num_locations = 2; - CGFloat locations[2] = { 0.0, 1.0 }; - CGFloat components[8] = { 0.0,0.0,0.0, 0.0, // Start color - 0.0,0.0,0.0,1.0}; // End color - myColorspace = CGColorSpaceCreateDeviceRGB(); - myGradient = CGGradientCreateWithColorComponents (myColorspace, components, - locations, num_locations); - CGContextDrawLinearGradient(context, myGradient, CGPointMake(self.bounds.size.width/2, 100.0f), CGPointMake(self.bounds.size.width/2, self.bounds.size.height-100.0f), kCGGradientDrawsAfterEndLocation); - - UIImage* image=UIGraphicsGetImageFromCurrentImageContext(); - CGContextRestoreGState(context); - CGColorSpaceRelease(myColorspace); - CGGradientRelease(myGradient); - UIGraphicsEndImageContext(); - return image; -} + @end diff --git a/WKPagesScrollView/WKPagesCollectionView/WKPagesCollectionViewFlowLayout.h b/WKPagesScrollView/WKPagesCollectionView/WKPagesCollectionViewFlowLayout.h index 42518bc..b3d1e1b 100644 --- a/WKPagesScrollView/WKPagesCollectionView/WKPagesCollectionViewFlowLayout.h +++ b/WKPagesScrollView/WKPagesCollectionView/WKPagesCollectionViewFlowLayout.h @@ -9,9 +9,8 @@ #import #import "WK.h" -@interface WKPagesCollectionViewFlowLayout : UICollectionViewFlowLayout{ - -} +@interface WKPagesCollectionViewFlowLayout : UICollectionViewFlowLayout + @property (nonatomic,readonly) CGFloat pageHeight; @end diff --git a/WKPagesScrollView/WKPagesCollectionView/WKPagesCollectionViewFlowLayout.m b/WKPagesScrollView/WKPagesCollectionView/WKPagesCollectionViewFlowLayout.m index df1ccdc..be8209b 100644 --- a/WKPagesScrollView/WKPagesCollectionView/WKPagesCollectionViewFlowLayout.m +++ b/WKPagesScrollView/WKPagesCollectionView/WKPagesCollectionViewFlowLayout.m @@ -6,40 +6,28 @@ // Copyright (c) 2013年 秦 道平. All rights reserved. // - #import "WKPagesCollectionViewFlowLayout.h" #import "WKPagesCollectionView.h" + #define RotateDegree -60.0f + @interface WKPagesCollectionViewFlowLayout() -@property (nonatomic,retain) NSMutableArray* deleteIndexPaths; -@property (nonatomic,retain) NSMutableArray* insertIndexPaths; + +@property (nonatomic,strong) NSMutableArray *deleteIndexPaths; +@property (nonatomic,strong) NSMutableArray *insertIndexPaths; + @end -@implementation WKPagesCollectionViewFlowLayout{ - -} --(id)init{ - self=[super init]; - if (self){ - - } - return self; -} --(void)prepareLayout + +@implementation WKPagesCollectionViewFlowLayout + +#pragma mark - UICollectionViewLayout (UISubclassingHooks) + +- (CGSize)collectionViewContentSize { - [super prepareLayout]; - self.itemSize=[UIScreen mainScreen].bounds.size; - self.minimumLineSpacing=-1*(self.itemSize.height-WKPagesCollectionViewPageSpacing); - self.scrollDirection=UICollectionViewScrollDirectionVertical; -} --(CGFloat)pageHeight{ - return self.itemSize.height; -} --(CGSize)collectionViewContentSize{ + NSInteger numberOfItems = [self.collectionView numberOfItemsInSection:0]; + CGFloat contentHeight = numberOfItems*self.pageHeight + self.self.minimumLineSpacing*(numberOfItems - 1); + contentHeight = fmaxf(contentHeight, self.collectionView.frame.size.height); - int numberOfItems=[self.collectionView numberOfItemsInSection:0]; -// CGFloat topMargin=[(WKPagesCollectionView*)self.collectionView topOffScreenMargin]; - CGFloat contentHeight=numberOfItems*self.pageHeight+self.self.minimumLineSpacing*(numberOfItems-1); - contentHeight=fmaxf(contentHeight, self.collectionView.frame.size.height); return CGSizeMake(self.collectionView.frame.size.width,contentHeight); } @@ -47,80 +35,107 @@ - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)oldBounds { return YES; } --(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)path + +#pragma mark - UICollectionViewLayout (UIUpdateSupportHooks) + +- (void)prepareLayout +{ + [super prepareLayout]; + + self.minimumLineSpacing = -1*(self.itemSize.height-WKPagesCollectionViewPageSpacing); + self.scrollDirection = UICollectionViewScrollDirectionVertical; +} + +- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)path { -// NSLog(@"layoutAttributesForItemAtIndexPath:%d",path.row); - UICollectionViewLayoutAttributes* attributes=[super layoutAttributesForItemAtIndexPath:path]; + UICollectionViewLayoutAttributes* attributes = [super layoutAttributesForItemAtIndexPath:path]; [self makeRotateTransformForAttributes:attributes]; + return attributes; } --(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect + +-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { -// NSLog(@"layoutAttributesForElementsInRect:%@",NSStringFromCGRect(rect)); - NSArray* array = [super layoutAttributesForElementsInRect:rect]; - for (UICollectionViewLayoutAttributes* attributes in array) { + NSArray *layoutAttributes = [super layoutAttributesForElementsInRect:rect]; + for (UICollectionViewLayoutAttributes *attributes in layoutAttributes) { [self makeRotateTransformForAttributes:attributes]; } - return array; + + return layoutAttributes; } -#pragma mark Collection Update --(void)prepareForCollectionViewUpdates:(NSArray *)updateItems{ -// NSLog(@"prepareForCollectionViewUpdates"); + +- (void)prepareForCollectionViewUpdates:(NSArray *)updateItems +{ [super prepareForCollectionViewUpdates:updateItems]; - self.deleteIndexPaths=[NSMutableArray array]; - self.insertIndexPaths=[NSMutableArray array]; - for (UICollectionViewUpdateItem* updateItem in updateItems) { - if (updateItem.updateAction==UICollectionUpdateActionDelete){ + self.deleteIndexPaths = [NSMutableArray array]; + self.insertIndexPaths = [NSMutableArray array]; + for (UICollectionViewUpdateItem *updateItem in updateItems) { + if (updateItem.updateAction == UICollectionUpdateActionDelete){ [self.deleteIndexPaths addObject:updateItem.indexPathBeforeUpdate]; } - if (updateItem.updateAction==UICollectionUpdateActionInsert){ + if (updateItem.updateAction == UICollectionUpdateActionInsert){ [self.insertIndexPaths addObject:updateItem.indexPathAfterUpdate]; } } } --(void)finalizeCollectionViewUpdates{ -// NSLog(@"finalizeCollectionViewUpdates"); + +- (void)finalizeCollectionViewUpdates +{ [super finalizeCollectionViewUpdates]; - self.deleteIndexPaths=nil; - self.insertIndexPaths=nil; + self.deleteIndexPaths = nil; + self.insertIndexPaths = nil; } --(UICollectionViewLayoutAttributes*)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath{ - UICollectionViewLayoutAttributes* attributes=[super initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath]; -// NSLog(@"initialLayoutAttributesForAppearingItemAtIndexPath:%d",itemIndexPath.row); + +- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath +{ + UICollectionViewLayoutAttributes *attributes = [super initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath]; + if ([self.insertIndexPaths containsObject:itemIndexPath]){ if (!attributes) - attributes=[self layoutAttributesForItemAtIndexPath:itemIndexPath]; + attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath]; CATransform3D rotateTransform=WKFlipCATransform3DPerspectSimpleWithRotate(-90.0f); attributes.transform3D=rotateTransform; } + return attributes; } --(UICollectionViewLayoutAttributes*)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath{ - NSLog(@"finalLayoutAttributesForDisappearingItemAtIndexPath:%d",itemIndexPath.row); - UICollectionViewLayoutAttributes* attributes=[super finalLayoutAttributesForDisappearingItemAtIndexPath:itemIndexPath]; + +- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath +{ + UICollectionViewLayoutAttributes *attributes = [super finalLayoutAttributesForDisappearingItemAtIndexPath:itemIndexPath]; + if ([self.deleteIndexPaths containsObject:itemIndexPath]){ if (!attributes){ - attributes=[self layoutAttributesForItemAtIndexPath:itemIndexPath]; + attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath]; } - CATransform3D moveTransform=CATransform3DMakeTranslation(-320.0f, 0.0f, 0.0f); - attributes.transform3D=CATransform3DConcat(attributes.transform3D, moveTransform); - } - return attributes; + CATransform3D moveTransform = CATransform3DMakeTranslation(-self.itemSize.width, 0.0, 0.0); + attributes.transform3D = CATransform3DConcat(attributes.transform3D, moveTransform); + } + return attributes; +} + +#pragma mark - + +- (CGFloat)pageHeight +{ + return self.itemSize.height; } -///As attribute set up a new perspective --(void)makeRotateTransformForAttributes:(UICollectionViewLayoutAttributes*)attributes{ - attributes.zIndex=attributes.indexPath.row;///To set the zIndex,Otherwise, there will be NO blocking order - CGFloat distance=attributes.frame.origin.y-self.collectionView.contentOffset.y; + +#pragma mark - + +// As attribute set up a new perspective +- (void)makeRotateTransformForAttributes:(UICollectionViewLayoutAttributes *)attributes +{ + attributes.zIndex = attributes.indexPath.row; //To set the zIndex,Otherwise, there will be NO blocking order + CGFloat distance = attributes.frame.origin.y - self.collectionView.contentOffset.y; CGFloat normalizedDistance = distance / self.collectionView.frame.size.height; - normalizedDistance=fmaxf(normalizedDistance, 0.0f); - CGFloat rotate=RotateDegree+20.0f*normalizedDistance; - //CGFloat rotate=RotateDegree; -// NSLog(@"makeRotateTransformForAttributes:row:%d,normalizedDistance:%f,rotate:%f", -// attributes.indexPath.row,normalizedDistance,rotate); - ///Angle and angle will cross a small cell, even if you set zIndex is useless here to set the angle of the bottom of the cell is growing - CATransform3D rotateTransform=WKFlipCATransform3DPerspectSimpleWithRotate(rotate); - attributes.transform3D=rotateTransform; - + normalizedDistance = fmaxf(normalizedDistance, 0.0); + CGFloat rotate = RotateDegree+ 20.0*normalizedDistance; + + // Angle and angle will cross a small cell, even if you set zIndex is useless here to set the angle of the bottom of the cell is growing + CATransform3D rotateTransform = WKFlipCATransform3DPerspectSimpleWithRotate(rotate); + attributes.transform3D = rotateTransform; } + @end \ No newline at end of file