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


本文实现了类似电子书首页,用来展示图书或小说的布局页面,书架列表【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模仿实现书架效果,类似于手机上的掌上阅读软件的首页,希望本文对大家的学习有所帮助。



相关阅读:
JS实现仿QQ面板的手风琴效果折叠菜单代码
PHP自带方法验证邮箱是否存在
PHP正则表达式过滤html标签属性(DEMO)
jquery ajax结合thinkphp的getjson实现跨域的方法
ThinkPHP使用心得分享-分页类Page的用法
如何调试异步加载页面里包含的js文件
JavaScript编程中实现对象封装特性的实例讲解
JavaScript获取图片像素颜色并转换为box-shadow显示
QuiteRSS: Linux桌面的RSS阅读器 你值得拥有
C#数据绑定(DataBinding)简单实现方法
css教程制作css圆角边框(兼容全部浏览器)
如何解决PHP使用mysql_query查询超大结果集超内存问题
jQuery插件开发的两种方法及$.fn.extend的详解
win10耳机有电流声而且声音很大影响使用该怎么办?
快速导航
PHP MySQL HTML CSS JavaScript MSSQL AJAX .NET JSP Linux Mac ASP 服务器 CMS SQL jQuery C# C++ java Android IOS oracle MongoDB PostgreSQL SQLite 交通频道 G4722 G1875 G215 G569 G421 G6733 G7577 G8906 G1235 G4916 G7291 G1953 G245 G662 G1570 G6285 G719 G1836 G1346 G4781 G4908 G289 G6781 G9290 G7358 G1928 G1815 G325 G132 G4901 G6012 G6290 G7131 G5367 G184 G151 G5303 G1136 G6481 G7028 G575 G1744 G7660 G7693 G2344 G4937 G1234 G1814 G6252 G1492 G253 G2926 G883 G9275 G1231 G556 G241 G1306 G7646 G8103 G600 G1858 G9678 G6160 G7156 G825 G1125 G7249 G1809 G1350 G432 G9466 G7067 G785 G6404 G4663 G7008 G150 G823 G1514 G7529 G1201 G2353 G205 G7629 G9409 G6147 G677 G390 G8016 G9239 G456 G828 G8045 G491 G7145 G397 G7012 G1021 G6482 G2322 G7264 G1301 G9247 G96 G1294 G7133 G4824 G7005 G1653 G5307 G1213 G822 G4837 G1422 G411 G6227 G1571 G359 G1882 G6074 G7678 G21 G7077 G1272 G8918 G9645 G461 G1254 G1846 G8021 G7303 G1104 G76 G82 G621 G218 G8533 G2341 G8543 G555 G8013 G4802 G1364 G1153 G1342 G1861 G8905 G590 G4780 G668 G9261 G1304 G1638 G1395 G2914 G8003 G7158 G1833 G1873 G8128 G1856 G1841 G8709 G7346 G4612 G2103 G835 G8712 G381 G7240 G8932 G507 G29 G4054 G6273 G6752 G426 G211 G9473 G7119 G2333 G1567 G6153 G360 G4011 G5301 G7648 G8010 G8015 G6706 G614 G423 G8557 G9465 G72 G6018 G8901 G7030 G123

丹东 云霄 辽中 德阳 克拉玛依 惠山 招远 昭通 铁岭西 延吉西 军粮城北 定西 晋中 许昌东 郫县 诏安 七台河 高碑店东 南昌 延安 敦化 铜陵北 嵩明 鲘门 扬中 龙里北 舟山 洛阳 运城北 鞍山 西昌 邵阳北 绍兴 白山 三明 肇东 陵水 衡山西 嘉善 宜都 泰兴 泉州 汉口 东胜西 昌图西 锦州南 安阳东 怀化 黄南 亚龙湾 扬州 温州 南翔北 福安 金山北 永川东 安达 曲阜东 郑州西 天门 绍兴北 涪陵北 阳泉北 三亚 葫芦岛北 徐州 阳江 辽源 新泰 阿坝 孝感北 三穗 金寨 保山 高安 安阳 牟平 西双版纳 信阳 繁昌西 哈尔滨北 达州 新余 沈阳南 四平 扶余北 伊宁 郴州西 济源 水家湖 民权北 福鼎 如皋 奉化 全州南 安庆 太姥山 武汉 乐清 皮口 武昌 茂名 邯郸 资阳 马鞍山 三水南 泰安 包头东 衡阳东 南丰 仙桃西 安吉 罗源 山海关 平湖 惠州 资阳北 淄博 丹阳 莱州 巴东 关岭 盐城 锦州 格尔木 益阳 大英东 吉林 湛江 临安 襄汾西 渑池南 当涂东 辽阳 徐水 贺州 韶关 光明城 邯郸东 普安县 南江口 铜川 五龙背东 张家港 烟台南 萍乡北 青堆 长乐 江门 台州 衡水 湘潭北 闽清北 高邑西 盖州西 石柱县 潮汕 肇庆 泰康 邵东 湖州 余姚 平凉 宜宾 增城 沧州 都匀 防城港 鹰潭北 海东西 福田 余姚北 岳池 广州北 南安 蓬莱 瓦房店西 李石寨 葛店南 海安 无锡东 上饶 通辽 四会 桂林西 砀山南 兰州 滨海 龙口 绅坊 莱西 石林西 深圳 大连北 成都 上海西 孝感 杏树屯 德清 嘉兴

Copyright © 2016 phpStudy |