ios - 如果同时开始动画,则 UIPageViewController 转换会出现错误

标签 ios swift uipageviewcontroller

我在使用 UIPageViewController 的应用程序中有一个奇怪的行为。 我的应用程序的布局是一个 PageViewController(类似相机胶卷),底部有一个广告横幅。

横幅的容器开始时是隐藏的,当广告加载时,我用动画设置了 isHidden=false

我的问题是,当横幅进入屏幕时,如果正在进行中,它会中断 UIPageViewController 转换,如本视频所示:

enter image description here

我创建了一个新项目,只需几行就可以很容易地重现错误,您可以在 GITHUB 中查看它:您只需点击“下一步”按钮,直到加载横幅。它也可以通过滑动 PageViewController 来重现,但更难重现。

完整的示例代码是:

class TestViewController: UIViewController,UIPageViewControllerDelegate,UIPageViewControllerDataSource {
    @IBOutlet weak var constraintAdviewHeight: NSLayoutConstraint!
    weak var pageViewController : UIPageViewController?

    @IBOutlet weak var containerAdView: UIView!
    @IBOutlet weak var adView: UIView!
    @IBOutlet weak var containerPager: UIView!

    var currentIndex = 0;
    var clickEnabled = true

    override func viewDidLoad() {
        super.viewDidLoad()
        let pageVC = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
        pageViewController = pageVC
        pageVC.delegate = self
        pageVC.dataSource = self
        addChildViewController(pageVC)
        pageVC.didMove(toParentViewController: self)
        containerPager.addSubview(pageVC.view)
        pageVC.view.translatesAutoresizingMaskIntoConstraints = true
        pageVC.view.frame = containerPager.bounds
        pushViewControllerForCurrentIndex()
    }
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        self.containerAdView.isHidden = true

        DispatchQueue.main.asyncAfter(deadline: .now()+4) {
            self.simulateBannerLoad()
        }

    }

    @IBAction func buttonClicked(_ sender: Any) {
        guard clickEnabled else {return}
        currentIndex -= 1;
        pushViewControllerForCurrentIndex()

    }

    @IBAction func button2Clicked(_ sender: Any) {
        guard clickEnabled else {return}
        currentIndex += 1;
        pushViewControllerForCurrentIndex()
    }


    private func simulateBannerLoad(){
        constraintAdviewHeight.constant = 50
        pageViewController?.view.setNeedsLayout()
        UIView.animate(withDuration: 0.3,
                       delay: 0, options: .allowUserInteraction, animations: {
                        self.containerAdView.isHidden = false
                        self.view.layoutIfNeeded()
                        self.pageViewController?.view.layoutIfNeeded()
        })

    }

    //MARK: data source

    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        return getViewControllerForIndex(currentIndex+1)
    }

    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        return getViewControllerForIndex(currentIndex-1)
    }

    func getViewControllerForIndex(_ index:Int) -> UIViewController? {
        guard (index>=0) else {return nil}
        let vc :UIViewController = UIStoryboard(name: "Main", bundle: .main).instantiateViewController(withIdentifier: "pageTest")
        vc.view.backgroundColor = (index % 2 == 0) ? .red : .green
        return vc
    }

    func pushViewControllerForCurrentIndex() {
        guard let vc = getViewControllerForIndex(currentIndex) else {return}
        print("settingViewControllers start")
        clickEnabled = false
        pageViewController?.setViewControllers([vc], direction: .forward, animated: true, completion: { finished in
            print("setViewControllers finished")
            self.clickEnabled = true
        })
    }
}

注意:另一个不需要的效果是当错误发生时最后一个完成 block 没有被调用,所以它使按钮处于禁用状态:

    func pushViewControllerForCurrentIndex() {
        guard let vc = getViewControllerForIndex(currentIndex) else {return}
        print("settingViewControllers start")
        clickEnabled = false
        pageViewController?.setViewControllers([vc], direction: .forward, animated: true, completion: { finished in
            print("setViewControllers finished")
            self.clickEnabled = true
        })
    }

Note2:横幅加载事件是我无法手动控制的。由于用于显示广告的库,它在主线程中随时可能发生的回调。在示例项目中,这是通过 DispatchQueue.main.asyncAfter: 调用

模拟的

我该如何解决?谢谢

最佳答案

怎么了?

布局会中断动画。

如何预防?

在 pageViewController 动画时不改变布局。

加载广告时: 确认pageViewController是否正在动画,如果是,等到动画完成再更新,或者更新

示例:

    private func simulateBannerLoad(){
        if clickEnabled {
            self.constraintAdviewHeight.constant = 50
        } else {
            needUpdateConstraint = true
        }
    }
    var needUpdateConstraint = false
    var clickEnabled = true {
        didSet {
            if clickEnabled && needUpdateConstraint {
                self.constraintAdviewHeight.constant = 50
                needUpdateConstraint = false
            }
        }
    }

关于ios - 如果同时开始动画,则 UIPageViewController 转换会出现错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46988411/

相关文章:

ios - 类似Apple Watch健身应用的渐变画环

ios - UIPageViewController viewController 在过渡后消失

ios - 如何正确地将 NSNumber 转换为 long long int?

iphone - MPMediaItem 到 AVAsset 到 .MP3

multithreading - iOS 线程 "Modifying layer that is being finalized"

ios - 我是否可以发布其某些功能和 UI 因过期日期而更改的应用程序?

ios - 尝试使用 AVFoundation 捕获图像,捕获时输出图像非常暗(几乎看起来像黑屏)

返回 Firebase 用户名时的 iOS 异步任务问题

ios - viewControllerAfterViewController : does not get called with UIPageViewController

xcode - 为什么 UIButton 在模拟器中这么大?