【Swift】拆分小说阅读器功能,分享内部实现

这篇具有很好参考价值的文章主要介绍了【Swift】拆分小说阅读器功能,分享内部实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

  公司项目结束了,公司估计也快黄了,年底事少,也给了我不少时间来维护博客。

  公司的项目是一个类似于简书的创作平台,涵盖写作、小说、插画内容。

  本期主要先下小说阅读部分,UI样式仿照的是微信读书样式,因之前也写过小说阅读器,但是代码并没有解耦,这次彻彻底底做一次大改动。

   小说用户的常见操作:当前阅读进入记录和书签列表,因公司项目的结构问题,目前新项目并没有做项目进度记录和书签保存功能,以后有优化时候,再补充相关内容。先看下小说的结构。

【Swift】拆分小说阅读器功能,分享内部实现

 

  小说的主要模型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文章来源地址https://www.toymoban.com/news/detail-807422.html

到了这里,关于【Swift】拆分小说阅读器功能,分享内部实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 漫画聚合阅读器Tachidesk

    今天是老苏居家隔离的第 50 天。 周三没做核酸,还以为暂时不用做了,但是周四、周五一早又是抗原又是核酸,而且间隔就半个小时; 最近 https://cdn.jsdelivr.net 访问不了了, https://unpkg.com 也是,所以老苏的博客的图片和评论都要用点特殊手段才能看到。暂时就这样吧,反正

    2024年02月06日
    浏览(41)
  • 前端pdf-阅读器-3d版

    目录 一、基于pdf.js、turnjs4封装的3d翻页效果pdf文件阅读器、pdf文件url拼接地址栏就可以展示、兼容pc端、H5端 二、效果展示图 三、部分源码 四、点赞关注加收藏私信我发源码(记得私信我、发邮箱哈) 一、基于pdf.js、turnjs4封装的3d翻页效果pdf文件阅读器、pdf文件url拼接地址

    2024年03月24日
    浏览(43)
  • iOS Epub阅读器改造记录

    六个月前在这个YHEpubDemo阅读器的基础上做了一些优化,这里做一下记录。 1.首行缩进修复 由于分页的存在,新的一页的首行可能是新的一行,则应该缩进;也可能是前面一页段落的延续,这时候不应该缩进。YHEpubDemo基于XDSReader,XDSReader目前存在新页首行没有缩进的问题。

    2024年02月12日
    浏览(36)
  • 基于 ChatGPT 实现一个 PDF 阅读器

    最近随着 OpenAI 开放了相关 API, 市面上出现了越来越多的 AI 应用,chatpdf 这个项目吸引了我的注意,它是如何突破 API 最大 token 的限制来读取这种长文本的呢? 基于对 chatpdf 原理的好奇,我开始研究起市面上相关的应用,于是简单了解后写了个简单的 demo 用于学习,顺便熟

    2023年04月09日
    浏览(37)
  • Dynamsoft 条形码阅读器 10.0.0 Crack

    将来自不同来源的图像数据转换为标准输入图像数据。 7月 06, 2023 - 10:32新版本 特征 SDK经过重构,与DynamsoftCaptureVision(DCV)架构集成,该架构包括: ImageSourceAdapter(ISA) - 用于将来自不同源的图像数据转换为标准输入图像数据的标准输入接口。此外,ISA还集成了一个图像

    2024年02月12日
    浏览(33)
  • Xpdf 阅读器源码编译后查看文件中文乱码问题解决

    经查阅,是由于缺少中文字体包: 第一步: 下载所需要的字体包 下载https://dl.xpdfreader.com/xpdf-t1fonts.tar.gz 包含 下载中文字体包(非嵌入字体) http://ftp.gnu.org/gnu/non-gnu/chinese-fonts-truetype/gkai00mp.ttf.gz http://ftp.gnu.org/gnu/non-gnu/chinese-fonts-truetype/gbsn00lp.ttf.gz 完整包含中文字体文件如

    2024年02月07日
    浏览(51)
  • Koodo Reader : 一个开源免费的电子书阅读器

    今天在浏览 GitHub 的时候,偶然发现了一个非常有趣的开源项目——Koodo Reader。这个项目是一款开源免费的电子书阅读器,支持多种格式。它具有一些非常独特的功能,深深地吸引了我的注意。在接下来的内容中,我将为大家详细介绍一下这个备受关注的阅读器项目。 Koodo

    2024年01月22日
    浏览(44)
  • 使用ComPDFKit PDF SDK 构建iOS PDF阅读器

    在当今以移动为先的世界中,为企业和开发人员创建一个iOS应用程序是必不可少的。随着对PDF文档处理需求的增加,使用ComPDFKit这个强大的PDF软件开发工具包(SDK)来构建iOS PDF阅读器和编辑器可以让最终用户轻松查看和编辑PDF文档。 在本博客中,我们将首先探讨整合ComPDFK

    2024年02月15日
    浏览(36)
  • WPF开发txt阅读器7:自定义文字和背景颜色

    除了字体、字体大小之外,文字和背景颜色也会影响阅读观感,其设置方法与选择字体如出一辙,都通过combobox控件来选择。故而在阅读设置里面添加 考虑到C#中封装的大多数颜色,其实我们都不太认识,为了更加直观,故而在 ComboBox 中的每个选项都赋上对应的颜色,其对应

    2024年02月08日
    浏览(49)
  • 用Adobe Reader PDF阅读器来验证电子签名有效性

    正常情况下,Adobe的阅读器打开PDF会显示“已签名且所有签名都有效”,表明这份PDF是一份没有经过篡改的电子文档,即 该PDF上所添加的 数字证书 是有效的数字证书; 该PDF上所添加的 数字签名 没有经过篡改; 该PDF上的 所有内容 没有经过篡改。 通过Adobe Reader阅读器打开签

    2024年02月07日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包