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 (нажатие без видимого результата). Но логику можно было бы изменить, чтобы отключить кнопки и повторно использовать их в блоке завершения (таким образом они будут исчезать в/из во время смены страницы).