仅当样式设置为“滚动”时 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 Area
和 Superview
),但没有任何改变.我对 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 Area
的UIScrollView
和一个锚定到UIScrollView
的UIImageView
组成。
当我尝试在页面视图中滚动到下一页时,应用程序立即崩溃,并引发以下错误:
*** 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 中的约束异常的主要内容,如果未能解决你的问题,请参考以下文章