本文介绍本地 .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..<count { let range = ranges[i] let pageModel = ReadPageModel() let content = attrString.attributedSubstring(from: range) pageModel.pageRange = range pageModel.content = content pageModel.page = i // ... // 内容Size (滚动模式 || 长按菜单) let maxW = READ_VIEW_RECT.width pageModel.contentSize = CGSize(width: maxW, height: CoreText.GetAttrStringHeight(attrString: content, maxW: maxW)) // ... pageModels.append(pageModel) } } return pageModels }
该章节 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: ReadMarkViewDelegateextension 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..<count { let range = pageModels[i].pageRange! if loc < range.rhs { return i } } return 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() } }