Swift拆分小说阅读器功能,内部实现
公司项目结束了,公司估计也快黄了,年底事少,也给了我不少时间来维护博客。
公司的项目是一个类似于简书的创作平台,涵盖写作、小说、插画内容。
本期主要先下小说阅读部分,UI样式仿照的是微信读书样式,因之前也写过小说阅读器,但是代码并没有解耦,这次彻彻底底做一次大改动。
小说用户的常见操作:当前阅读进入记录和书签列表,因公司项目的结构问题,目前新项目并没有做项目进度记录和书签保存功能,以后有优化时候,再补充相关内容。先看下小说的结构。
小说的主要模型ReadModel
小说章节模型
-
class JFChapterModel: NSObject {
-
-
var title: String?
-
var path: String?
-
var chapterIndex: Int = 1
-
}
小说页面Model,一个页面,就是一个Model
-
class JFPageModel: NSObject {
-
-
var attributedString: NSAttributedString?
-
var range: NSRange?
-
var pageIndex: Int = 1
-
-
}
一本书的数据结构确立后,进入功能开发
1、模型解析
1、把资源路径转化为正文,解析出所有的章节目录,把正文作为一个字符串,正则拆分出所有的章节,映射为ChapterModel
首先正则获取章节目录
-
func doTitleMatchWith(content: String) -> [NSTextCheckingResult] {
-
let pattern = "第[ ]*[0-9一二三四五六七八九十百千]*[ ]*[章回].*"
-
let regExp = try! NSRegularExpression(pattern: pattern, options: NSRegularExpression.Options.caseInsensitive)
-
let results = regExp.matches(in: content, options: .reportCompletion, range: NSMakeRange(0, content.count))
-
return results
-
}
-
let content = path
-
var models = Array<JFChapterModel>()
-
var titles = Array<String>()
-
DispatchQueue.global().async {
-
let document = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).first
-
let fileName = name
-
let bookPath = document! "/\(String(fileName))"
-
if FileManager.default.fileExists(atPath: bookPath) == false {
-
try? FileManager.default.createDirectory(atPath: bookPath, withIntermediateDirectories: true, attributes: nil)
-
}
-
-
let results = self.doTitleMatchWith(content: content)
-
if results.count == 0 {
-
let model = JFChapterModel()
-
model.chapterIndex = 1
-
model.path = path
-
completeHandler([], [model])
-
}else {
-
var endIndex = content.startIndex
-
-
for (index, result) in results.enumerated() {
-
let startIndex = content.index(content.startIndex, offsetBy: result.range.location)
-
endIndex = content.index(startIndex, offsetBy: result.range.length)
-
-
let currentTitle = String(content[startIndex...endIndex])
-
titles.append(currentTitle)
-
let chapterPath = bookPath "/chapter" String(index 1) ".txt"
-
let model = JFChapterModel()
-
model.chapterIndex = index 1
-
model.title = currentTitle
-
model.path = chapterPath
-
models.append(model)
-
-
if FileManager.default.fileExists(atPath: chapterPath) {
-
continue
-
}
-
var endLoaction = 0
-
if index == results.count - 1 {
-
endLoaction = content.count - 1
-
}else {
-
endLoaction = results[index 1].range.location - 1
-
}
-
let startLocation = content.index(content.startIndex, offsetBy: result.range.location)
-
let subString = String(content[startLocation...content.index(content.startIndex, offsetBy: endLoaction)])
-
try! subString.write(toFile: chapterPath, atomically: true, encoding: String.Encoding.utf8)
-
-
}
-
-
DispatchQueue.main.async {
-
completeHandler(titles, models)
-
}
-
}
-
}
拿到阅读模型后,展示出来,就可以看书了。
2、翻页模式处理
翻页模式,有仿真、平移和滚动
这里以仿真为例子:
仿真的效果,使用 UIPageViewController
先添加 UIPageViewController 的视图,到阅读容器视图 contentView 上面
-
private func loadPageViewController() -> Void {
-
-
self.clearReaderViewIfNeed()
-
let transtionStyle: UIPageViewController.TransitionStyle = (self.config.scrollType == .curl) ? .pageCurl : .scroll
-
self.pageVC = JFContainerPageViewController(transitionStyle: transtionStyle, navigationOrientation: .horizontal, options: nil)
-
self.pageVC?.dataSource = self
-
self.pageVC?.delegate = self
-
self.pageVC?.view.backgroundColor = UIColor.clear
-
-
// 翻页背部带文字效果
-
self.pageVC?.isDoubleSided = (self.config.scrollType == .curl) ? true : false
-
-
self.addChild(self.pageVC!)
-
self.view.addSubview((self.pageVC?.view)!)
-
self.pageVC?.didMove(toParent: self)
-
}
- 提供分页控制器的内容,即阅读内容
以下是获取下一页的代码,
获取上一页的,类似
-
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
-
print("向后翻页 -------1")
-
struct LastPage {
-
static var arrived = false
-
}
-
let nextIndex: Int
-
let pageArray = self.pageArrayFromCache(chapterIndex: currentChapterIndex)
-
if viewController is JFPageViewController {
-
let page = viewController as! DUAPageViewController
-
nextIndex = page.index 1
-
if nextIndex == pageArray.count {
-
LastPage.arrived = true
-
}
-
let backPage = JFBackViewController()
-
backPage.grabViewController(viewController: page)
-
return backPage
-
}
-
if LastPage.arrived {
-
LastPage.arrived = false
-
if currentChapterIndex 1 > totalChapterModels.count {
-
return nil
-
}
-
pageVC?.willStepIntoNextChapter = true
-
self.requestChapterWith(index: currentChapterIndex 1)
-
let nextPage = self.getPageVCWith(pageIndex: 0, chapterIndex: currentChapterIndex 1)
-
/// 需要的页面并没有准备好,此时出现页面饥饿
-
if nextPage == nil {
-
self.postReaderStateNotification(state: .busy)
-
pageHunger = true
-
}
-
return nextPage
-
}
-
let back = viewController as! JFBackViewController
-
return self.getPageVCWith(pageIndex: back.index 1, chapterIndex: back.chapterBelong)
-
}
3、计算页码
一个章节有几页,是怎么计算出来的?
先拿着一个章节的富文本,和显示区域,计算出书页的范围
通常显示区域,是放不满一章的。
显示区域先放一页,得到这一页的开始范围和长度,对应一个 ReadPageModel
显示区域再放下一页 ...
-
let layouter = JFCoreTextLayouter.init(attributedString: attrString)
-
let rect = CGRect(x: config.contentFrame.origin.x, y: config.contentFrame.origin.y, width: config.contentFrame.size.width, height: config.contentFrame.size.height - 5)
-
var frame = layouter?.layoutFrame(with: rect, range: NSRange(location: 0, length: attrString.length))
-
-
var pageVisibleRange = frame?.visibleStringRange()
-
var rangeOffset = pageVisibleRange!.location pageVisibleRange!.length
拿上一步计算出来的范围,创建该章节每一页的模型 ReadPageModel
-
while rangeOffset <= attrString.length && rangeOffset != 0 {
-
let pageModel = DUAPageModel.init()
-
pageModel.attributedString = attrString.attributedSubstring(from: pageVisibleRange!)
-
pageModel.range = pageVisibleRange
-
pageModel.pageIndex = count - 1
-
-
frame = layouter?.layoutFrame(with: rect, range: NSRange(location: rangeOffset, length: attrString.length - rangeOffset))
-
pageVisibleRange = frame?.visibleStringRange()
-
if pageVisibleRange == nil {
-
rangeOffset = 0
-
}else {
-
rangeOffset = pageVisibleRange!.location pageVisibleRange!.length
-
}
-
-
let completed = (rangeOffset <= attrString.length && rangeOffset != 0) ? false : true
-
completeHandler(count, pageModel, completed)
-
count = 1
-
}
4、翻页
获取下一页的代码
翻一页,就是当前的 RecordModel , 翻到下一页,
交给阅读控制器去呈现, ReadViewController 的子类 ReadLongPressViewController
标准的模型更新,刷新视图
-
func setViewController(viewController: UIViewController, direction: translationControllerNavigationDirection, animated: Bool, completionHandler: ((Bool) -> Void)?) -> Void {
-
if animated == false {
-
for controller in self.children {
-
self.removeController(controller: controller)
-
}
-
self.addController(controller: viewController)
-
if completionHandler != nil {
-
completionHandler!(true)
-
}
-
}else {
-
let oldController = self.children.first
-
self.addController(controller: viewController)
-
-
var newVCEndTransform: CGAffineTransform
-
var oldVCEndTransform: CGAffineTransform
-
viewController.view.transform = .identity
-
if direction == .left {
-
viewController.view.transform = CGAffineTransform(translationX: screenWidth, y: 0)
-
newVCEndTransform = .identity
-
oldController?.view.transform = .identity
-
oldVCEndTransform = CGAffineTransform(translationX: -screenWidth, y: 0)
-
}else {
-
viewController.view.transform = CGAffineTransform(translationX: -screenWidth, y: 0)
-
newVCEndTransform = .identity
-
oldController?.view.transform = .identity
-
oldVCEndTransform = CGAffineTransform(translationX: screenWidth, y: 0)
-
}
-
-
UIView.animate(withDuration: animationDuration, animations: {
-
oldController?.view.transform = oldVCEndTransform
-
viewController.view.transform = newVCEndTransform
-
}, completion: { (complete) in
-
if complete {
-
self.removeController(controller: oldController!)
-
}
-
if completionHandler != nil {
-
completionHandler!(complete)
-
}
-
})
-
}
-
}
//如果到了最后一章、最后一页时,就翻不动了
-
self.postReaderStateNotification(state: .ready)
-
if pageHunger {
-
pageHunger = false
-
if pageVC != nil {
-
self.loadPage(pageIndex: currentPageIndex)
-
}
-
if tableView != nil {
-
if currentPageIndex == 0 && tableView?.scrollDirection == .up {
-
self.requestLastChapterForTableView()
-
}
-
if currentPageIndex == self.pageArrayFromCache(chapterIndex: currentChapterIndex).count - 1 && tableView?.scrollDirection == .down {
-
self.requestNextChapterForTableView()
-
}
-
}
-
}
-
-
if firstIntoReader {
-
firstIntoReader = false
-
currentPageIndex = pageIndex <= 0 ? 0 : (pageIndex - 1)
-
updateChapterIndex(index: chapter.chapterIndex)
-
self.loadPage(pageIndex: currentPageIndex)
-
if self.delegate?.reader(reader: readerProgressUpdated: curPage: totalPages: ) != nil {
-
self.delegate?.reader(reader: self, readerProgressUpdated: currentChapterIndex, curPage: currentPageIndex 1, totalPages: self.pageArrayFromCache(chapterIndex: currentChapterIndex).count)
-
}
-
}
-
-
if isReCutPage {
-
isReCutPage = false
-
var newIndex = 1
-
for (index, item) in pages.enumerated() {
-
if prePageStartLocation >= (item.range?.location)! && prePageStartLocation <= (item.range?.location)! (item.range?.length)! {
-
newIndex = index
-
}
-
}
-
currentPageIndex = newIndex
-
self.loadPage(pageIndex: currentPageIndex)
-
-
/// 触发预缓存
-
// self.forwardCacheIfNeed(forward: true)
-
// self.forwardCacheIfNeed(forward: false)
-
}
-
-
if successSwitchChapter != 0 {
-
self.readChapterBy(index: successSwitchChapter, pageIndex: 1)
-
}
小说内容,实在太多,一时不知道下手开始写这边博文,就借鉴了别人的写作思路。地址:https://segmentfault.com/a/1190000023555795
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhibcgba
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
TikTok加速器哪个好免费的TK加速器推荐
TK小达人 10-01 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
excel下划线不显示怎么办
PHP中文网 06-23 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
微信运动停用后别人还能看到步数吗
PHP中文网 07-22