12
votes

My view hierarchy is this

PhotoDetailViewController.swift

    View
      UIScrollView
        UIImageView

I set this up using storyboard, and add four constraints(top=0, bottom=0, leading=0, tailing=0) to UIScrollView, four constraints(top=0, bottom=0, leading=0, tailing=0) to UIImageView, but there are two error says

"ScrollView has ambiguous scrollable content width"
"ScrollView has ambiguous scrollable content height"

I understand that this is because I haven't set UIScrollView contentSize, but What I trying to do is load photo from PHAsset asynchronously, so I can only get the photo size at run time. So the question is:

1:Given that photo size can only be get at run time, how to solve the "ambiguous scrollable content" error?

2:In which View's life cycle method should I call PHImageManager.requestImageForAsset? because I think I should set UIScrollView contentSize programmatically, but when?

update with PhotoDetailViewController.swift

import UIKit
import Photos

class PhotoDetailViewController : UIViewController {

@IBOutlet weak var scrollView: UIScrollView!
@IBOutlet weak var imageViewBottomConstraint: NSLayoutConstraint!
@IBOutlet weak var imageViewLeadingConstraint: NSLayoutConstraint!
@IBOutlet weak var imageViewTopConstraint: NSLayoutConstraint!
@IBOutlet weak var imageViewTrailingConstraint: NSLayoutConstraint!

var devicePhotosAsset : PHFetchResult!
var index = 0
var photo : UIImage!
var imgManager:PHImageManager!

@IBOutlet weak var imageView : UIImageView!

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
}

override func awakeFromNib() {
    self.imgManager = PHImageManager()
}

override func viewDidLoad() {
    super.viewDidLoad()
    self.displayPhoto()
}

override func viewWillLayoutSubviews() {
    super.viewDidLayoutSubviews()
    updateMinZoomScaleForSize()
    updateConstraintsForSize()
}

func displayPhoto () {
    _ = self.imgManager.requestImageForAsset(self.devicePhotosAsset[self.index] as! PHAsset, targetSize: PHImageManagerMaximumSize, contentMode: .AspectFit, options: nil, resultHandler: {(result, info) -> Void in
            NSOperationQueue.mainQueue().addOperationWithBlock(){
                self.imageView.image = result
            }

        })
}

private func targetSize() -> CGSize {
    let scale = UIScreen.mainScreen().scale
    let targetSize = CGSizeMake(CGRectGetWidth(self.imageView.bounds)*scale, CGRectGetHeight(self.imageView.bounds)*scale)

    return targetSize
}

private func updateMinZoomScaleForSize() {
    let size = scrollView.bounds.size
    let widthScale = size.width / imageView.bounds.width
    let heightScale = size.height / imageView.bounds.height
    let minScale = min(widthScale, heightScale)

    scrollView.minimumZoomScale = minScale

    scrollView.zoomScale = minScale
}

func recenterImage(){
    let scrollViewSize = scrollView.bounds.size
    let imageSize = imageView.frame.size

    let horizontalSpace = imageSize.width < scrollViewSize.width ? (scrollViewSize.width - imageSize.width)/2 : 0
    let verticalSpace = imageSize.height < scrollViewSize.height ? (scrollViewSize.height - imageSize.height)/2 : 0
    scrollView.contentInset = UIEdgeInsets(top: verticalSpace, left: horizontalSpace, bottom: verticalSpace, right: horizontalSpace)
}

private func updateConstraintsForSize() {
    let size = scrollView.bounds.size
    let yOffset = max(0, (size.height - imageView.frame.height) / 2)
    imageViewTopConstraint.constant = yOffset
    imageViewBottomConstraint.constant = yOffset

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

    view.layoutIfNeeded()
}
}

extension PhotoDetailViewController: UIScrollViewDelegate {
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
    return imageView
}

func scrollViewDidZoom(scrollView: UIScrollView) {
    updateConstraintsForSize()
}
}
4

4 Answers

7
votes

Your existing constraints are enough to set the content size, it's just that it's based on the image view intrinsic content size and that doesn't really exist until the image view has an image.

You can add a width and height constraint to the image view with default values and deactivate those constraints when the image is set to the view. Or you could use a placeholder image and avoid those extra constraints because you'd always have an intrinsic content size for the image view.

3
votes

You should set two more constraint to your imageView.

  1. Horizontally in Container (or you can say it center X)
  2. Fixed Height

Second thing you can put UIView on Scrollview with Constraints like, Top,leading,trailing,bottom,Horizontally in container(center x),fixed height).

Then add your imageview to that view. And can change it's constraint after getting image to resize it's height and width.

You can connect outlet of any constraint and can change it's constant programmatically.

3
votes

Xcode UI builder has special type of constraint for such cases (when you can setup constraint only in runtime). It's so called "placeholder constraint" which will be removed at build time but helps to remove constraints errors for developing.

Placeholder constraint

So solution is

  1. Add some sample constraints IB and mark them as placeholders
  2. Add needed constraints in runtime
2
votes

When you get the data, just add these lines

float sizeOfContent = 0;
UIView *lLast = [yourscrollview.subviews lastObject];
NSInteger wd = lLast.frame.origin.y;
NSInteger ht = lLast.frame.size.height;
sizeOfContent = wd+ht;
yourscrollview.contentSize = CGSizeMake(yourscrollview.frame.size.width, sizeOfContent);

Hope this helps