iOS模仿电子书首页实现书架布局样式

内容摘要
本文实现了类似电子书首页,用来展示图书或小说的布局页面,书架列表【iPhone6模拟器】,屏幕尺寸还没进行适配,只是做个简单的demo【纯代码实现方式】

实现采用的是UICollectionV
文章正文

本文实现了类似电子书首页,用来展示图书或小说的布局页面,书架列表【iPhone6模拟器】,屏幕尺寸还没进行适配,只是做个简单的demo【纯代码实现方式】

实现采用的是UICollectionView和UICollectionViewFlowLayout。关于UICollectionView的详细讲解请参考

 

一、实现layout的DecorationView


//
// FWBookShelfDecarationViewCollectionReusableView.h
// FWPersonalApp
//
// Created by hzkmn on 16/2/18.
// Copyright © 2016年 ForrstWoo. All rights reserved.
//

#import <UIKit/UIKit.h>

extern NSInteger const kDecorationViewHeight;


@interface FWBookShelfDecarationView : UICollectionReusableView

@end

//
// FWBookShelfDecarationViewCollectionReusableView.m
// FWPersonalApp
//
// Created by hzkmn on 16/2/18.
// Copyright © 2016年 ForrstWoo. All rights reserved.
//



#import "FWBookShelfDecarationView.h"

NSInteger const kDecorationViewHeight = 216;

@implementation FWBookShelfDecarationView

- (instancetype)initWithFrame:(CGRect)frame
{
  if (self = [super initWithFrame:frame])
  {
    UIImageView *img = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, screenSize.width, kDecorationViewHeight)];
    img.image = [UIImage imageNamed:@"boolshelf.png"];
    [self addSubview:img];
  }
  
  return self;
}
@end
 

FWBookShelfDecarationView类非常简单只是定义了Decarationview的背景图片,图上。

二、下载及导入可重新排序的第三方layout,用于我们移动图书后重新布局
在实际项目中你或许会遇到在一个集合视图中移动一项到另外一个位置,那么此时我们需要对视图中的元素进行重新排序,今天推荐一个很好用的第三方类LXReorderableCollectionViewFlowLayout【点此链接进入GITHUB】

下面附上实现代码

//
// LXReorderableCollectionViewFlowLayout.h
//
// Created by Stan Chang Khin Boon on 1/10/12.
// Copyright (c) 2012 d--buzz. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface LXReorderableCollectionViewFlowLayout : UICollectionViewFlowLayout <UIGestureRecognizerDelegate>

@property (assign, nonatomic) CGFloat scrollingSpeed;
@property (assign, nonatomic) UIEdgeInsets scrollingTriggerEdgeInsets;
@property (strong, nonatomic, readonly) UILongPressGestureRecognizer *longPressGestureRecognizer;
@property (strong, nonatomic, readonly) UIPanGestureRecognizer *panGestureRecognizer;

- (void)setUpGestureRecognizersOnCollectionView __attribute__((deprecated("Calls to setUpGestureRecognizersOnCollectionView method are not longer needed as setup are done automatically through KVO.")));

@end

@protocol LXReorderableCollectionViewDataSource <UICollectionViewDataSource>

@optional

- (void)collectionView:(UICollectionView *)collectionView itemAtIndexPath:(NSIndexPath *)fromIndexPath willMoveToIndexPath:(NSIndexPath *)toIndexPath;
- (void)collectionView:(UICollectionView *)collectionView itemAtIndexPath:(NSIndexPath *)fromIndexPath didMoveToIndexPath:(NSIndexPath *)toIndexPath;

- (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath;
- (BOOL)collectionView:(UICollectionView *)collectionView itemAtIndexPath:(NSIndexPath *)fromIndexPath canMoveToIndexPath:(NSIndexPath *)toIndexPath;

@end

@protocol LXReorderableCollectionViewDelegateFlowLayout <UICollectionViewDelegateFlowLayout>
@optional

- (void)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout willBeginDraggingItemAtIndexPath:(NSIndexPath *)indexPath;
- (void)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout didBeginDraggingItemAtIndexPath:(NSIndexPath *)indexPath;
- (void)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout willEndDraggingItemAtIndexPath:(NSIndexPath *)indexPath;
- (void)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout didEndDraggingItemAtIndexPath:(NSIndexPath *)indexPath;

@end
//
// LXReorderableCollectionViewFlowLayout.m
//
// Created by Stan Chang Khin Boon on 1/10/12.
// Copyright (c) 2012 d--buzz. All rights reserved.
//

#import "LXReorderableCollectionViewFlowLayout.h"
#import <QuartzCore/QuartzCore.h>
#import <objc/runtime.h>

#define LX_FRAMES_PER_SECOND 60.0

#ifndef CGGEOMETRY_LXSUPPORT_H_
CG_INLINE CGPoint
LXS_CGPointAdd(CGPoint point1, CGPoint point2) {
  return CGPointMake(point1.x + point2.x, point1.y + point2.y);
}
#endif

typedef NS_ENUM(NSInteger, LXScrollingDirection) {
  LXScrollingDirectionUnknown = 0,
  LXScrollingDirectionUp,
  LXScrollingDirectionDown,
  LXScrollingDirectionLeft,
  LXScrollingDirectionRight
};

static NSString * const kLXScrollingDirectionKey = @"LXScrollingDirection";
static NSString * const kLXCollectionViewKeyPath = @"collectionView";

@interface CADisplayLink (LX_userInfo)
@property (nonatomic, copy) NSDictionary *LX_userInfo;
@end

@implementation CADisplayLink (LX_userInfo)
- (void) setLX_userInfo:(NSDictionary *) LX_userInfo {
  objc_setAssociatedObject(self, "LX_userInfo", LX_userInfo, OBJC_ASSOCIATION_COPY);
}

- (NSDictionary *) LX_userInfo {
  return objc_getAssociatedObject(self, "LX_userInfo");
}
@end

@interface UICollectionViewCell (LXReorderableCollectionViewFlowLayout)

- (UIImage *)LX_rasterizedImage;

@end

@implementation UICollectionViewCell (LXReorderableCollectionViewFlowLayout)

- (UIImage *)LX_rasterizedImage {
  UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.isOpaque, 0.0f);
  [self.layer renderInContext:UIGraphicsGetCurrentContext()];
  UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  UIGraphicsEndImageContext();
  return image;
}

@end

@interface LXReorderableCollectionViewFlowLayout ()

@property (strong, nonatomic) NSIndexPath *selectedItemIndexPath;
@property (strong, nonatomic) UIView *currentView;
@property (assign, nonatomic) CGPoint currentViewCenter;
@property (assign, nonatomic) CGPoint panTranslationInCollectionView;
@property (strong, nonatomic) CADisplayLink *displayLink;

@property (assign, nonatomic, readonly) id<LXReorderableCollectionViewDataSource> dataSource;
@property (assign, nonatomic, readonly) id<LXReorderableCollectionViewDelegateFlowLayout> delegate;

@end

@implementation LXReorderableCollectionViewFlowLayout

- (void)setDefaults {
  _scrollingSpeed = 300.0f;
  _scrollingTriggerEdgeInsets = UIEdgeInsetsMake(50.0f, 50.0f, 50.0f, 50.0f);
}

- (void)setupCollectionView {
  _longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self
                                        action:@selector(handleLongPressGesture:)];
  _longPressGestureRecognizer.delegate = self;
  
  // Links the default long press gesture recognizer to the custom long press gesture recognizer we are creating now
  // by enforcing failure dependency so that they doesn't clash.
  for (UIGestureRecognizer *gestureRecognizer in self.collectionView.gestureRecognizers) {
    if ([gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) {
      [gestureRecognizer requireGestureRecognizerToFail:_longPressGestureRecognizer];
    }
  }
  
  [self.collectionView addGestureRecognizer:_longPressGestureRecognizer];
  
  _panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self
                                  action:@selector(handlePanGesture:)];
  _panGestureRecognizer.delegate = self;
  [self.collectionView addGestureRecognizer:_panGestureRecognizer];

  // Useful in multiple scenarios: one common scenario being when the Notification Center drawer is pulled down
  [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleApplicationWillResignActive:) name: UIApplicationWillResignActiveNotification object:nil];
}

- (id)init {
  self = [super init];
  if (self) {
    [self setDefaults];
    [self addObserver:self forKeyPath:kLXCollectionViewKeyPath options:NSKeyValueObservingOptionNew context:nil];
  }
  return self;
}

- (id)initWithCoder:(NSCoder *)aDecoder {
  self = [super initWithCoder:aDecoder];
  if (self) {
    [self setDefaults];
    [self addObserver:self forKeyPath:kLXCollectionViewKeyPath options:NSKeyValueObservingOptionNew context:nil];
  }
  return self;
}

- (void)dealloc {
  [self invalidatesScrollTimer];
  [self removeObserver:self forKeyPath:kLXCollectionViewKeyPath];
  [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillResignActiveNotification object:nil];
}

- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes {
  if ([layoutAttributes.indexPath isEqual:self.selectedItemIndexPath]) {
    layoutAttributes.hidden = YES;
  }
}

- (id<LXReorderableCollectionViewDataSource>)dataSource {
  return (id<LXReorderableCollectionViewDataSource>)self.collectionView.dataSource;
}

- (id<LXReorderableCollectionViewDelegateFlowLayout>)delegate {
  return (id<LXReorderableCollectionViewDelegateFlowLayout>)self.collectionView.delegate;
}

- (void)invalidateLayoutIfNecessary {
  NSIndexPath *newIndexPath = [self.collectionView indexPathForItemAtPoint:self.currentView.center];
  NSIndexPath *previousIndexPath = self.selectedItemIndexPath;
  
  if ((newIndexPath == nil) || [newIndexPath isEqual:previousIndexPath]) {
    return;
  }
  
  if ([self.dataSource respondsToSelector:@selector(collectionView:itemAtIndexPath:canMoveToIndexPath:)] &&
    ![self.dataSource collectionView:self.collectionView itemAtIndexPath:previousIndexPath canMoveToIndexPath:newIndexPath]) {
    return;
  }
  
  self.selectedItemIndexPath = newIndexPath;
  
  if ([self.dataSource respondsToSelector:@selector(collectionView:itemAtIndexPath:willMoveToIndexPath:)]) {
    [self.dataSource collectionView:self.collectionView itemAtIndexPath:previousIndexPath willMoveToIndexPath:newIndexPath];
  }

  __weak typeof(self) weakSelf = self;
  [self.collectionView performBatchUpdates:^{
    __strong typeof(self) strongSelf = weakSelf;
    if (strongSelf) {
      [strongSelf.collectionView deleteItemsAtIndexPaths:@[ previousIndexPath ]];
      [strongSelf.collectionView insertItemsAtIndexPaths:@[ newIndexPath ]];
    }
  } completion:^(BOOL finished) {
    __strong typeof(self) strongSelf = weakSelf;
    if ([strongSelf.dataSource respondsToSelector:@selector(collectionView:itemAtIndexPath:didMoveToIndexPath:)]) {
      [strongSelf.dataSource collectionView:strongSelf.collectionView itemAtIndexPath:previousIndexPath didMoveToIndexPath:newIndexPath];
    }
  }];
}

- (void)invalidatesScrollTimer {
  if (!self.displayLink.paused) {
    [self.displayLink invalidate];
  }
  self.displayLink = nil;
}

- (void)setupScrollTimerInDirection:(LXScrollingDirection)direction {
  if (!self.displayLink.paused) {
    LXScrollingDirection oldDirection = [self.displayLink.LX_userInfo[kLXScrollingDirectionKey] integerValue];

    if (direction == oldDirection) {
      return;
    }
  }
  
  [self invalidatesScrollTimer];

  self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleScroll:)];
  self.displayLink.LX_userInfo = @{ kLXScrollingDirectionKey : @(direction) };

  [self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}

#pragma mark - Target/Action methods

// Tight loop, allocate memory sparely, even if they are stack allocation.
- (void)handleScroll:(CADisplayLink *)displayLink {
  LXScrollingDirection direction = (LXScrollingDirection)[displayLink.LX_userInfo[kLXScrollingDirectionKey] integerValue];
  if (direction == LXScrollingDirectionUnknown) {
    return;
  }
  
  CGSize frameSize = self.collectionView.bounds.size;
  CGSize contentSize = self.collectionView.contentSize;
  CGPoint contentOffset = self.collectionView.contentOffset;
  UIEdgeInsets contentInset = self.collectionView.contentInset;
  // Important to have an integer `distance` as the `contentOffset` property automatically gets rounded
  // and it would diverge from the view's center resulting in a "cell is slipping away under finger"-bug.
  CGFloat distance = rint(self.scrollingSpeed / LX_FRAMES_PER_SECOND);
  CGPoint translation = CGPointZero;
  
  switch(direction) {
    case LXScrollingDirectionUp: {
      distance = -distance;
      CGFloat minY = 0.0f - contentInset.top;
      
      if ((contentOffset.y + distance) <= minY) {
        distance = -contentOffset.y - contentInset.top;
      }
      
      translation = CGPointMake(0.0f, distance);
    } break;
    case LXScrollingDirectionDown: {
      CGFloat maxY = MAX(contentSize.height, frameSize.height) - frameSize.height + contentInset.bottom;
      
      if ((contentOffset.y + distance) >= maxY) {
        distance = maxY - contentOffset.y;
      }
      
      translation = CGPointMake(0.0f, distance);
    } break;
    case LXScrollingDirectionLeft: {
      distance = -distance;
      CGFloat minX = 0.0f - contentInset.left;
      
      if ((contentOffset.x + distance) <= minX) {
        distance = -contentOffset.x - contentInset.left;
      }
      
      translation = CGPointMake(distance, 0.0f);
    } break;
    case LXScrollingDirectionRight: {
      CGFloat maxX = MAX(contentSize.width, frameSize.width) - frameSize.width + contentInset.right;
      
      if ((contentOffset.x + distance) >= maxX) {
        distance = maxX - contentOffset.x;
      }
      
      translation = CGPointMake(distance, 0.0f);
    } break;
    default: {
      // Do nothing...
    } break;
  }
  
  self.currentViewCenter = LXS_CGPointAdd(self.currentViewCenter, translation);
  self.currentView.center = LXS_CGPointAdd(self.currentViewCenter, self.panTranslationInCollectionView);
  self.collectionView.contentOffset = LXS_CGPointAdd(contentOffset, translation);
}


- (void)handleLongPressGesture:(UILongPressGestureRecognizer *)gestureRecognizer {
  switch(gestureRecognizer.state) {
    case UIGestureRecognizerStateBegan: {
      NSIndexPath *currentIndexPath = [self.collectionView indexPathForItemAtPoint:[gestureRecognizer locationInView:self.collectionView]];
      
      if ([self.dataSource respondsToSelector:@selector(collectionView:canMoveItemAtIndexPath:)] &&
        ![self.dataSource collectionView:self.collectionView canMoveItemAtIndexPath:currentIndexPath]) {
        return;
      }
      
      self.selectedItemIndexPath = currentIndexPath;
      
      if ([self.delegate respondsToSelector:@selector(collectionView:layout:willBeginDraggingItemAtIndexPath:)]) {
        [self.delegate collectionView:self.collectionView layout:self willBeginDraggingItemAtIndexPath:self.selectedItemIndexPath];
      }
      
      UICollectionViewCell *collectionViewCell = [self.collectionView cellForItemAtIndexPath:self.selectedItemIndexPath];
      
      self.currentView = [[UIView alloc] initWithFrame:collectionViewCell.frame];
      
      collectionViewCell.highlighted = YES;
      UIImageView *highlightedImageView = [[UIImageView alloc] initWithImage:[collectionViewCell LX_rasterizedImage]];
      highlightedImageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
      highlightedImageView.alpha = 1.0f;
      
      collectionViewCell.highlighted = NO;
      UIImageView *imageView = [[UIImageView alloc] initWithImage:[collectionViewCell LX_rasterizedImage]];
      imageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
      imageView.alpha = 0.0f;
      
      [self.currentView addSubview:imageView];
      [self.currentView addSubview:highlightedImageView];
      [self.collectionView addSubview:self.currentView];
      
      self.currentViewCenter = self.currentView.center;
      
      __weak typeof(self) weakSelf = self;
      [UIView
       animateWithDuration:0.3
       delay:0.0
       options:UIViewAnimationOptionBeginFromCurrentState
       animations:^{
         __strong typeof(self) strongSelf = weakSelf;
         if (strongSelf) {
           strongSelf.currentView.transform = CGAffineTransformMakeScale(1.1f, 1.1f);
           highlightedImageView.alpha = 0.0f;
           imageView.alpha = 1.0f;
         }
       }
       completion:^(BOOL finished) {
         __strong typeof(self) strongSelf = weakSelf;
         if (strongSelf) {
           [highlightedImageView removeFromSuperview];
           
           if ([strongSelf.delegate respondsToSelector:@selector(collectionView:layout:didBeginDraggingItemAtIndexPath:)]) {
             [strongSelf.delegate collectionView:strongSelf.collectionView layout:strongSelf didBeginDraggingItemAtIndexPath:strongSelf.selectedItemIndexPath];
           }
         }
       }];
      
      [self invalidateLayout];
    } break;
    case UIGestureRecognizerStateCancelled:
    case UIGestureRecognizerStateEnded: {
      NSIndexPath *currentIndexPath = self.selectedItemIndexPath;
      
      if (currentIndexPath) {
        if ([self.delegate respondsToSelector:@selector(collectionView:layout:willEndDraggingItemAtIndexPath:)]) {
          [self.delegate collectionView:self.collectionView layout:self willEndDraggingItemAtIndexPath:currentIndexPath];
        }
        
        self.selectedItemIndexPath = nil;
        self.currentViewCenter = CGPointZero;
        
        UICollectionViewLayoutAttributes *layoutAttributes = [self layoutAttributesForItemAtIndexPath:currentIndexPath];
        
        __weak typeof(self) weakSelf = self;
        [UIView
         animateWithDuration:0.3
         delay:0.0
         options:UIViewAnimationOptionBeginFromCurrentState
         animations:^{
           __strong typeof(self) strongSelf = weakSelf;
           if (strongSelf) {
             strongSelf.currentView.transform = CGAffineTransformMakeScale(1.0f, 1.0f);
             strongSelf.currentView.center = layoutAttributes.center;
           }
         }
         completion:^(BOOL finished) {
           __strong typeof(self) strongSelf = weakSelf;
           if (strongSelf) {
             [strongSelf.currentView removeFromSuperview];
             strongSelf.currentView = nil;
             [strongSelf invalidateLayout];
             
             if ([strongSelf.delegate respondsToSelector:@selector(collectionView:layout:didEndDraggingItemAtIndexPath:)]) {
               [strongSelf.delegate collectionView:strongSelf.collectionView layout:strongSelf didEndDraggingItemAtIndexPath:currentIndexPath];
             }
           }
         }];
      }
    } break;
      
    default: break;
  }
}

- (void)handlePanGesture:(UIPanGestureRecognizer *)gestureRecognizer {
  switch (gestureRecognizer.state) {
    case UIGestureRecognizerStateBegan:
    case UIGestureRecognizerStateChanged: {
      self.panTranslationInCollectionView = [gestureRecognizer translationInView:self.collectionView];
      CGPoint viewCenter = self.currentView.center = LXS_CGPointAdd(self.currentViewCenter, self.panTranslationInCollectionView);
      
      [self invalidateLayoutIfNecessary];
      
      switch (self.scrollDirection) {
        case UICollectionViewScrollDirectionVertical: {
          if (viewCenter.y < (CGRectGetMinY(self.collectionView.bounds) + self.scrollingTriggerEdgeInsets.top)) {
            [self setupScrollTimerInDirection:LXScrollingDirectionUp];
          } else {
            if (viewCenter.y > (CGRectGetMaxY(self.collectionView.bounds) - self.scrollingTriggerEdgeInsets.bottom)) {
              [self setupScrollTimerInDirection:LXScrollingDirectionDown];
            } else {
              [self invalidatesScrollTimer];
            }
          }
        } break;
        case UICollectionViewScrollDirectionHorizontal: {
          if (viewCenter.x < (CGRectGetMinX(self.collectionView.bounds) + self.scrollingTriggerEdgeInsets.left)) {
            [self setupScrollTimerInDirection:LXScrollingDirectionLeft];
          } else {
            if (viewCenter.x > (CGRectGetMaxX(self.collectionView.bounds) - self.scrollingTriggerEdgeInsets.right)) {
              [self setupScrollTimerInDirection:LXScrollingDirectionRight];
            } else {
              [self invalidatesScrollTimer];
            }
          }
        } break;
      }
    } break;
    case UIGestureRecognizerStateCancelled:
    case UIGestureRecognizerStateEnded: {
      [self invalidatesScrollTimer];
    } break;
    default: {
      // Do nothing...
    } break;
  }
}

#pragma mark - UICollectionViewLayout overridden methods

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
  NSArray *layoutAttributesForElementsInRect = [super layoutAttributesForElementsInRect:rect];
  
  for (UICollectionViewLayoutAttributes *layoutAttributes in layoutAttributesForElementsInRect) {
    switch (layoutAttributes.representedElementCategory) {
      case UICollectionElementCategoryCell: {
        [self applyLayoutAttributes:layoutAttributes];
      } break;
      default: {
        // Do nothing...
      } break;
    }
  }
  
  return layoutAttributesForElementsInRect;
}

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
  UICollectionViewLayoutAttributes *layoutAttributes = [super layoutAttributesForItemAtIndexPath:indexPath];
  
  switch (layoutAttributes.representedElementCategory) {
    case UICollectionElementCategoryCell: {
      [self applyLayoutAttributes:layoutAttributes];
    } break;
    default: {
      // Do nothing...
    } break;
  }
  
  return layoutAttributes;
}

#pragma mark - UIGestureRecognizerDelegate methods

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
  if ([self.panGestureRecognizer isEqual:gestureRecognizer]) {
    return (self.selectedItemIndexPath != nil);
  }
  return YES;
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
  if ([self.longPressGestureRecognizer isEqual:gestureRecognizer]) {
    return [self.panGestureRecognizer isEqual:otherGestureRecognizer];
  }
  
  if ([self.panGestureRecognizer isEqual:gestureRecognizer]) {
    return [self.longPressGestureRecognizer isEqual:otherGestureRecognizer];
  }
  
  return NO;
}

#pragma mark - Key-Value Observing methods

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
  if ([keyPath isEqualToString:kLXCollectionViewKeyPath]) {
    if (self.collectionView != nil) {
      [self setupCollectionView];
    } else {
      [self invalidatesScrollTimer];
    }
  }
}

#pragma mark - Notifications

- (void)handleApplicationWillResignActive:(NSNotification *)notification {
  self.panGestureRecognizer.enabled = NO;
  self.panGestureRecognizer.enabled = YES;
}

#pragma mark - Depreciated methods

#pragma mark Starting from 0.1.0
- (void)setUpGestureRecognizersOnCollectionView {
  // Do nothing...
}

@end

效果图:

 

三、实现自己的layout

首先继承LXReorderableCollectionViewFlowLayout,让该类具有重新排序功能。

//
// FWBookshelfCollectionViewLayout.h
// FWPersonalApp
//
// Created by hzkmn on 16/2/18.
// Copyright © 2016年 ForrstWoo. All rights reserved.
//

#import "LXReorderableCollectionViewFlowLayout.h"

extern NSString * const FWBookshelfCollectionViewLayoutDecorationViewKind;

@interface FWBookshelfCollectionViewLayout : LXReorderableCollectionViewFlowLayout

@end

//
// FWBookshelfCollectionViewLayout.m
// FWPersonalApp
//
// Created by hzkmn on 16/2/18.
// Copyright © 2016年 ForrstWoo. All rights reserved.
//

#import "FWBookshelfCollectionViewLayout.h"

#import "FWBookShelfDecarationView.h"

NSString * const FWBookshelfCollectionViewLayoutDecorationViewKind = @"FWBookshelfCollectionViewLayoutDecorationViewKind";

@interface FWBookshelfCollectionViewLayout ()

@property (nonatomic, strong) NSDictionary *bookShelfRectanges;
@property NSInteger countOfRow;

@end

@implementation FWBookshelfCollectionViewLayout

- (void)prepareLayout
{
  [super prepareLayout];
  
  [self registerClass:[FWBookShelfDecarationView class] forDecorationViewOfKind:FWBookshelfCollectionViewLayoutDecorationViewKind];
  
  NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
  
  NSInteger itemCount = [self.collectionView numberOfItemsInSection:0];
  self.countOfRow = ceilf(itemCount / 3.0);
  for (int row = 0; row < self.countOfRow; row++)
  {
    dictionary[[NSIndexPath indexPathForItem:row inSection:0]] = [NSValue valueWithCGRect:CGRectMake(0, kDecorationViewHeight * row, screenSize.width, kDecorationViewHeight)];
  }
  
  self.bookShelfRectanges = [NSDictionary dictionaryWithDictionary:dictionary];
}

#pragma mark Runtime Layout Calculations
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
  // call super so flow layout can return default attributes for all cells, headers, and footers
  // NOTE: Flow layout has already taken care of the Cell view layout attributes! :)
  NSArray *array = [super layoutAttributesForElementsInRect:rect];
  
  // create a mutable copy so we can add layout attributes for any shelfs that
  // have frames that intersect the rect the CollectionView is interested in
  NSMutableArray *newArray = [array mutableCopy];
  //  NSLog(@"in rect:%@",NSStringFromCGRect(rect));
  // Add any decoration views (shelves) who's rect intersects with the
  // CGRect passed to the layout by the CollectionView
  [self.bookShelfRectanges enumerateKeysAndObjectsUsingBlock:^(id key, id shelfRect, BOOL *stop) {
    //    NSLog(@"[shelfRect CGRectValue]:%@",NSStringFromCGRect([shelfRect CGRectValue]));
    
    if (CGRectIntersectsRect([shelfRect CGRectValue], rect))
    {
      UICollectionViewLayoutAttributes *shelfAttributes =
      [self layoutAttributesForDecorationViewOfKind:FWBookshelfCollectionViewLayoutDecorationViewKind
                       atIndexPath:key];
      [newArray addObject:shelfAttributes];
    }
  }];
  
  for (int i = 0; i < [self.collectionView numberOfItemsInSection:0]; i++)
  {
    NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
    
    [newArray addObject:[self layoutAttributesForItemAtIndexPath:indexPath]];
  }
  
  return [newArray copy];
}

- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
  //  NSLog(@"%@", NSStringFromCGSize([self screenSize]));375 667
  UICollectionViewLayoutAttributes *attris = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
  
  NSInteger currentRow = indexPath.item / 3;
  CGRect frame = CGRectMake(20 + (indexPath.item % 3) * (kCellWidth + 17.5), 35+ currentRow * (kCellHeight + 65), kCellWidth, kCellHeight);
  attris.frame = frame;
  attris.zIndex = 1;
  
  return attris;
}

- (UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString *)decorationViewKind atIndexPath:(NSIndexPath *)indexPath
{
  
  id shelfRect = self.bookShelfRectanges[indexPath];
  
  // this should never happen, but just in case...
  if (!shelfRect)
    return nil;
  
  UICollectionViewLayoutAttributes *attributes =
  [UICollectionViewLayoutAttributes layoutAttributesForDecorationViewOfKind:decorationViewKind
                                withIndexPath:indexPath];
  attributes.frame = [shelfRect CGRectValue];
  //  NSLog(@"UICollectionViewLayoutAttributes :.%@", NSStringFromCGRect([shelfRect CGRectValue]));
  
  attributes.zIndex = -1; // shelves go behind other views
  
  return attributes;
}

- (CGSize)collectionViewContentSize
{
  CGSize contentSize = CGSizeMake(self.collectionView.bounds.size.width, self.countOfRow * kDecorationViewHeight + 20);
  
  return contentSize;
}

@end

四、应用

//
// FWAncientPoetryCollectionViewController.m
// FWPersonalApp
//
// Created by hzkmn on 16/2/17.
// Copyright © 2016年 ForrstWoo. All rights reserved.
//



#import "FWAncientPoetryCollectionViewController.h"

#import "FWBookShelfDecarationView.h"
#import "FWBookshelfCollectionViewLayout.h"
#import "FWBookCategoryViewController.h"

@interface FWAncientPoetryCollectionViewController () <LXReorderableCollectionViewDataSource, LXReorderableCollectionViewDelegateFlowLayout>

@property (nonatomic, strong) NSMutableArray *books;

@end

@implementation FWAncientPoetryCollectionViewController

static NSString * const cellReuseIdentifier = @"Cell";
- (void)viewDidLoad {
  [super viewDidLoad];
  self.title = @"古籍";
  self.collectionView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"bookShelfBackground.png"]];
  [self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:cellReuseIdentifier];
  self.books = [[NSMutableArray alloc] initWithArray:[self bookNameOfAllBooks]];
}

- (NSArray *)bookNameOfAllBooks
{
 return [[FWDataManager getDataForPoetry] allKeys];
}

#pragma mark <UICollectionViewDataSource>

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
  return 1;
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
  return [self.books count];
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
  UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellReuseIdentifier forIndexPath:indexPath];
  UIImage *image = [UIImage imageNamed:self.books[indexPath.item]];
  cell.backgroundColor = [UIColor colorWithPatternImage:image];

  return cell;
}

- (void)collectionView:(UICollectionView *)collectionView itemAtIndexPath:(NSIndexPath *)fromIndexPath willMoveToIndexPath:(NSIndexPath *)toIndexPath
{
  NSString *theBookName = self.books[fromIndexPath.item];
  [self.books removeObjectAtIndex:fromIndexPath.item];
  [self.books insertObject:theBookName atIndex:toIndexPath.item];
}

#pragma mark - UICollectionViewDelegateFlowLayout

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
  return CGSizeMake(kCellWidth, kCellHeight);
}

- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
  FWBookCategoryViewController *vc = [[FWBookCategoryViewController alloc] initWithUrlString:[[FWDataManager getDataForPoetry] objectForKey:self.books[indexPath.item]]];
  [self.navigationController pushViewController:vc animated:YES];
}

- (void)didReceiveMemoryWarning {
  [super didReceiveMemoryWarning];

  [self.books removeAllObjects];
  self.books = nil;
}

@end

以上就是本文的全部内容,ios模仿实现书架效果,类似于手机上的掌上阅读软件的首页,希望本文对大家的学习有所帮助。


代码注释

作者:喵哥笔记

IDC笔记

学的不仅是技术,更是梦想!