本文介绍本地 .txt
小说阅读器功能开发的 5 个相关技术点。
网络 .txt 小说开发,则多了下载和缓存两步
一本书有什么,即书的数据结构
一本书有书名,有正文,有目录
手机书架上的书很多,需给书分配一个 id,去除重复
小说用户的常见操作有两种,当前阅读进度记录和书签列表
小说的主要模型 ReadModel
书的两个自然属性: ID 和目录
( 一本书有书名,这里与 ID 合并 )
书的两个用户操作属性,阅读记录和书签
class ReadModel: NSObject,NSCoding {
/// 小说ID, 书名
let bookID:String
/// 目录, 章节列表
// 书的正文,按照章节拆分,保存在 ChapterBriefModel 关联的 ReadChapterModel 中
var chapterListModels = [ChapterBriefModel]()
/// 当前阅读记录
var recordModel:ReadRecordModel?
/// 书签列表
var markModels = [ReadMarkModel]()
}
小说的目录模型 ChapterBriefModel
class ChapterBriefModel{
/// 章节ID
var id: Int!
/// 小说ID
var bookID:String!
/// 章节名称
var name:String!
}
有了目录,要阅读,需要正文
小说的章节模型
包含具体的阅读章节纯文本 content,和用来渲染呈现的富文本 fullContent
含有上一章和下一章的 ID,作为一个链表,用于连续阅读
class ReadChapterModel: NSObject,NSCoding {
/// 小说ID
let bookID: String
/// 章节ID
let id: Int
/// 上一章ID
var previousChapterID: Int?
/// 下一章ID
var nextChapterID: Int?
/// 章节名称
var name:String!
/// 内容
/// 此处 content 是经过排版好且双空格开头的内容。
var content:String!
/// 可以渲染的富文本内容
var fullContent:NSAttributedString!
/// 本章有多少页
var pageCount: Int = 0
/// 分页数据,
// 一屏幕内容,对应一个 ReadPageModel
var pageModels = [ReadPageModel]()
/// 内容的排版属性
private var attributes = [NSAttributedString.Key: Any]()
}
小说的章节模型 ReadChapterModel 通过 bookID 小说 ID 和 id 章节 ID,
与上面的目录模型 ChapterBriefModel 作关联,
有了 ChapterBriefModel ,拿关联信息,去解档,找出 ReadChapterModel
这样的好处是:
一本《三国演义》的 txt, 1.8 M, 有 120 章, 拆分成 120 个占内存的 ReadChapterModel,
占内存的 ReadChapterModel 需要时解档,不需要就释放,
阅读模型 ReadModel 持有的是,轻量级的目录模型 ChapterBriefModel
小说一屏幕内容,就是一页,一个 ReadPageModel
class ReadPageModel: NSObject,NSCoding {
// MARK: 常用属性
/// 当前页内容
var content:NSAttributedString!
/// 当前页范围,
// (当前页的第一个字,是第多少个), (当前页有多少字)
var pageRange:NSRange!
/// 当前页的页码
var page: Int = 0
// MARK: 滚动模式相关
// 滚动模式的排版
/// 根据开头类型返回开头高度
var headTypeHeight:CGFloat = 0
/// 当前内容 Size
var contentSize = CGSize.zero
/// 当前内容头部类型
private
var headTypeIndex: Int = 0
/// 当前内容头部类型
var headType: PageHeadType? {
set{
if let n = newValue{
headTypeIndex = n.rawValue
}
}
get{
PageHeadType(rawValue: headTypeIndex)
}
}
}
一本书的数据结构确立后,进入功能开发
1,基础呈现:
网上下载了一本 《三国演义》,制作一个基本的阅读界面
.txt 小说 -> 小说代码模型 -> 用视图把小说呈现出来
1.1 模型解析
1.1.1 把资源路径,转化为正文
对文本编码
class func encode(url:URL) -> String {
var content = ""
if url.absoluteString.isEmpty { return content }
// utf8
content = encode(path: url, encoding: String.Encoding.utf8.rawValue)
// 进制编码
if content.isEmpty { content = encode(path: url, encoding: 0x80000632) }
if content.isEmpty { content = encode(path: url, encoding: 0x80000631) }
if content.isEmpty { content = "" }
return content
}
class func encode(path url:URL, encoding:UInt) ->String {
do{
return try NSString(contentsOf: url, encoding: encoding) as String
}
catch{
return ""
}
}
1.1.2 解析出所有的章节目录,不含正文的 ChapterBriefModel, 含正文的 ReadChapterModel
下面的代码,分为两部分,一个正则, 一个 for 循环
把正文作为一个字符串,正则拆分出所有的章节,
for 循环中,把拆除来的章节,映射为 ChapterBriefModel 和 ReadChapterModel
ReadChapterModel 归档持久化,调用时再解档
/// 解析整本小说
/// - Parameters: - bookID: 小说ID - content: 小说内容
/// - Returns: 章节列表
private class func parser(segments bookID:String, content:String) ->[ChapterBriefModel] {
// 章节列表
var chapterListModels = [ChapterBriefModel]()
// 正则
let parten = "第[0-9一二三四五六七八九十百千]*[章回].*"
// 排版
let content = ReadParserIMP.contentTypesetting(content: content)
// 正则匹配结果
var results = [NSTextCheckingResult]()
// 开始匹配
do{
let regularExpression = try NSRegularExpression(pattern: parten, options: .caseInsensitive)
results = regularExpression.matches(in: content, options: .reportCompletion, range: NSRange(location: 0, length: content.count))
}catch{
return chapterListModels
}
// 解析匹配结果
guard results.isEmpty == false else {
// ....
return // ...
}
// 章节数量
let count = results.count
// 记录最后一个Range
var lastRange:NSRange!
// 记录最后一个章节对象C
var lastChapterModel:ReadChapterModel?
// 有前言
var isHavePreface = true
// 遍历
for i in 0...count {
// 章节数量分析:
// count + 1 = 匹配到的章节数量 + 最后一个章节
// 1 + count + 1 = 第一章前面的前言内容 + 匹配到的章节数量 + 最后一个章节
Log("章节总数: \(count + 1) 当前正在解析: \(i + 1)")
var range = NSMakeRange(0, 0)
var location = 0
if i < count {
range = results[i].range
location = range.location
}
// 章节内容
let chapterModel = ReadChapterModel(id: i + isHavePreface.val, in: bookID)
switch i {
case 0:
// 前言
// 章节名
chapterModel.name = "开始"
// 内容
chapterModel.content = content.substring(NSMakeRange(0, location))
// 记录
lastRange = range
// 没有内容则不需要添加列表
if chapterModel.content.isEmpty {
isHavePreface = false
continue
}
case count:
// 结尾
// 章节名
chapterModel.name = content.substring(lastRange)
// 内容(不包含章节名)
chapterModel.content = content.substring(NSMakeRange(lastRange.rhs, content.count - lastRange.rhs))
default:
// 中间章节
// 章节名
chapterModel.name = content.substring(lastRange)
// 内容(不包含章节名)
chapterModel.content = content.substring(NSMakeRange(lastRange.rhs, location - lastRange.rhs))
}
// 章节开头双空格 + 章节纯内容
chapterModel.content = TypeSetting.readSpace + chapterModel.content.removeSEHeadAndTail
// 设置上一个章节ID
chapterModel.previousChapterID = lastChapterModel?.id ?? nil
// 设置下一个章节ID
if i == (count - 1) { // 最后一个章节了
chapterModel.nextChapterID = nil
}
else{
lastChapterModel?.nextChapterID = chapterModel.id
}
// 保存
chapterModel.persist()
lastChapterModel?.persist()
// 记录
lastRange = range
lastChapterModel = chapterModel
// 通过章节内容生成章节列表
chapterListModels.append(chapterModel.chapterList)
}
// 返回
return chapterListModels
}
1.1.3 产生阅读模型
// 阅读模型
let readModel = ReadModel.model(bookID: bookID)
// 记录章节列表
readModel.chapterListModels = chapterListModels
// 设置第一个章节为阅读记录
readModel.recordModel?.modify(chapterID: readModel.chapterListModels.first!.id, toPage: 0)
拿到阅读模型,展示出来,就可以看书了
1.2 视图呈现
- 阅读文本视图 ReadView ->
- 阅读控制器,添加状态栏 ReadViewController ->
- 阅读的主控制器 ( 带菜单功能的 )->
- 主控制器的,翻页模式处理
1.2.1, 阅读文本视图 ReadView
制定一页的模型 ReadPageModel, 产生一帧文本 CTFrame, 文本绘制到界面上,ok
class ReadView: UIView {
/// 当前页模型 ( 使用contentSize 绘制)
var pagingModel:ReadPageModel! {
didSet{
frameRef = CoreText.GetFrameRef(attrString: pagingModel.showContent, rect: CGRect(origin: CGPoint.zero, size: pagingModel.contentSize))
}
}
/// CTFrame
var frameRef:CTFrame? {
didSet{
if frameRef != nil { setNeedsDisplay() }
}
}
/// 绘制
override func draw(_ rect: CGRect) {
guard let frame = frameRef, let ctx = UIGraphicsGetCurrentContext() else {
return
}
ctx.textMatrix = CGAffineTransform.identity
ctx.translateBy(x: 0, y: bounds.size.height)
ctx.scaleBy(x: 1.0, y: -1.0)
CTFrameDraw(frame, ctx)
}
}
1.2.2, 阅读控制器,添加状态栏 ReadViewController
- 添加顶部状态栏,顶部有书名和章节名
- 添加底部状态栏,底部有当前的进度
- 阅读视图展示。是首页,展示封面。不是,就展示正文
class ReadViewController: ViewController {
// 需要两个对象, 当前页阅读记录 和 阅读对象
/// 当前页阅读记录对象
var recordModelBasic:ReadRecordModel!
/// 阅读对象 ( 用于显示书名以及书籍首页显示书籍信息 )
weak var readModel:ReadModel!
/// 顶部状态栏
var topView:ReadViewStatusTopView!
/// 底部状态栏
var bottomView:ReadViewStatusBottomView!
/// 阅读视图
private var readView:ReadView!
/// 书籍首页视图, 封面
private var homeView:ReadHomeView!
}
1.2.3, 阅读的主控制器 ( 带菜单功能的 )
- 添加左侧弹窗, 章节列表, 和书签
- 添加设置菜单
菜单包括:
顶部栏,书签按钮和返回按钮
底部栏,上一章按钮、下一章按钮和进度拖动, 目录入口和设置入口
设置栏,控制字体大小和种类,控制翻页方式,控制进度展示方式
- 添加阅读容器视图
阅读容器视图,上面是控制翻页方式的控制器的视图
控制翻页方式的控制器,管理上一步的阅读控制器 ReadViewController
class ReadController: ViewController{
// MARK: 数据相关
/// 阅读对象
let readModel:ReadModel
// MARK: UI相关
/// 阅读容器视图
var contentView = ReadContentView(frame: CGRect(x: 0, y: 0, width: ScreenWidth, height: ScreenHeight))
/// 左侧弹窗: 章节列表, 和书签
var leftView = ReadLeftView(frame: CGRect(x: -READ_LEFT_VIEW_WIDTH, y: 0, width: READ_LEFT_VIEW_WIDTH, height: ScreenHeight))
/// 底部设置菜单
lazy var readMenu = ReadMenu(vc: self, delegate: self)
// 控制翻页方式的控制器:
/// 翻页控制器 (仿真)
var pageViewController:UIPageViewController!
/// 翻页控制器 (滚动)
var scrollController:ReadViewScrollController!
/// 翻页控制器 (无效果,覆盖)
var coverController:CoverController!
/// 非滚动模式时,当前显示 ReadViewController
var currentDisplayController:ReadViewController?
}
1.2.4, 翻页模式处理
翻页模式,有仿真、平移和滚动
这里以仿真为例子:
仿真的效果,使用 UIPageViewController
- 先添加 UIPageViewController 的视图,到阅读容器视图 contentView 上面
func creatPageController(displayController:ReadViewController? = nil) {
guard let displayCtrl = displayController else {
return
}
// 创建
let options = [UIPageViewController.OptionsKey.spineLocation : NSNumber(value: UIPageViewController.SpineLocation.min.rawValue)]
pageViewController = UIPageViewController(transitionStyle: .pageCurl,navigationOrientation: .horizontal,options: options)
pageViewController.delegate = self
pageViewController.dataSource = self
// 翻页背部带文字效果
pageViewController.isDoubleSided = true
contentView.insertSubview(pageViewController.view, at: 0)
pageViewController.view.backgroundColor = UIColor.clear
pageViewController.view.frame = contentView.bounds
pageViewController.setViewControllers([displayCtrl], direction: .forward, animated: false, completion: nil)
}
- 提供分页控制器的内容,即阅读内容
以下是获取下一页的代码,
获取上一页的,类似
/// 获取下一页
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
tempNumber += 1
// 获取当前页阅读记录
var recordModel:ReadRecordModel? = (viewController as? ReadViewController)?.recordModelBasic
// 如果没有则从背面页面获取
if recordModel == nil {
recordModel = (viewController as? ReadViewBGController)?.recordModel
}
if abs(tempNumber) % 2 == 0 { // 背面
return getBackgroundController(recordModel: recordModel)
}
else{
// 内容
recordModel = getBelowReadRecordModel(recordModel: recordModel)
return getReadController(recordModel: recordModel)
}
}
这样,.txt 的小说,可读一下了
2,计算页码
一个章节有几页,是怎么计算出来的?
先拿着一个章节的富文本,和显示区域,计算出书页的范围
通常显示区域,是放不满一章的。
显示区域先放一页,得到这一页的开始范围和长度,对应一个 ReadPageModel
显示区域再放下一页 ...
/// 获得内容分页列表
/// - Parameters: - attrString: 内容 - rect: 显示范围
/// - Returns: 内容分页列表
class func pagingRanges(attrString:NSAttributedString, rect:CGRect) ->[NSRange] {
var rangeArray = [NSRange]()
let framesetter = CTFramesetterCreateWithAttributedString(attrString as CFAttributedString)
let path = CGPath(rect: rect, transform: nil)
var range = CFRangeMake(0, 0)
var rangeOffset = 0
repeat{
let frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(rangeOffset, 0), path, nil)
range = CTFrameGetVisibleStringRange(frame)
rangeArray.append(NSMakeRange(rangeOffset, range.length))
rangeOffset += range.length
}while(range.location + range.length < attrString.length)
return rangeArray
}
拿上一步计算出来的范围,创建该章节每一页的模型 ReadPageModel
/// 内容分页
/// - Parameters: - attrString: 内容 - rect: 显示范围
/// - isFirstChapter: 是否为本文章第一个展示章节,如果是则加入书籍首页
/// - Returns: 内容分页列表
class func pageing(attrString:NSAttributedString, rect:CGRect, isFirstChapter:Bool = false) ->[ReadPageModel] {
var pageModels = [ReadPageModel]()
if isFirstChapter { // 第一页为书籍页面
let pageModel = ReadPageModel()
pageModel.pageRange = NSMakeRange(TypeSetting.readBookHomePage, 1)
pageModel.contentSize = READ_VIEW_RECT.size
pageModels.append(pageModel)
}
let ranges = CoreText.pagingRanges(attrString: attrString, rect: rect)
if !ranges.isEmpty {
let count = ranges.count
for i in 0..
该章节 ReadPageModel
的数目,就是该章节有几页
2.1 翻页
获取下一页的代码
翻一页,就是当前的 ReadRecordModel , 翻到下一页,
交给阅读控制器去呈现, ReadViewController 的子类 ReadLongPressViewController
标准的模型更新,刷新视图
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
// ...
// 内容
recordModel = recordModel?.getBelowReadRecordModel
return getReadController(recordModel: recordModel)
}
/// 获取指定阅读记录阅读页
func getReadController(recordModel:ReadRecordModel!) ->ReadViewController? {
if recordModel != nil {
// 需要返回支持长按的控制器
let controller = ReadLongPressViewController()
controller.recordModelBasic = recordModel
controller.readModel = readModel
return controller
}
return nil
}
阅读记录模型:
ReadRecordModel, 主要是三个属性,
一本小说,绑定一个进度,需要小说 ID
当前看到那一章,有一个章节的模型 ReadChapterModel
当前这一章,看到第几页了,有一个页码 page,
可以计算出,
当前阅读到的这一屏的,页面模型 ReadPageModel
和当前阅读到的这一屏的富文本 contentAttributedString,用来渲染
class ReadRecordModel: NSObject,NSCoding {
/// 小说ID
var bookID:String!
/// 当前记录的阅读章节
var chapterModel:ReadChapterModel!
/// 阅读到的页码
var page:Int = 0
/// 当前记录分页模型
var pageModel:ReadPageModel{
chapterModel.pageModels[page]
}
/// 当前记录页码富文本
var contentAttributedString:NSAttributedString {
chapterModel.contentAttributedString(page: page)
}
ReadRecordModel, 翻页的计算逻辑:
本章内,页码 + 1, 就好了, page 处理下
本章最后一页了,换下一章
本章到了最后一章,最后一页了,就翻不动了
/// 获取当前记录下一页阅读记录
var getBelowReadRecordModel: ReadRecordModel?{
// ...
// 复制
let recordModel = copyModel
// 书籍ID
// 章节ID
guard let bookID = recordModel.bookID, let chapterID = recordModel.chapterModel.nextChapterID else{
return nil
}
// 最后一章 最后一页
if recordModel.isLastChapter, recordModel.isLastPage {
Log("已经是最后一页了")
return nil
}
// 最后一页
if recordModel.isLastPage {
// 检查是否存在章节内容
if ReadChapterModel.isExist(bookID: bookID, chapterID: chapterID){
// 修改阅读记录
recordModel.modify(chapterID: chapterID, toPage: 0, isSave: false)
}
}else{
recordModel.nextPage()
}
return recordModel
}
3,目录
目录展示,比较简单
把上文解析出来的目录模型 ChapterBriefModel
,用一个列表展示就好了
滚动到阅读记录
譬如,当前阅读到第 50 章了,打开目录,显示第一章,不太好。
需要滚动到,阅读记录对应的章节
当前阅读进度,使用 recordModel 追踪,
从目录 ChapterBriefModel
列表中,找出 recordModel 的章节模型的 id,就好了
ChapterBriefModel
和 ReadChapterModel
的 id 是一一对应的
/// 滚动到阅读记录
func scrollRecord() {
if let read = readModel, let record = read.recordModel {
tableView.reloadData()
if let chapterListModel = (read.chapterListModels as NSArray).filtered(using: NSPredicate(format: "id == %ld", record.chapterModel.id)).first as? ChapterBriefModel{
tableView.scrollToRow(at: read.chapterListModels.firstIndex(of: chapterListModel)!.ip, at: .middle, animated: false)
}
}
}
4,书签
从读到的位置,添加书签。
书签列表中,用书签,返回读到的位置
书签的数据结构
一个书签,绑定具体的小说,与该小说的某个章节
书签,最好能展示一些上次阅读的信息,content
要从书签,返回到阅读到的地方,需要一个位置 location
class ReadMarkModel: NSObject,NSCoding {
/// 小说ID
var bookID:String!
/// 章节ID
var chapterID: Int!
/// 章节名称
var name:String!
// 内容,
// 对应当前屏幕阅读模型 ReadPageModel 的内容
var content:String!
/// 时间戳
var time = NSNumber(value: 0)
/// 位置
// 对应当前屏幕阅读模型 ReadPageModel 的范围的开始点
var location: Int = 0
4.1 创建书签
从读到的位置,添加书签。
书签的展示,采用逆序。最新的,摆在前面,也就是最近添加的。
/// 添加书签,默认使用当前阅读记录!
func insetMark(recordModel:ReadRecordModel? = nil) {
let recordModel = (recordModel ?? self.recordModel)!
let markModel = ReadMarkModel()
markModel.bookID = recordModel.bookID
markModel.chapterID = recordModel.chapterModel.id
if recordModel.pageModel.isHomePage {
markModel.name = "(无章节名)"
markModel.content = bookID
}else{
markModel.name = recordModel.chapterModel.name
// 当前屏幕阅读模型 ReadPageModel 的内容,稍微处理了下
markModel.content = recordModel.contentString.removeSEHeadAndTail.removeEnterAll
}
markModel.time = NSNumber(value: Timer1970())
// location, 对应当前屏幕阅读模型 ReadPageModel 的范围的开始点
markModel.location = recordModel.locationFirst
if markModels.isEmpty {
markModels.append(markModel)
}else{
markModels.insert(markModel, at: 0)
}
// ...
}
4.2 从书签列表,选择书签,返回读到的地方
点击具体的书签,先处理下 UI,
再拿着章节 ID 和该章节的位置,去跳转上次读到的地方
// MARK: ReadMarkViewDelegate
extension ReadController: ReadMarkViewDelegate{
/// 书签列表选中书签
func markViewClickMark(markView: ReadMarkView, markModel: ReadMarkModel) {
showLeftView(isShow: false)
contentView.showCover(isShow: false)
goToChapter(chapterID: markModel.chapterID, coordinate: markModel.location)
}
}
跳转上次读到的地方,就是拿记录阅读位置的 ReadRecordModel,
更新他的章节模型 ReadChapterModel, 和阅读到的位置 page
模型更新,刷新 UI 界面,就是跳转过去了
/// 跳转指定章节(指定坐标)
func goToChapter(chapterID: Int, coordinate location: Int) {
// 复制阅读记录
let recordModel = readModel.recordModel?.copyModel
// 书籍ID
guard let bookID = recordModel?.bookID else{
return
}
// 检查是否存在章节内容
// 存在
if ReadChapterModel.isExist(bookID: bookID, chapterID: chapterID){
// 坐标定位
recordModel?.modifyLoc(chapterID: chapterID, location: location, isSave: false)
// 阅读阅读记录
if let record = recordModel{
update(read: record)
}
// 展示阅读
creatPageController(displayController: getReadController(recordModel: recordModel))
}
}
ReadRecordModel, 更新内容
刷新章节目录 ReadChapterModel ,比较简单,拿书的 id 和章节 id, 去创建新的
该章节中,阅读到第几页,需要在 ReadChapterModel 中计算下
class ReadRecordModel{
/// 修改阅读记录为指定章节位置
func modifyLoc(chapterID: Int, location: Int, isSave:Bool = true) {
if ReadChapterModel.isExist(bookID: bookID, chapterID: chapterID) {
chapterModel = ReadChapterModel(id: chapterID, in: bookID).real
page = chapterModel.page(location: location)
if isSave { save() }
}
}
}
ReadChapterModel 中计算,该章节阅读到第几页
拿着位置,在章节模型的书页模型列表中比较范围,得出
class ReadChapterModel{
// 获取存在指定坐标的页码
func page(location: Int?) -> Int {
let count = pageModels.count
guard let loc = location else {
return 0
}
for i in 0..
总结上文,功能主要是,各种模型之间的对应关系,转换来,转换去,跟数据库操作很像
5,滚动条,调进度
调进度分两种,
- 全文滚动,拉章节
全文百分比展示进度,滚动是在全文范围内的;
- 章节滚动,拉书页
当前章节展示进度,按页码,滚动是在当前章节范围内的
5.1 全文的进度展示与调节
全文百分比展示进度
拿着当前的阅读记录,去计算
- 是尾页,则很好计算
- 不是尾页,
先算出当前章初始位置的进度 ,chapterIndex/chapterCount
再加上当前页,在当前章的进度, (locationFirst / fullContentLength)/chapterCount
算的精度一般,把每一章的长度,等价了
extension ReadModel{
/// 计算总进度
func progress(readTotal recordModel:ReadRecordModel!) ->Float {
// 当前阅读进度
var progress:Float = 0
// 临时检查
if recordModel == nil { return progress }
if recordModel.isLastChapter, recordModel.isLastPage { // 最后一章最后一页
// 获得当前阅读进度
progress = 1.0
}else{
// 当前章节在所有章节列表中的位置
let chapterIndex = Float(recordModel.chapterModel.priority)
// 章节总数量
let chapterCount = Float(chapterListModels.count)
// 阅读记录首位置
let locationFirst = Float(recordModel.locationFirst)
// 阅读记录内容长度
let fullContentLength = Float(recordModel.chapterModel.fullContent.length)
// 获得当前阅读进度
progress = chapterIndex/chapterCount + (locationFirst / fullContentLength)/chapterCount
}
// 返回
return progress
}
}
滚动是在全文范围内,只能拉到某一章的开头
滚动条代理中,找到滚动的范围,
用该范围,找出目录列表中,对应的那一章,
跳过去,就好了
- 拉到底,就跳尾页
- 没拉到底,就跳到那一章的开头
/// 进度显示将要隐藏
func sliderWillHidePopUpView(_ slider: ASValueTrackingSlider!) {
// 有阅读数据
let readModel = readMenu.vc.readModel
// 有阅读记录以及章节数据
if readModel.recordModel?.chapterModel != nil{
// 总章节个数
let count = (readModel.chapterListModels.count - 1)
// 获得当前进度的章节索引
let index = Int(Float(count) * slider.value)
// 获得章节列表模型
let chapterListModel = readModel.chapterListModels[index]
// 页码
let toPage = (index == count) ? ReadingConst.lastPage : 0
// 传递
readMenu?.delegate?.readMenuDraggingProgress(readMenu: readMenu, toChapterID: chapterListModel.id, toPage: toPage)
}
}
下面两个方法,
就是模型更新,刷新界面
模型更新,就是更新当前阅读记录模型 ReadRecordModel 的位置
/// 拖拽章节进度
func readMenuDraggingProgress(readMenu: ReadMenu, toChapterID: Int, toPage: Int) {
// 不是当前阅读记录章节
if toChapterID != readModel.recordModel?.chapterModel.id{
goToChapter(chapterID: toChapterID, to: toPage)
// 检查当前内容是否包含书签
readMenu.topView.checkForMark()
}
}
/// 跳转指定章节的指定页面
func goToChapter(chapterID: Int, to page: Int = 0) {
// 复制阅读记录
let recordModel = readModel.recordModel?.copyModel
// 书籍ID
guard let bookID = recordModel?.bookID else{
return
}
// 检查是否存在章节内容
// 存在
if ReadChapterModel.isExist(bookID: bookID, chapterID: chapterID){
// 分页定位
recordModel?.modify(chapterID: chapterID, toPage: page, isSave: false)
// 阅读阅读记录
if let record = recordModel{
update(read: record)
}
// 展示阅读
creatPageController(displayController: getReadController(recordModel: recordModel))
}
}
5.2 当前章节的进度展示与调节
分页进度,
进度就靠 当前阅读记录模型 ReadRecordModel
/// 刷新阅读进度显示
private func reloadProgress() {
// 分页进度
if let record = vc.readModel.recordModel{
bottomView.progress.text = "\(record.page + 1)/\(record.chapterModel!.pageCount)"
}
// 显示进度
}
滚动是在当前章节范围内,拉到某一书页
前往某一书页,就是更新当前阅读记录模型 ReadRecordModel 的位置,
刷新界面
/// 进度显示将要隐藏
func sliderWillHidePopUpView(_ slider: ASValueTrackingSlider!) {
// 分页进度
readMenu?.delegate?.readMenuDraggingProgress(readMenu: readMenu, toPage: Int(slider.value - 1))
}
当前章节范围内,更新当前阅读记录模型 ReadRecordModel 的页码,
比较简单
/// 拖拽阅读记录
func readMenuDraggingProgress(readMenu: ReadMenu, toPage: Int) {
if readModel.recordModel?.page != toPage{
readModel.recordModel?.page = toPage
creatPageController(displayController: getCurrentReadViewController())
// 检查当前内容是否包含书签
readMenu.topView.checkForMark()
}
}