TextKit
快速分頁UIPageViewController
iOS, iPadOS
也許還支持 Mac Calalyst
?bash
Swiftapp
|- UIViewController // 根視圖, 可添加菜單顯示, 手勢操做等
|- UIPageController // 章節視圖, 一頁對應一章
| - UIPageController // 章節內容分頁視圖, 將單章內容進行分頁顯示
| | - UIViewController // 單頁顯示視圖, 對應單頁數據
| | |- UITextView // 文字視圖
| |
| | - UIViewController
| | |- UITextView
| |
| | ...
|
| - UIPageController
| | - UIViewController
| | |- UITextView
| |
| | - UIViewController
| | |- UITextView
| |
| | ...
|
| ...
複製代碼
章節內容分頁視圖中, 只要在返回單頁顯示視圖的代理中返回
nil
, 便可實現章節內容翻到最後一頁時, 繼續翻頁翻到下一章節的邏輯ide
首先, 必定要先肯定
好 TextView 的大小與內容間距, 即文字顯示區域的大小
, 這將嚴重影響到分頁後的數據能不能正常顯示佈局
其次, 首行縮進最好用空格代替, 而不是用 NSParagraphStyle
的 firstLineHeadIndent
屬性來實現, 不然會出現某段落從中間被分開, 下一頁依然被縮進的狀況性能
首行縮進的空格數量可用如下邏輯計算:測試
let normalWidth = "你好".size(font: textFont).width // 請根據內容語言改變文字
let speaceWidth = " ".size(font: textFont).width // 一個空格的寬
let speaceCount = Int(normalWidth / speaceWidth)
let speace = String(repeating: " ", count: speaceCount)
複製代碼
而後在每段前添加空格字體
let result = content.string.components(separatedBy: "\n").map { "\(speace)\($0)" }
複製代碼
這樣就能夠在每段首行添加一個合適的縮進了ui
接下來就是重點的分頁了spa
第一步, 前期參數準備:線程
準備好處理完成的 NSAttributedString
, 最好包含各類字體, 顏色, 格式等設置信息, 避免分頁視圖拿到數據後再次生成 NSAttributedString
, 重複設置內容樣式致使的分頁不許的狀況
準備好文字顯示區域大小的參數
第二步, 開始分頁:
準備數據:
// 建立 NSLayoutManager, 全部的分頁邏輯開端
let layoutManager = NSLayoutManager()
// 若是沒有給特定部分文字區域設置單獨的佈局, 可設置此項爲 false, 以提升性能
layoutManager.allowsNonContiguousLayout = false
// 使用以前準備好的 NSAttributedString 進行初始化 NSTextStorage
let textStorage = NSTextStorage(attributedString: string)
textStorage.addLayoutManager(layoutManager)
// 設定文字顯示區域參數
let viewSize: CGSize = CGSize(width: textAreaWidth, height: textAreaHeight)
// 設定 textView 的內間距
let textInsets = UIEdgeInsets.zero
let textViewFrame = CGRect(x: 0, y: 0, width: viewSize.width, height: viewSize.height)
// 開始分頁
var glyphRange: Int = 0
var numberOfGlyphs: Int = 0
複製代碼
分頁循環:
var ranges: [NSRange] = []
repeat {
let textContainer = NSTextContainer(size: viewSize)
layoutManager.addTextContainer(textContainer)
// 不斷建立 textView 讓 NSLayoutManager 進行內容分頁
let textView = UITextView(frame: textViewFrame, textContainer: textContainer)
textView.isEditable = false
textView.isSelectable = false
textView.textContainerInset = textInsets
textView.showsVerticalScrollIndicator = false
textView.showsHorizontalScrollIndicator = false
textView.isScrollEnabled = false // 禁止滑動, 不然計算結果將再也不準確
textView.bounces = false
textView.bouncesZoom = false
// 獲取當前分頁內容所在位置
let range = layoutManager.glyphRange(for: textContainer)
ranges.append(range)
// 斷定是否分頁完成
glyphRange = NSMaxRange(range)
numberOfGlyphs = layoutManager.numberOfGlyphs
} while glyphRange < numberOfGlyphs - 1
複製代碼
至此, 就獲得了帶有格式的全文 NSAttributedString
, 和分頁區域的 ranges
第三步, 顯示分頁數據 章節內容分頁視圖中, 將單章的 NSAttributedString
和分到的 range
分配給每個單頁顯示視圖, 在 UITextView
中直接設置 attributedText
爲 attributedString.attributedSubstring(from: range)
UITextView
的設置務必於分頁循環時的 UITextView
保持一致
NSLayoutManager
會根據加入的 NSTextContainer
不斷分走文字, 直到分完爲止, 這時候可使用 layoutManager.glyphRange(for: textContainer)
獲取 NSTextContainer
對應的文字範圍 range
, 以後就能夠根據這個 range
進行文字分割
改變顏色不須要從新盡心分頁操做, 直接操做 UITextView
的 attributedText
和原始 NSAttributedString
就行
let attributed = NSMutableAttributedString(attributedString: textView.attributedText!)
attributed.addAttribute(.foregroundColor, value: ChangeColor, range: .init(location: 0, length: attributed.length))
textView.attributedText = NSAttributedString(attributedString: attributed)
複製代碼
注意, 方法爲 addAttribute
, 而不是 setAttribute
, 後者會致使其餘信息被清空
對 UITextView
的 attributedText
和原始 NSAttributedString
的 font
設置爲新字體, 再從新進行分頁操做, 從新設置單頁顯示視圖便可
UITextView
內間距請經過 textContainerInset
設置間距, 與分頁時的參數保持一致, 單獨設置 contentInset
不保證顯示正確
直接在根視圖添加點擊手勢, 設置代理後, 根據點擊區域判斷行爲 這樣能夠避免 UIPageViewController
的翻頁手勢被遮擋
請自主作好手勢衝突的處理, 否則就是一片亂
因爲分頁流程主要在主線程上, 因此被分頁的數據最好不要過大, 單章單章分頁就剛恰好
每一個 NSTextContainer
的 frame 值都是被 NSLayoutManager
粗略計算過的, 與你設置 NSTextContainer
的 size 值略有出入, 有時候大些, 有時候小些, 但偏差絕度不會超過一個字符的高度. 因此, 蘋果建議咱們在設置 UITextView
的時候, 給這個 NSTextContainer
預留必定的高度......
還有字體問題, 由於系統有些字體對中文支持不太好, 可能會對文字的大小計算失誤, 請儘可能使用如下支持中文的字體, 或其餘支持中文的自定義字體:
Heiti SC 黑體-簡
Heiti TC 黑體-繁
PingFang TC 平方-簡
PingFang HK 平方-繁
PingFang SC 平方-繁
複製代碼
能夠添加分頁中標記, 存在標識時, 下一頁上一頁代理中返回 nil
具體判斷邏輯請根據自身項目調整
能夠嘗試一下, 內存的飆升絕對酸爽, 我在模擬器上測試, 翻了幾頁直接飆到 150+ M, 目前的方案在模擬器上 App 總體內存佔用最高穩定在 50 M 左右, 真機能夠穩定在 20 M 左右
固然, 也有多是個人方式有錯誤, 各位能夠嘗試各類方案, 但分頁邏輯萬變不離其宗