IOS UIPageViewController получает запутанные и ViewControllers не отображаются
У нас есть кнопки влево и вправо, настроенные для того, чтобы пользователь быстро просматривал разные автомобили. Контроллер просмотра страницы теряет контроллер просмотра, если пользователь быстро нажимает на следующую страницу 10 или более раз.
Ниже приведена страница автомобиля с изображением автомобиля (размыта, чтобы скрыть не относящуюся к делу информацию). Смотрите изображение здесь:
![Показывает автомобиль правильно]()
Если анимация прокрутки включена (true), она теряет страницу транспортного средства после быстрого нажатия стрелки вправо 6 или более раз. Смотрите изображение здесь:
![введите описание изображения здесь]()
код:
private func show(viewController:UIViewController, going direction: UIPageViewControllerNavigationDirection) {
let viewControllers = [viewController]
let isAnimated = true // false always works. However, animation is required.
setViewControllers(viewControllers, direction: direction, animated: isAnimated, completion: nil)
}
Во время отладки и когда контроллер просмотра страницы остановил показ автомобилей, я убедился, что установленный контроллер просмотра не равен нулю, а листинг (автомобиль) также не равен нулю.
Я попробовал вариант решения из UIPageViewController, как я могу правильно перейти на конкретную страницу, не нарушая порядок, указанный источником данных?, где завершение используется блок. Однако это не сработало.
weak var pvcw: UIPageViewController? = self
setViewControllers(viewControllers, direction: direction, animated: true, completion: {(_ finished: Bool) -> Void in
let pvcs: UIPageViewController? = pvcw
if pvcs == nil {
return
}
DispatchQueue.main.async(execute: {() -> Void in
pvcs?.setViewControllers(viewControllers, direction: direction, animated: false) {(_ finished: Bool) -> Void in }
})
})
Любые идеи? Спасибо.
Update
Я заметил, что иногда содержащийся контроллер просмотра может находиться вне центра, а не полностью отсутствует.
![View Controller Off Centered]()
Я посмотрел глубже в сценарий отсутствия контроллера представления. Нажав "Иерархия просмотра отладки" и включив " Показывать сжатый контент, было показано следующее, когда полный контроллер View отсутствует:
![Включение Show Clipped Content]()
![Обрезанный контент]()
Итак, кажется, что недостающее содержимое обрезается/выходит за пределы.
Отображение только каркасов показывает следующее:
![только проводные кадры]()
Контроллер просмотра страницы имеет
- _UIPageViewControllerContentView, содержащий
- _UIQueuingScrollView, содержащий
- UIView, содержащий
- VehicleDetailTableViewController (UITableViewController с автомобильным изображением и деталями).
Я также вижу, что границы _UIQueuingScrollView совершенно разные, когда все странно. Оценки имеют x из 1125 в отличие от X 375, когда все нормально.
Это происходит только при использовании стиля перехода прокрутки в отличие от скручивания страницы. При использовании скручивания страниц все работает нормально.
Как мы можем предотвратить/исправить это?
Второе обновление
Этот код устраняет проблему. Тем не менее, это оставляет более резкий опыт. Возможно, из-за задержки 0,4 секунды синий фон иногда появляется при нормальном использовании.
private func show(viewController:UIViewController, going direction: UIPageViewControllerNavigationDirection) {
let viewControllers = [viewController]
setViewControllers(viewControllers, direction: direction, animated: true, completion: { (_) in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4, execute: {
self.setViewControllers(viewControllers, direction: direction, animated: false, completion: nil)
})
})
}
Это не очень хороший пользовательский интерфейс. Есть ли лучший подход?
Я хочу, чтобы изменения прокрутки были плавными, а не кратковременно отображали синий фон и не теряли его содержимое, а также содержимое View Controller.
Ответы
Ответ 1
Хотя реальный ответ состоит в том, чтобы иметь как можно более простые (хотя и не простые) View Controllers, вот код, который устранил проблему с побочным эффектом отображения фона, когда пользователь переходит к следующему представлению контроллер.
private func show(viewController:UIViewController, going direction: UIPageViewControllerNavigationDirection) {
let viewControllers = [viewController]
setViewControllers(viewControllers, direction: direction, animated: true, completion: { (_) in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4, execute: {
self.setViewControllers(viewControllers, direction: direction, animated: false, completion: nil)
})
})
}
Ответ 2
Простое решение состоит в том, чтобы отделить кнопки от изменений в контроллере представления, добавив небольшой буфер "нажмите вперед". Создайте очередь кнопок (используйте простой NSMutableArray
, который действует как очередь FIFO), где вы добавляете каждую кнопку навигации, затем вызывайте функцию dequeue, если очередь была пустой до добавления.
В функции dequeue вы удаляете первую запись и меняете вид соответственно, затем снова вызываете себя в обработчике завершения setViewControllers
, если очередь не пуста.
Обязательно выполняйте обработку в основном потоке, чтобы избежать проблем с потоками. Если вы хотите, вы также можете добавить ограничения на количество "нажмите" вперед ", которые вы разрешите, и, возможно, очистите очередь от изменений направления.
Ответ 3
Привет, я создал Пример проекта, который должен решить вашу проблему. Я добавил 100 ViewControllers (через цикл), и он отлично работает с анимацией прокрутки. вещи на их месте.
Что я сделал в этом проекте:
-
Создал BaseClass для страницы с двумя свойствами
a) pageIndex типа Int
b) делегировать для протокола для обратных вызовов
-
Добавлен UIPageViewController в ViewController через ContainerView
-
Создан ViewController с именем Page, который расширяет страницу PageViewBase
-
Запустите цикл count 100 и добавьте данные в массив и установите источник данных и делегируйте его самостоятельно (PageControlelr) и управляйте им в соответствии со свойством pageIndex
PageViewController
class PageViewController: UIPageViewController {
var list = [Page]()
var sb: UIStoryboard?
var viewController: ViewController! // settting from ViewController
override func viewDidLoad() {
super.viewDidLoad()
sb = UIStoryboard(name: "Main", bundle: nil)
DispatchQueue.main.asyncAfter(deadline: .now()+0.4, execute: {
self.setupList()
})
}
func setupList(){
for i in 0..<100{
let model = PageModel(title: "Title \(i + 1)", subTitle: "SubTitle \(i + 1)")
let page = sb?.instantiateViewController(withIdentifier: "PageID") as! Page
page.data = model
page.pageIndex = i
page.delegate = viewController
list.append(page)
}
self.delegate = self
self.dataSource = self
setViewControllers([list[0]], direction: .forward, animated: true, completion: nil)
self.updateCurrentPageLabel(index: 0)
}
func movePage(index: Int){
let currentIndex = self.viewControllers![0] as! Page
self.updateCurrentPageLabel(index: index)
setViewControllers([list[index]], direction: index > currentIndex.pageIndex ? .forward : .reverse, animated: true)
}
func getCurrentPageIndex() -> Int{
return (self.viewControllers![0] as! Page).pageIndex
}
func updateCurrentPageLabel(index: Int){
(self.parent as? ViewController)?.currentListingLabel.text = "\(index + 1) of \(list.count)"
}
}
extension PageViewController: UIPageViewControllerDelegate{
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
let currentIndex = (self.viewControllers![0] as! Page).pageIndex
self.updateCurrentPageLabel(index: currentIndex)
}
}
extension PageViewController: UIPageViewControllerDataSource{
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
let index = (viewController as! Page).pageIndex
if index > 0 {
return list[index-1]
}
return nil
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
let index = (viewController as! Page).pageIndex
if index < list.count-1 {
return list[index+1]
}
return nil
}
}
Страница
import UIKit
struct PageModel {
var title: String
var subTitle: String
}
class Page: PageViewBase {
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var subTitleLabel: UILabel!
@IBOutlet weak var imageView: UIImageView!
@IBOutlet weak var btnWorking: UIButton!
var data: PageModel?
override func viewDidLoad() {
super.viewDidLoad()
setupTags()
setupActions()
setupData()
}
func setupData(){
if let data = data{
self.titleLabel.text = data.title
self.subTitleLabel.text = data.subTitle
imageView.image = #imageLiteral(resourceName: "car")
}
}
enum buttonTags: Int{
case working = 1
}
func setupTags(){
btnWorking.tag = buttonTags.working.rawValue
}
func setupActions(){
btnWorking.addTarget(self, action: #selector(self.didSelect(_:)), for: .touchUpInside)
}
@objc func didSelect(_ sender: UIView){
if let tag = buttonTags.init(rawValue: sender.tag){
switch tag{
case .working:
delegate?.didReceive(withMessage: "wokring button clicked of index \(pageIndex)")
}
}
}
}
ViewController//MainController
import UIKit
protocol CallBack {
func didReceive(withMessage message: String)
}
class ViewController: UIViewController {
@IBOutlet weak var containerView: UIView!
@IBOutlet weak var btnCall: UIButton!
@IBOutlet weak var btnMessage: UIButton!
@IBOutlet weak var btnNext: UIButton!
@IBOutlet weak var btnBack: UIButton!
@IBOutlet weak var currentListingLabel: UILabel!
var pageController: PageViewController?
override func viewDidLoad() {
super.viewDidLoad()
setupTags()
setupActions()
setupContainerView()
}
enum buttonTags: Int{
case call = 1
case message
case next
case back
}
func setupTags(){
btnCall.tag = buttonTags.call.rawValue
btnMessage.tag = buttonTags.message.rawValue
btnNext.tag = buttonTags.next.rawValue
btnBack.tag = buttonTags.back.rawValue
}
func setupActions(){
btnCall.addTarget(self, action: #selector(self.didSelect(_:)), for: .touchUpInside)
btnMessage.addTarget(self, action: #selector(self.didSelect(_:)), for: .touchUpInside)
btnNext.addTarget(self, action: #selector(self.didSelect(_:)), for: .touchUpInside)
btnBack.addTarget(self, action: #selector(self.didSelect(_:)), for: .touchUpInside)
}
@objc func didSelect(_ sender: UIView){
if let tag = buttonTags.init(rawValue: sender.tag){
switch tag{
case .call:
print("Call button called for index \(pageController?.getCurrentPageIndex() ?? 0)")
case .message:
print("message button called for index \(pageController?.getCurrentPageIndex() ?? 0)")
case .next:
if let p = pageController{
let currentIndex = p.getCurrentPageIndex()
if currentIndex < p.list.count - 1{
p.movePage(index: currentIndex + 1)
}
}
case .back:
if let p = pageController{
let currentIndex = p.getCurrentPageIndex()
if currentIndex > 0{
p.movePage(index: currentIndex - 1)
}
}
}
}
}
func setupContainerView(){
let sb = UIStoryboard(name: "Main", bundle: nil)
pageController = sb.instantiateViewController(withIdentifier: "PageViewControllerID") as? PageViewController
pageController?.viewController = self
addViewIntoParentViewController(vc: pageController)
}
func addViewIntoParentViewController(vc: UIViewController?){
if let vc = vc{
for v in self.containerView.subviews{
v.removeFromSuperview()
}
self.containerView.addSubview(vc.view)
self.containerView.translatesAutoresizingMaskIntoConstraints = false
vc.view.translatesAutoresizingMaskIntoConstraints = false
addChildViewController(vc)
NSLayoutConstraint.activate([
vc.view.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
vc.view.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
vc.view.topAnchor.constraint(equalTo: containerView.topAnchor),
vc.view.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
])
vc.didMove(toParentViewController: self)
}
}
}
extension ViewController: CallBack{
func didReceive(withMessage message: String) {
print("message: \(message)")
}
}
PageViewBase
import UIKit
class PageViewBase: UIViewController {
var pageIndex = -1
var delegate: CallBack?
}
Ответ 4
Я не уверен, что это решение будет подходящим для ваших пользователей, но если проблема возникла из-за быстрого перехода на пользователя, вы можете реализовать блокировку, которая запретила бы эту быструю навигацию. По существу:
private func show(viewController:UIViewController, going direction: UIPageViewControllerNavigationDirection) {
guard !isChangingPages else { return }
isChangingPages = true
let viewControllers = [viewController]
let isAnimated = true // false always works. However, animation is required.
setViewControllers(viewControllers, direction: direction, animated: isAnimated, completion: { [weak self] _ in
self?.isChangingPages = false
})
}
Таким образом, вам нужно будет закончить переход на новую страницу, прежде чем разрешить переход к следующему.
Вероятно, это приведет к путанице для пользователя, если вы сохранили кнопки навигации, если для этого параметра bool было установлено значение true (нажатие без видимого результата). Но логику можно было бы изменить, чтобы отключить кнопки и повторно использовать их в блоке завершения (таким образом они будут исчезать в/из во время смены страницы).