仅当样式设置为“滚动”时 UIPageViewController 中的约束异常

Posted

技术标签:

【中文标题】仅当样式设置为“滚动”时 UIPageViewController 中的约束异常【英文标题】:Constraint exception in UIPageViewController only when style is set to 'Scroll' 【发布时间】:2019-07-08 05:33:49 【问题描述】:

我正在使用 UIPageView 来允许用户滚动浏览一组图像,并且当 UIPageViewController 设置为 Scroll 转换样式时出现约束错误(没有共同祖先),但不是设置为Page Curl

到目前为止,我已经尝试将引发错误的约束的锚点(我已经确定哪些是罪魁祸首)更改为不同的东西(尝试了 Safe AreaSuperview),但没有任何改变.我对 Swift 比较陌生,对UIPageViewController 的工作方式不太了解,所以很有可能我只是按错误的顺序调用事物而没有意识到这一点。我有点困惑,为什么错误发生在Scroll 过渡样式而不是Page Curl,因为在我看来它们应该类似地工作......也对为什么感到困惑当它锚定到自己的超级视图时,约束锚点不在同一个视图层次结构中?

这是UIPageViewController 代表:

class ImageDetailPageViewController: UIPageViewController 

    // MARK: Properties

    var images: [UIImage]!
    var index: Int!

    var navbarHeight: CGFloat!

    fileprivate lazy var pages: [UIViewController] = 
        return getPages()
    ()

    // MARK: Methods

    override func viewDidLoad() 
        super.viewDidLoad()

        self.dataSource = self
        self.delegate = self

        self.view.translatesAutoresizingMaskIntoConstraints = false

        setViewControllers([pages[index]], direction: .forward, animated: true, completion: nil)
    

    override var preferredStatusBarStyle: UIStatusBarStyle 
        return .lightContent
    

    // MARK: Methods

    fileprivate func getPages() -> [ImageView] 
        var pages = [ImageView]()

        let storyboard = UIStoryboard(name: "ImageDetail", bundle: nil)

        for image in images 
            let imageDetail = storyboard.instantiateViewController(withIdentifier: "ImageDetail") as! ImageView
            imageDetail.image = image

            pages.append(imageDetail)
        

        return pages
    


// MARK: Extensions

extension ImageDetailPageViewController: UIPageViewControllerDataSource 
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? 
        guard let viewControllerIndex = pages.firstIndex(of: viewController) else 
            return nil
        

        let previousIndex = viewControllerIndex - 1

        guard previousIndex >= 0 else 
            return pages.last
        

        guard pages.count > previousIndex else 
            return nil
        

        return pages[previousIndex]
    

    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController?        
        guard let viewControllerIndex = pages.firstIndex(of: viewController) else 
            return nil
        

        let nextIndex = viewControllerIndex + 1

        guard nextIndex < pages.count else 
            return pages.first
        

        guard pages.count > nextIndex else 
            return nil
        

        return pages[nextIndex]
    

    func presentationCount(for pageViewController: UIPageViewController) -> Int 
        return pages.count
    

    func presentationIndex(for pageViewController: UIPageViewController) -> Int 
        return index
    


extension ImageDetailPageViewController: UIPageViewControllerDelegate  

页面视图 (ImageDetail) 中的视图控制器非常简单。它由一个锚定到Safe AreaUIScrollView 和一个锚定到UIScrollViewUIImageView 组成。

当我尝试在页面视图中滚动到下一页时,应用程序立即崩溃,并引发以下错误:

*** Terminating app due to uncaught exception 'NSGenericException', reason: 'Unable to activate constraint with anchors <NSLayoutYAxisAnchor:0x600001b72400 "UIImageView:0x7ff44c56caf0.top"> and <NSLayoutYAxisAnchor:0x600001b77e40 "UIScrollView:0x7ff44d164800.top"> because they have no common ancestor.  Does the constraint or its anchors reference items in different view hierarchies?  That's illegal.'

当设置为Page Curl 时,它可以完美运行。视图控制器在页面视图之外单独加载时也能完美呈现。

编辑: 这是ImageDetail 视图的代码:

class ImageView: UIViewController, UIScrollViewDelegate 

    // MARK: Properties

    @IBOutlet weak var scrollView: UIScrollView!
    @IBOutlet weak var imageView: UIImageView!
    @IBOutlet var zoomGestureRecognizer: UITapGestureRecognizer!

    // imageView contraints
    @IBOutlet var imageViewBottomConstraint: NSLayoutConstraint!
    @IBOutlet var imageViewLeadingConstraint: NSLayoutConstraint!
    @IBOutlet var imageViewTopConstraint: NSLayoutConstraint!
    @IBOutlet var imageViewTrailingConstraint: NSLayoutConstraint!

    var image: UIImage!

    // MARK: Overrides

    override func viewDidLoad() 
        super.viewDidLoad()

        // set the image
        if let image = image 
            imageView.image = image
         else 
            // log error
            os_log("Error. Image Detail opened without an image loaded.", log: OSLog.default, type: .error)

            // dismiss view
            self.dismiss(animated: false, completion: nil)
        

        // assume scrollView delegate
        scrollView.delegate = self
        scrollView.maximumZoomScale = 2

        // set imageView snapshot mode
        imageView.snapshotView(afterScreenUpdates: true)
    

    override func viewWillLayoutSubviews() 
        super.viewWillLayoutSubviews()

        updateMinZoomScaleForSize(view.compatibleSafeAreaLayoutGuide.layoutFrame.size)
        updateConstraintsForSize(view.compatibleSafeAreaLayoutGuide.layoutFrame.size)
        scrollView.setZoomScale(scrollView.minimumZoomScale, animated: false)
    

    // MARK: Methods

    func viewForZooming(in scrollView: UIScrollView) -> UIView? 
        return imageView
    

    func scrollViewDidZoom(_ scrollView: UIScrollView) 
        updateConstraintsForSize(view.compatibleSafeAreaLayoutGuide.layoutFrame.size)
    

    fileprivate func updateConstraintsForSize(_ size: CGSize) 
        let yOffset = max(0, (view.compatibleSafeAreaLayoutGuide.layoutFrame.height - imageView.frame.height) / 2)
        imageViewTopConstraint.constant = yOffset
        imageViewBottomConstraint.constant = yOffset

        let xOffset = max(0, (view.compatibleSafeAreaLayoutGuide.layoutFrame.width - imageView.frame.width) / 2)
        imageViewLeadingConstraint.constant = xOffset
        imageViewTrailingConstraint.constant = xOffset

        // activate constraints
        imageViewTopConstraint.isActive = true
        imageViewBottomConstraint.isActive = true
        imageViewLeadingConstraint.isActive = true
        imageViewTrailingConstraint.isActive = true

        view.layoutIfNeeded()
    

    fileprivate func updateMinZoomScaleForSize(_ size: CGSize) 
        let widthScale = view.compatibleSafeAreaLayoutGuide.layoutFrame.width / imageView.bounds.width
        let heightScale = view.compatibleSafeAreaLayoutGuide.layoutFrame.height / imageView.bounds.height
        let minScale = min(widthScale, heightScale)

        scrollView.minimumZoomScale = minScale
        scrollView.zoomScale = minScale
    

    fileprivate func zoomRectForScale(_ scale: CGFloat, center: CGPoint) -> CGRect 
        // create and size view window
        var zoomRect = CGRect.zero
        zoomRect.size.height = imageView.frame.size.height
        zoomRect.size.width = imageView.frame.size.width

        // center on tapped point
        let newCenter = imageView.convert(center, from: view)
        zoomRect.origin.x = newCenter.x - (zoomRect.size.width / 2.0)
        zoomRect.origin.y = newCenter.y - (zoomRect.size.height / 2.0)

        // return rect
        return zoomRect
    

    // MARK: Actions

    @IBAction func tapToZoom(_ sender: UITapGestureRecognizer) 
        if scrollView.zoomScale == scrollView.minimumZoomScale 
            scrollView.zoom(to: zoomRectForScale(scrollView.maximumZoomScale, center: zoomGestureRecognizer.location(in: zoomGestureRecognizer.view)), animated: true)
         else 
            scrollView.setZoomScale(scrollView.minimumZoomScale, animated: true)
        
    

以及相应的情节提要: Storyboard screenshot

【问题讨论】:

self.view.translatesAutoresizingMaskIntoConstraints = false 评论此行,它将起作用! @SohilR.Memon 对此发表了评论并得到了完全相同的错误:( 能否请您发布详细控制器的代码,以便我可以看到您设置的约束! 你需要用imageView和scrollView展示你的storyboard的截图,或者给我们看代码。因为那是 Xcode 指向的地方。 @SohilR.Memon 这是一个演示项目:github.com/jaismith/TestApp,抱歉回复缓慢,感谢您的帮助! 【参考方案1】:

知道imageView.snapshotView(afterScreenUpdates: true) 这一行明确告诉渲染UIImageView 的外观,如果失败,则快照可能没有可见内容。

我不确定您要达到什么目的,但注释掉该行肯定会解决您的问题。如果您需要任何帮助来呈现您想要的 UI 或其他内容,请告诉我。

阅读官方documentation了解更多详情。

【讨论】:

您好!后续问题:当我注释掉 imageView.snapshotView(afterScreenUpdates: true) 时,图像渲染不正确并且 scrollView 缩放中断(无限缩小)。我已经用更改更新了演示项目,非常感谢您提供的任何其他指针。 @Jai Sure 会检查并通知您 碰上这个...有什么解决办法吗?提前致谢!

以上是关于仅当样式设置为“滚动”时 UIPageViewController 中的约束异常的主要内容,如果未能解决你的问题,请参考以下文章

单行 UITableView 直到滚动才显示 detailTextLabel

仅当父元素具有特定类时才为列表项元素设置样式[重复]

仅当键盘快速出现时如何使scrollView滚动

位置样式为绝对时滚动时的文本框定位问题

仅当窗口缩小时如何启用滚动条?

仅当父滚动视图到达结束时滚动子滚动视图