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,不需要关心}

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

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

 


时间: 2024-11-11 22:40:58

iOS阅读器实践系列(四)阅读视图方案的相关文章

iOS阅读器实践系列(一)coretext纯文本排版基础

前言:之前做了公司阅读类的App,最近有时间来写一下阅读部分的实现过程,供梳理逻辑,计划会写一个系列希望能涉及到尽量多的方面与细节,欢迎大家交流.吐槽.拍砖,共同进步. 阅读的排版用的是coretext,这篇介绍用coretext实现基本的排版功能. 关于coretext的实现原理,可以查看文档或其他资料,这里就不介绍了,只介绍如何应用coretext来实现一个简单的文本排版功能. 因为coretext是离屏排版的,即在将内容渲染到屏幕之前,内容的排版工作的已经完成了. 排版过程大致过程分为 步

iOS阅读器实践系列(三)图文混排

本篇介绍coretext中的图文混排,这里暂用静态的内容,即在文本中某一固定位置插入图片,而不是插入位置是根据文本内容动态插入的(要实现这一效果需要写一个文本解析器,将原信息内容解析为某些特定格式的结构来标示出特定的类型(比如文字.图片.链接等),然后按照其结构中的属性配置,生成属性字符串,之后渲染到视图中). 这部分的思路参考唐巧大神的blog. 在第一篇介绍过coretext是离屏渲染的,即在将内容渲染到屏幕上之前,coretext已完成排版工作.coretext排版的第一步是组织数据,即由

Apache Kafka系列(四) 多线程Consumer方案

Apache Kafka系列(一) 起步 Apache Kafka系列(二) 命令行工具(CLI) Apache Kafka系列(三) Java API使用 Apache Kafka系列(四) 多线程Consumer方案 本文的图片是通过PPT截图出的,读者如果修改意见请联系我 一.Consumer为何需要实现多线程 假设我们正在开发一个消息通知模块,该模块允许用户订阅其他用户发送的通知/消息.该消息通知模块采用Apache Kafka,那么整个架构应该是消息的发布者通过Producer调用AP

iOS React Native实践系列一

Facebook 在 React.js Conf 2015 大会上推出了基于 JavaScript 的开源框架 React Native React Native 结合了 Web 应用和 Native 应用的优势,可以使用 JavaScript 来开发 iOS 和 Android 原生应用.在 JavaScript 中用 React 抽象操作系统原生的 UI 组件,代替 DOM 元素来渲染等. React Native 使你能够使用基于 JavaScript 和 React 一致的开发体验在本地

作业部落 Cmd Markdown 编辑阅读器

Cmd Markdown 编辑阅读器 Cmd Markdown 编辑阅读器 WindowsMacLinux 全平台客户端 什么是 Markdown 书写一个质能守恒公式1 高亮一段代码2 高效绘制 流程图 高效绘制 序列图 绘制表格 更详细语法说明 什么是 Cmd Markdown 实时同步预览 编辑工具栏 编辑模式 实时的云端文稿 离线模式 管理工具栏 阅读工具栏 阅读模式 标签分类和搜索 文稿发布和分享 我们理解您需要更便捷更高效的工具记录思想,整理笔记.知识,并将其中承载的价值传播给他人,

RSS阅读器

RSS阅读器基本可以分为三类. 第一类大多数阅读器是运行在计算机桌面上的应用程序,通过所订阅网站的新闻供应,可自动.定时地更新新闻标题.在该类阅读器中,有Awasu.FeedDemon和RSSReader这三款流行的阅读器,都提供免费试用版和付费高级版 第二类新闻阅读器通常是内嵌于已在计算机中运行的应用程序中.例如,NewsGator内嵌在微软的Outlook中,所订阅的新闻标题位于Outlook的收件箱文件夹中.另外,Pluck内嵌在Internet Explorer浏览器中! 第三类则是在线

酒店护照阅读器SDK可集成于前台自助机

一.酒店护照阅读器发展背景 随着国际化的进程加速,电子护照替代传统护照已经成为国际发展的必然趋势.目前,国际民用航空组织(ICAO)和欧盟(EU)已经推出三代电子护照,第一.二代护照已经在全球发行.随着技术的不断发展,第一.二代护照暴露出许多安全问题,第三代护照采用了新的访问控制机制,其安全性得到很大提高.介绍ICAO和EU的三代电子护照发展历程,从物理层面.通信层面和协议层面分析了电子护照的安全,重点研究电子护照协议层存在的安全问题以及探讨这些问题的一些解决方案.综合分析研究,对提高电子护照的

RSS阅读器&amp;BT sync

①RSS阅读器? 答:RSS阅读器是一种软件或是说一个程序,这种软件可以自由读取RSS和Atom两种规范格式的文档,且这种读取RSS和Atom文档的软件有多个版本,由不同的人或公司开发,有着不同的名字. Really Simple Syndication “真正简单的聚合”就是RSS的英文原意.把新闻标题.摘要(Feed).内容按照用户的要求,“送”到用户的桌面就是RSS的目的. ②BT sync BT sync 是一个文件同步工具,让你在几台不同的设备之间,同步文件.

ubuntu下安装CAJ阅读器

目录 1.ubuntu下wine的基本介绍 (1)wine的介绍 (2)wine的安装 (3)exe文件的安装 (4)exe程序的卸载 (6)wine的基本使用 2.CAJ阅读器的安装 (1)首先放上正确的安装方式--三步完成 (2)然后讲述以下之前安装出问题的经验 1.ubuntu下wine的基本介绍 wine基本介绍 (1)wine的介绍 Wine("Wine Is Not an Emulator")是一个兼容层,能够在几个POSIX兼容的操作系统上运行Windows应用程序,如L