Я использую UIPageView, чтобы позволить пользователям прокручивать коллекцию изображений, и получаю ошибку ограничения (без общего предка), когда UIPageViewController
установлен на стиль перехода Scroll
, но не когда это установить на Page Curl
.
До сих пор я пытался изменить привязки ограничений, выбрасывая ошибки (я точно определил, кто из них является виновником), на разные вещи (пробовал Safe Area
и Superview
), но ничего не изменило , Я относительно новичок в Swift и мало знаю о том, как работает UIPageViewController
, поэтому есть хороший шанс, что я просто называю вещи в неправильном порядке и не осознаю этого. Я немного смущен, хотя, почему ошибка only происходит со стилем перехода 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
) довольно прост. Он состоит из UIScrollView
, привязанного к Safe Area
, с UIImageView
, привязанного к UIScrollView
.
Приложение вылетает, как только я пытаюсь перейти к следующей странице в представлении страницы, выдавая следующую ошибку:
*** 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
работает отлично. Контроллер представления также прекрасно воспроизводится, когда загружается сам по себе, за пределами представления страницы.
EDIT:
Вот код для 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)
}
}
}
И соответствующая раскадровка:
Скриншот раскадровки