近實現了一個完整的電子書閱讀器,支持txt和epub格式的電子書閱讀,其中epub支持圖文混排的方式展示。本文主要談談其中兩種翻頁效果的實現,分別為仿真翻頁和水平滑動翻頁。
仿真翻頁
最合適的方案就是使用系統提供的UIPageviewcontroller了,不過默認的UIpageviewcontroller翻頁時背面是白色的,而閱讀器通常都會有背景色或背景圖片,翻頁時用戶體驗就很糟糕,比如就像下面這樣
所以接下來主要說說如何修改背面顏色以達到美觀的翻頁效果。
UIpageviewcontroller有一個屬性叫做isDoubleSided,默認為yes,也就是內容只會在單面(正面)顯示,設置為no后,內容便可以正面和背面雙面顯示,這時每翻一頁,pageview的下面兩個回調會調用兩次
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { // 第一次回調索取背面的controller // 第二次回調索取正面的controller } func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { // 第一次回調索取背面的controller // 第二次回調索取正面的controller }
所以我們可以對正面的controller進行反向截圖,并將其放在背面的controller上顯示,這樣整體翻頁效果就會很美觀了。
代碼示例
// 對輸入的controller進行反向截圖 func grabViewController(viewController: DUAPageViewController) -> Void { self.index = viewController.index self.chapterBelong = viewController.chapterBelong let rect = viewController.view.bounds UIGraphicsBeginImageContextWithOptions(rect.size, true, 0.0) let context = UIGraphicsGetCurrentContext() let transform = CGAffineTransform(a: -1.0, b: 0.0, c: 0.0, d: 1.0, tx: rect.size.width, ty: 0.0) context?.concatenate(transform) viewController.view.layer.render(in: context!) self.backImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() }
效果像下面這樣
左右平滑翻頁
最初采用的是UIpageviewcontroller的另一種翻頁模式——滾動模式。該模式蘋果在底層是用scrollView實現的。但這種模式在頁面切換時存在一些問題,由于蘋果會對相鄰的controller進行緩存,當調用open func setViewControllers(_ viewControllers: [UIViewController]?, direction: UIPageViewControllerNavigationDirection, animated: Bool, completion: ((Bool) -> Swift.Void)? = nil)方法并且動畫為true時,有時蘋果會錯誤的認為它已經有了頁面的緩存而不再執行數據源方法,從而引發一些問題,更詳細的說明可以看這篇文章,因此決定自己寫一個翻頁控件
DUATranslationController
DUATranslationController并沒有采用Scrollview的方式實現,而是基于controller容器,通過替換child controller來實現,具體來說就是當用戶點擊或者滑動時,判斷需要展示上一個頁面還是下一個頁面,然后模仿UIpageviewcontroller通過回調的方式索取controller,加入到controller容器中,并通過動畫的方式將新的controller平滑移動進入屏幕,舊的controller同時移出,如下是單擊手勢代碼示例(滑動手勢涉及和用戶交互,邏輯更復雜些,但基本思路是一致的)
@objc func handleTapGes(gesture: UITapGestureRecognizer) -> Void { let hitPoint = gesture.location(in: gesture.view) let curController = self.childViewControllers.first! if hitPoint.x < gesture.view!.frame.size.width/3 { // 滑向上一個controller let lastController = self.delegate?.translationController(translationController: self, controllerBefore: curController) if lastController != nil { self.delegate?.translationController(translationController: self, willTransitionTo: lastController!) self.setViewController(viewController: lastController!, direction: .right, animated: allowAnimating, completionHandler: {(complete) in self.delegate?.translationController(translationController: self, didFinishAnimating: complete, previousController: curController, transitionCompleted: complete) }) } } if hitPoint.x > gesture.view!.frame.size.width*2/3 { // 滑向下一個controller let nextController: UIViewController? = self.delegate?.translationController(translationController: self, controllerAfter: self.childViewControllers.first!) if nextController != nil { self.delegate?.translationController(translationController: self, willTransitionTo: nextController!) self.setViewController(viewController: nextController!, direction: .left, animated: allowAnimating, completionHandler: {(complete) in self.delegate?.translationController(translationController: self, didFinishAnimating: complete, previousController: curController, transitionCompleted: complete) }) } } } // 該方法模仿UIpageviewcontroller,切換到某一個controller func setViewController(viewController: UIViewController, direction: translationControllerNavigationDirection, animated: Bool, completionHandler: ((Bool) -> Void)?) -> Void { if animated == false { // 直接添加child controller ,略 }else { let oldController = self.childViewControllers.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) } }) } }
最終效果像這樣:
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。