最近寫了一個問卷、試卷類型的需求浊仆,題型包括單選、多選豫领、簡答,并且每道題包括是否必選
看到這個需求的時候第一想法就是 CollectionView 嵌套UITableView實現(xiàn)一個左右滑動等恐,嵌套上下滑動洲劣,這么一個結構(網(wǎng)上類似的需求也多是這么實現(xiàn)的课蔬,大同小異)。
單選與多選的問題二跋,參照tableView 單選及多選的簡單實現(xiàn)
但是實現(xiàn)過程中战惊,發(fā)現(xiàn)一下幾個問題不是很容易解決
滑動效果不是很理想扎即,CollectionView的滑動,開了pageEnabled之后谚鄙,滑動超過一半才會滑動過去,而不到一半的時候會回彈闷营。
更新頁碼的時候烤黍,發(fā)現(xiàn)CollectionView的代理方法不能很好的滿足需求,而放在scrollViewDidScroll方法里面去判斷是否超過一半的時候變動蚊荣,可以實現(xiàn)效果莫杈,但是變化也不是很理想
由于是CollectionView,對每個cell的監(jiān)聽就比較少了筝闹,最終必選的問題,被放大了关顷,找不到一個系統(tǒng)的代理糊秆、方法可以合理的處理必選的問題
綜合這些問題-------決定試試UIPageViewController
因為比較少用這個议双,因此也添了一些坑就不表了,直接上代碼
/// 答題的主題位置
func makePageViewController() {
//主體答題位置平痰,設置樣式為 pageCurl 汞舱,
pageViewController = UIPageViewController(transitionStyle: .pageCurl, navigationOrientation: .horizontal, options: [UIPageViewControllerOptionSpineLocationKey: NSNumber(value:UIPageViewControllerSpineLocation.min.rawValue)])
pageViewController.view.frame = CGRect(x: Q_A.Padding.left, y: Q_A.Padding.top, width: view.frame.width - 2 * Q_A.Padding.left, height: view.frame.height - Q_A.Padding.top - answerCardFrame.height - 44 - 64)
pageViewController.delegate = self
pageViewController.dataSource = self
pageViewController.isDoubleSided = false //單面
pageViewController.cancleSideTouch() //自定義宗雇,取消了邊緣響應點擊事件
// 根據(jù)數(shù)據(jù)個數(shù),設置controller的數(shù)組赔蒲,并設置數(shù)據(jù)源
for i in 0..<questions.count {
let current = QuestionViewController()
current.view.frame = pageViewController.view.bounds
current.dataSource = (questions[i], realAnswer[i])
viewControllers.append(current)
}
pageViewController.setViewControllers([viewControllers.first!], direction: .forward, animated: true) { (bool) in
print("設置完成")
self.addChildViewController(self.pageViewController)
self.view.addSubview(self.pageViewController.view)
self.pageViewController.didMove(toParentViewController: self) //前面兩句已經(jīng)加了,這句是什么意思舞虱?
}
}
這個里面欢际,UIPageViewController 的滑動類型分為2種砾嫉,pageCurl 和 scroll 設置了一個參數(shù) *** UIPageViewControllerOptionSpineLocationKey,這個參數(shù)控制的是書脊的位置焕刮,設置這個和UI的表現(xiàn)形式有關系舶沿,當且僅當 spineLocationKey 為mid***的時候配并,界面就像一本打開的書一樣,且默認設置了正反頁顯示溉旋。
這里面還涉及到pageViewController的 頁面設置方法
open func setViewControllers(_ viewControllers: [UIViewController]?, direction: UIPageViewControllerNavigationDirection, animated: Bool, completion: ((Bool) -> Swift.Void)? = nil)
方法中需要傳一個 viewControllers: [UIViewController]?畸冲,這個地方,根據(jù)pageViewController的 transitionStyle 的不同有所區(qū)別算行,當且僅當 transitionStyle 為.pageCurl && spineLocationKey 為.mid時,需要傳2個苫耸,剩下的需要傳一個VC
'NSInvalidArgumentException', reason: 'The number of provided view controllers (1) doesn't match the number required (2) for the requested spine location (UIPageViewControllerSpineLocationMid)'
如果出現(xiàn)這種錯誤,就說明你的VC的個數(shù)和transitionStyle沖突了褪子,
'UIPageViewControllerSpineLocationMid' is specified, 'doubleSided' must be 'YES'.'
這個錯誤就很明顯了~
然后我們來看UIPageViewControllerDataSource中的方法
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
//獲取即將顯示的頁面的后一頁,
let index = viewControllers.index(of: viewController)!
if index == viewControllers.count - 1 { //第一條
return nil
}
return viewControllers[pageIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
//獲取即將顯示的頁面的前一頁
let index = viewControllers.index(of: viewController)!
if index == 0 { //第一條
return nil
}
return viewControllers[pageIndex]
}
這里踩過坑,試過通過pageIndex 直接設置上一個或者下一個嫌褪,但是發(fā)現(xiàn)這里執(zhí)行了方法呀枢,不意味著已經(jīng)跳轉到前一個或者后一個笼痛,僅僅是一個預處理,因此這里通過 let index = viewControllers.index(of: viewController)! 獲取當前的頁面在viewcontrollers數(shù)組中的位置,然后直接設置前一個界面|后一個界面
這里測試過程中發(fā)現(xiàn): 當我每次調(diào)用setViewControllers的時候,下一次滑動税迷,一定會分別執(zhí)行一次上面兩個方法县爬,大概是手動設置當前頁面之后,系統(tǒng)也蒙蔽了,所以要重新了解一下吧活尊。所以在這個方法里面就不能做太多的動作了
這里說一下 解決上面滑動過程中取消滑動漏益,pageIndex變化的問題蛹锰,在UIPageViewControllerDelegate 的方法中
//將要到--
func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
//提前設置頁碼數(shù)據(jù)绰疤,如果翻頁中途取消的話,在下面設置回去
let finishOne = pendingViewControllers.first
let index = viewControllers.index(of: finishOne!)
pageIndex = index!
pageLabel.text = "\(index! + 1)/\(questions.count)"
}
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
//判斷是否成功轻庆,不成功,重新設置回去
if !completed {
//
let finishOne = previousViewControllers.first
let index = viewControllers.index(of: finishOne!)
pageIndex = index!
pageLabel.text = "\(index! + 1)/\(questions.count)"
}
}
這邊設置當前的頁碼余爆。更新UI纷宇,如果滑動為完成的話 complete 為false蛾方,這時候就要重新設置回去了上陕。這樣就解決了滑動過程中變化的問題--開始滑動就+1,滑動不成功再減回來M卮骸!
UIPageViewController自帶了單擊邊緣翻頁的效果硼莽,不想要辕万,因此干掉了
//MARK: 拓展UIPageViewController沉删,取消了邊緣的點擊事件
extension UIPageViewController: UIGestureRecognizerDelegate {
/// 拓展一個方法醉途,取消UIPageViewController的點擊邊界翻頁
fileprivate func cancleSideTouch() {
for ges in gestureRecognizers {
ges.delegate=self;
}
}
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
guard gestureRecognizer is UITapGestureRecognizer else {
return true
}
return false
}
}
檢查|統(tǒng)計答案的時候肯定需要實現(xiàn)一個遍歷數(shù)組的問題,推薦兩個方法隘擎,
public func filter(_ isIncluded: (Element) throws -> Bool) rethrows -> [Element]
public func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]
實現(xiàn)如下(僅供參考):
let unDidAnswers = realAnswer.filter { (answer) -> Bool in
return (answer.answer.isEmpty && answer.required == 1)
}
guard unDidAnswers.count == 0 else {
print("any question required is not answerd")
let firstUnDid = realAnswer.index(of: unDidAnswers.first!)
let alert = UIAlertController(title: "存在未答題的必選項:\(firstUnDid! + 1)題", message: nil, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "繼續(xù)答題", style: .cancel, handler: nil))
self.present(alert, animated: true, completion: {
//
})
return
}
//.準備提交
let results = realAnswer.map{["id": $0.questionId,"answer": $0.answer]}
主要代碼都在這了殴穴,如果找demo的話货葬,去github
都看到這了,說明真的用到了震桶,加了個復用的休傍,UIPageController實現(xiàn) 問卷蹲姐、試卷 2
雖然都很拙略,但也希望有些幫助