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模仿实现书架效果,类似于手机上的掌上阅读软件的首页,希望本文对大家的学习有所帮助。

(0)

相关推荐

  • 深入解析iOS应用开发中九宫格视图布局的相关计算方法

    来看一个简单的例子: 复制代码 代码如下: /*  * 总列数  */ NSUInteger totalloc = 3; /*  * View的宽高  */ CGFloat shopW = 80; CGFloat shopH = 100; /*  * 每个View之间的间隔  */ CGFloat margin = (self.view.frame.size.width - totalloc * shopW) / (totalloc + 1); /*  * View的总个数  */ NSUInt

  • iOS App开发中扩展RCLabel组件进行基于HTML的文本布局

    iOS系统是一个十分注重用户体验的系统,在iOS系统中,用户交互的方案也十分多,然而要在label中的某部分字体中添加交互行为确实不容易的,如果使用其他类似Button的控件来模拟,文字的排版又将是一个解决十分困难的问题.这个问题的由来是项目中的一个界面中有一些广告位标签,而这些广告位的标签却是嵌在文本中的,当用户点击文字标签的位置时,会跳转到响应的广告页. CoreText框架和一些第三方库可以解决这个问题,但直接使用CoreText十分复杂,第三方库多注重于富文本的排版,对类似文字超链接的支

  • IOS实现自定义布局瀑布流

    瀑布流是电商应用展示商品通常采用的一种方式,如图示例 瀑布流的实现方式,通常有以下几种 通过UITableView实现(不常用) 通过UIScrollView实现(工作量较大) 通过UICollectionView实现(通常采用的方式) 一.UICollectionView基础 1.UICollectionView与UITableView有很多相似的地方,如 都通过数据源提供数据 都通过代理执行相关的事件 都可以自定义cell,且涉及到cell的重用 都继承自UIScrollView,具有滚动效

  • IOS封装自定义布局的方法

    一.概述 1.对于经常使用的控件或类,通常将其分装为一个单独的类来供外界使用,以此达到事半功倍的效果 2.由于分装的类不依赖于其他的类,所以若要使用该类,可直接将该类拖进项目文件即可 3.在进行分装的时候,通常需要用到代理设计模式 二.代理设计模式 1.代理设计模式的组成 客户类(通常作为代理):通常委托这是角色来完成业务逻辑 真实角色:将客户类的业务逻辑转化为方法列表,即代理协议 代理协议: 定义了需要实现的业务逻辑 定义了一组方法列表,包括必须实现的方法或选择实现的方法 代理协议是代理对象所

  • iOS App开发中Masonry布局框架的基本用法解析

    Masonry是一个轻量级的布局框架,拥有自己的描述语法,采用更优雅的链式语法封装自动布局,简洁明了并具有高可读性,而且同时支持 iOS 和 Max OS X.Masonry是一个用代码写iOS或OS界面的库,可以代替Auto layout.Masonry的github地址:https://github.com/SnapKit/Masonry Masonry使用讲解: mas_makeConstraints 是给view添加约束,约束有几种,分别是边距,宽,高,左上右下距离,基准线.添加过约束后

  • 详解iOS应用使用Storyboard布局时的IBOutlet与IBAction

    在图形界面编程时,解决的第一问题就是如何将静态界面与代码关联起来,或者说是代码如何与界面上的对象 通信, 代码如何操作界面上的对象.在iPhone平台上,引入了IBOutlet与IBAction.通过在变量前增加IBOutlet 来说明该变量将与界面上的某个UI对象对应,在方法前增加IBAction来说明该方法将与界面上的事件对应. 下面通过一个连接网络服务器(NetworkConnection)的例子来说明IBOutlet与IBAction. 界面上有host 与 port 的Text Fie

  • iOS开发之手动布局子视图

    手动布局子视图: 下面先看下效果图,我们今天要实现的效果: 这里我们默认用storyboard启动: 首先我们要在白色的屏幕上面创建一个父视图SuperView(蓝色的背景),在父视图里面创建四个小视图(橘黄色的背景) 下面看代码, 在SuperView.h文件里面: #import <UIKit/UIKit.h> @interface SuperView : UIView{ UIView * _view01; UIView * _view02; UIView * _view03; UIVie

  • iOS组件封装与自动布局自定义表情键盘

    下面的东西是编写自定义的表情键盘,话不多说,开门见山吧!下面主要用到的知识有MVC, iOS开发中的自动布局,自定义组件的封装与使用,Block回调,CoreData的使用.有的小伙伴可能会问写一个自定义表情键盘肿么这么麻烦?下面 将会介绍我们如何用上面提到的东西来定义我们的表情键盘的.下面的内容会比较多,这篇文章还是比较有料的. 还是那句话写技术博客是少不了代码的,下面会结合代码来回顾一下iOS的知识,本篇博文中用到的知识点在前面的博客中都能找到相应的内容,本篇 算是一个小小的功能整合.先来张

  • 详解iOS中UIView的layoutSubviews子视图布局方法使用

    概念 在UIView里面有一个方法layoutSubviews: 复制代码 代码如下: - (void)layoutSubviews;    // override point. called by layoutIfNeeded automatically. As of iOS 6.0, when constraints-based layout is used the base implementation applies the constraints-based layout, other

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

    本文实现了类似电子书首页,用来展示图书或小说的布局页面,书架列表[iPhone6模拟器],屏幕尺寸还没进行适配,只是做个简单的demo[纯代码实现方式] 实现采用的是UICollectionView和UICollectionViewFlowLayout.关于UICollectionView的详细讲解请参考 一.实现layout的DecorationView // // FWBookShelfDecarationViewCollectionReusableView.h // FWPersonalA

  • 微信小程序淘宝首页双排图片布局排版代码(推荐)

    效果图: 使用技术主要是flex布局,绝对定位布局,小程序前端页面开发,以及一些样式! 直接贴代码,都有详细注释,熟悉一下,方便以后小程序开发! wxml: <view class="taobaolist"> <block wx:for="{{imagelist}}" wx:key="item"> <view class="taobao-list"> <view class="

  • IOS UITableView和UITableViewCell的几种样式详细介绍

    IOS UITableView和UITableViewCell的几种样式详细介绍 今天要分享的是IOS开发中一个使用率非常高的一个控件-------UITableView,这两天正在使用tableview做信息的显示,在写代码时对tableview和tableviewcell的几种样式一直分不清楚,今天我详细的研究了一下,下面就跟大家分享一下: 一.系统自己的UITableView样式有两种: 1.UITableViewStylePlain: Plain样式的是方形的,充满你给的view.fra

  • iOS模仿微信长按识别二维码的多种方式

    参考:https://github.com/nglszs/BCQRcode 方式一: #import <UIKit/UIKit.h> @interface ViewController : UIViewController @end ************** #import "ViewController.h" @interface ViewController () @end @implementation ViewController - (void)viewDid

  • Bootstrap表单布局样式源代码

    Bootstrap提供了三种表单布局:垂直表单,内联表单和水平表单 创建垂直或基本表单: •·向父 <form> 元素添加 role="form". •·把标签和控件放在一个带有 class .form-group 的 <div> 中.这是获取最佳间距所必需的. •·向所有的文本元素 <input>.<textarea> 和 <select> 添加 class .form-control 内联表单: 内联表单中所有元素都向左对

  • Bootstrap表单布局样式代码

    废话不多说了,直接给大家贴代码了. <form class="form-horizontal" role="form"> <fieldset> <legend>配置数据源</legend> <div class="form-group"> <label class="col-sm-2 control-label" for="ds_host"&

  • Android开发中RecyclerView模仿探探左右滑动布局功能

    我在此基础上优化了部分代码, 添加了滑动回调, 可自定义性更强. 并且添加了点击按钮左右滑动的功能. 据说无图都不敢发文章了. 看图: 1:这种功能, 首先需要自己管理布局 继承 RecyclerView.LayoutManager , 显示自己管理布局, 比如最多显示4个view, 并且都是居中显示. 底部的View还需要进行缩放,平移操作. public class OverLayCardLayoutManager extends RecyclerView.LayoutManager { p

  • Android 利用ViewPager+GridView实现首页导航栏布局分页效果

    最近我尝试使用ViewPager+GridView实现的,看起来一切正常,废话不多说,具体代码如下: 如图是效果图 首先分析下思路 1.首先是怎么布局:整体是一个ViewPager将GridView作为一个View添加到ViewPager的adapter中,下方是圆点 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.a

  • iOS仿高德首页推拉效果实例代码

    目录 1.滑动view的调用 2.为视图添加滑动手势和tableview相关配置 3.设置允许同时响应多个手势 4.滑动相关逻辑处理 4.注意点 总结 上面是实现的效果,滑动的视图是新建的一个UIView子类 1.滑动view的调用 SlideView * slideView = [[SlideView alloc] initWithFrame:CGRectMake(0, kScreenHeight-140, kScreenWidth, kScreenHeight-100)]; slideVie

  • div结合css布局bbs首页(div+css布局入门)

    我把论坛首页分为header区,信息区,内容区,页脚区.首先用一大div把这些包含进来,主要是考虑到页面整体调节方便,比如要调成宽屏的或者是窄屏的,只要设置一下这个大div就可以了. 先把代码贴出来,供朋友们调试使用.css: 复制代码 代码如下: /* CSS Document */ body{ background-color:#F5F5F5; margin:0; padding:0; font-family : "Lucida Grande", Verdana, Lucida,

随机推荐