首页 > 代码库 > iOS阅读器实践系列(四)阅读视图方案

iOS阅读器实践系列(四)阅读视图方案

阅读过程中文章内容可能需要很多屏才能展示完,那么现在的问题是创建多少个视图来渲染内容。

经过考虑,决定用两个视图,利用手势来控制不断来回切换来实现。

首先定义两个私有的实体的view对象:

@property (nonatomic, strong) DisplyManagerView *buffer1;
@property (nonatomic, strong) DisplyManagerView *buffer2;

buffer1与buffer2就是用于渲染内容的两个视图。

其次还要定义一个用于区分是当前显示的视图还是被当前视图覆盖的视图(用于下一页或上一页的渲染):

@property (nonatomic, strong) DisplyManagerView *curDisplayView;
@property (nonatomic, strong) DisplyManagerView *nextDisplayView;

curDisplayView与nextDisplayView只是buffer1与buffer2的两个引用,并不是实际添加到上层视图上的两个实体的视图。它们只是把当前视图与下次要用以显示的视图作区分。可能这样看着有点绕,下面看代码就容易理解了。

实现阶段父视图初始化时要创建两个实体视图并给两个索引视图赋值并添加相应手势:

- (id)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame])
    { 
        [self createSubViews];
        [self addPanGesture];
    }
    return self;
}

如果初始化时要走其他的初始化方法,就添加在其他的初始化方法中。

- (void)createSubViews
{
    _buffer2 = [[DisplyManagerView alloc] initWithFrame:self.bounds];
    [_buffer2 createBottomView];
    TypographicManager *aManager = [TypographicManager getInstanse];
    [_buffer2 setBgColor:aManager.backgroundColor];
    [self addSubview:_buffer2];
    _nextDisplayView = _buffer2;
    
    _buffer1 = [[DisplyManagerView alloc] initWithFrame:self.bounds];
    [_buffer1 createBottomView];
    [_buffer1 setBgColor:aManager.backgroundColor];
    [self addSubview:_buffer1];
    _curDisplayView = _buffer1;
}
- (void)addPanGesture
{
    self.userInteractionEnabled = YES;
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
    pan.delegate = self;
    [self addGestureRecognizer:pan];
    
    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tap:)];
    tap.delegate = self;
    [self addGestureRecognizer:tap];
}

这里我们只解释滑动手势的相应方法:- (void)pan:(UIPanGestureRecognizer *)panGes{

    CGPoint pointer = [panGes locationInView:self];
    if (panGes.state == UIGestureRecognizerStateBegan)      //滑动开始
    {
        _startX = pointer.x;
        _scrollingX = pointer.x;
        _displyViewX = _curDisplayView.frame.origin.x;
    }
    else if (panGes.state == UIGestureRecognizerStateChanged)   //滑动过程中(滑动过程中一直处于此状态,即滑动过程中每次调用该方法都会进入该条件)
    {
        CGPoint pointer = [panGes locationInView:self];
        if (pointer.x > _scrollingX) {
            _instantDirection = ScrollingInstantDirectionRight;
        }
        else if(pointer.x == _scrollingX) {
            _instantDirection = ScrollingInstantDirectionNone;
        }
        else {
            _instantDirection = ScrollingInstantDirectionLeft;
        }
        
        _scrollingX = pointer.x;     //记录瞬时触碰点的坐标
        
        if (self.delegate != nil && [self.delegate respondsToSelector:@selector(typographicViewBeginScroll)])
        {
            [self.delegate typographicViewBeginScroll];
        }    

//以下涉及到翻页方式,利用了装饰模式 TypographicViewFlipOverManager *flipOverManager = TypographicViewFlipOverManager.instance; flipOverManager.decorateView.typographicView = self; [flipOverManager displyViewMoveToX:pointer.x - _startX]; } else if (panGes.state == UIGestureRecognizerStateEnded) //滑动结束 { if (pointer.x - _startX > 0) { if (_instantDirection == ScrollingInstantDirectionLeft) { [self resetView:pointer.x - _startX isRight:NO]; } else { [self resetView:pointer.x - _startX isRight:YES]; } } else { if (_instantDirection == ScrollingInstantDirectionRight) { [self resetView:pointer.x - _startX isRight:YES]; } else { [self resetView:pointer.x - _startX isRight:NO]; } } } }

该方法主要分三个阶段:滑动开始、滑动过程中、滑动结束。

滑动开始阶段主要记录开始触碰点的坐标。

滑动过程中阶段主要记录滑动过程中瞬时的触碰点的坐标,并根据瞬时坐标来判断滑动方向,并根据瞬时触碰点的坐标来实时移动视图。这个过程中会涉及到不同的翻页方式,两个视图会有不同的移动方式,这里使用了装饰模式,相比于使用继承来实现此功能,利用装饰模式这种组合的方式会更灵活。

滑动结束阶段和主要确定视图的最终位置:

 

- (void)resetView:(CGFloat)moveX isRight:(BOOL)isRight
{
    CGFloat width = self.frame.size.width;
    TypographicViewFlipOverManager *flipOverManager = TypographicViewFlipOverManager.instance;
    flipOverManager.decorateView.typographicView = self;
    if (moveX <= -width / 100)
    {
        if (isRight && moveX > -width / 3)
        {
            [flipOverManager recoverPage:moveX];
        }
        else
        {
            [flipOverManager moveToNextPage];
        }
    }
    else if (moveX >= width / 100)
    {
        if (!isRight && moveX < width / 3)
        {
            [flipOverManager recoverPage:moveX];
        }
        else
        {
            [flipOverManager moveToPrePage];
        }
    }
    else
    {
        [flipOverManager recoverPage:moveX];
    }
} 
flipOverManager对象中的方法:
    func displyViewMoveToX(_ x: CGFloat) {
        _ = self.decorateView?.displyViewMoveTo(x: x)
    }
    
    func moveToNextPage() {
        self.decorateView?.moveToNextPage()
    }
    
    func moveToPrePage() {
        self.decorateView?.moveToPrePage()
    }
    
    func recoverPage(_ moveX: CGFloat) {
        self.decorateView?.recoverPage(moveX)
    }
decorateView对象中的方法:
class TypographicDecorateView: TypographicExtensionView {
    
    var typographicView: TypographicView?
    
    override func displyViewMoveTo(x: CGFloat) ->Bool {
        let x = x
        
        return (self.typographicView?.displyViewMoveTo(x: x))!
    }
    
    override func moveToNextPage() {
        
        self.typographicView?.moveToNextPage()
    }
    
    override func moveToPrePage() {
        
        self.typographicView?.moveToPrePage()
    }
    
    override func recoverPage(_ moveX: CGFloat) {
    }

}
typographicView即为前面说的buffer1和buffer2的父视图,而typographicView中的相应代码为:
- (void)recoverPage:(CGFloat)moveX
{
}

- (void)moveToNextPage
{
    [self switchBuffer];
    [_readManager changeViewIndexToNext];   //处理数据的变化
}

- (void)moveToPrePage
{
    [self switchBuffer];
    [_readManager changeViewIndexToPre];   //处理数据的变化
} 

- (void)switchBuffer //这个方法由于滑动后切换当前和下一个要显示的视图
{
  if (_isFirstViewDisplay)
  {
    _curDisplayView = _buffer2;
    _nextDisplayView = _buffer1;
    _isFirstViewDisplay = NO;
  }
  else
  {
    _curDisplayView = _buffer1;
    _nextDisplayView = _buffer2;
    _isFirstViewDisplay = YES;
  }
  [self bringSubviewToFront:_curDisplayView];
}

- (BOOL)displyViewMoveToX:(CGFloat)x
{
   //一些业务数据上的判断,来返回YES/NO,不需要关心
}

这一系列流程就体现了装饰模式的思路,没有解释可能有点晕。

先写到这,这篇未完待续。。。

 

 


 

 

 

iOS阅读器实践系列(四)阅读视图方案