Center UIImageView in UIScrollView

Hi,


part of my app is an image viewer of postprocessed images shot with the iPhone. I would like to display the images in aspect fit in the center of the screen inside a ScrollView. To do so I made a UIScrolView and UIImageView with constraints to fill the whole screen. The UIImageView is set to Aspect Fill, whereas the UIScrollView is set to Aspect Fit in the Storyboard.


When the result ViewController is called, the image still needs to be calculated, so I show a ActivityIndicator. When I just call


self.imageView.image = self.result_image


after the image is processed, the image fits from its height in portrait mode, but with regarding to its width, you can see therefore rafly 30% of the image. The center if that matters. To overcome that I run the following code directly after I asign an image to the imageView (the line above):


self.scrollView.contentMode = .ScaleAspectFit
self.imageView.sizeToFit()
self.scrollView.contentSize = CGSizeMake(self.imageView.frame.size.width, self.imageView.frame.size.height)
                   
let imageViewSize = self.imageView.bounds.size
let scrollViewSize = self.scrollView.bounds.size
let widthScale = scrollViewSize.width / imageViewSize.width
let heightScale = scrollViewSize.height / imageViewSize.height
              
let minZoomScale = min(widthScale, heightScale)
self.scrollView.minimumZoomScale = minZoomScale
self.scrollView.zoomScale = minZoomScale


Which displays the whole image in Aspect Fit at the upper border of the screen.


Once I zoom, however, the image get repositioned and resized:



The only other function regarding any of these two elements in my code is:


func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
   return self.imageView
}


Can anyone explain this strange behaviour and has any suggestions what I can do? I thought actually this would be an easy problem...


I really appreciate your help


Best,

Chris

Answered by cpoetter in 138725022

Hi QuinceyMorris,


Thank you so much for your help and the time you invested, but I couldn't make to work like that. I used now the following github example and now its working:


https://github.com/evgenyneu/ios-imagescroll-swift


I think my problem was that I did not set the ImageView in the Storyboard to be a Placeholder or something like that. At least that was one difference between my project file and theirs.


Thanks again a lot for your help.


Best,

Chris

It's a little hard to answer your question, in part because your description is a bit garbled in the 3rd paragraph, and because you've being flailing around a bit, adding extra code that's probably a step in the wrong direction.


If you have a scroll view, that's presumably because you want the content (the image view, right?) to scroll if it's bigger than the screen. If your image is bigger than the screen and you want to display it centered on the screen (so that it can be scrolled left or right to see the rest of it), then shouldn't you be setting the scroll view's contentOffset?


OTOH, if you want it to be scaled initially to fit completely on the screen? And only scroll if it's zoomed in? Sorry, I can't tell from what you've said.


The other point of confusion is that you don't seem to account for autolayout. Any constraints or default behavior might be working against your intentions, whatever they are.


Also, remember that zooming the image view via the scroll view will scale the image view, making it bigger in scroll view bounds coordinates, while leaving the size in image view bounds coordinates alone.


Lastly, remember that when a view has a transform (including scaling as a result of being zoomed), its "frame" property is meaningless. You should refer to its "center" and "bounds" properties instead, making sure you convert those correctly to the coordinate system you want to use for calculations.


Perhaps you can clarify some of this?

Hi QuinceyMorris,


thanks for helping me out in this! Sorry for the late answer, somehow I did not receive a notification that you replied on my post. I will try again to describe my intension. Actually it should do exactly what apples Photos App on the iPhone does when displaying a selected image. The image is in the center of the screen in aspect fit. But if you want to zoom you can do that. Also if you tap the image the navigation controller and tab bar should disappear.


I was generally able to achieve all of it with the Storyboard. There were checkboxes to hide the bars on tap. I can zoom, because the ImageView is inside of a ScrollView. I set the imageView to aspect fit, so that the image is "On Boot" shown in full with white filling on the top and bottom. The ScrollView has constraints to fill the whole screen and the ImageView has contrains to fill the whole ScrollView.


The problem I have with this solution is, that once I zoom these fillings at the top and bottom zoom with the image. Once I zoom in the top or bottom with a factor of 6 for example, I see just the white filling (which was neccessary for the aspect fit in the intital loading of the picture).


With the above approach is tried to overcome that problem of the filling, but it did not work out at all.


We don't need to rescue my code here, I would just like to know how to make this apple photos image view style 🙂


Best,

Chis

I dunno, the basic approach to this is straightforward:


1. Create a UIImage.


2. Set it as the "image" property of a UIImageView, allowing the image view to default to the size of the image.


3. Center the image view in its UIScrollView using autolayout constraints.


After that, the user can pan and zoom your image.


You can either create the image at the correct (100%) size to just fit on the screen, or you can leave it at its native size and set an initial zoom factor on the scroll view.


Beyond these basics, what else you need to do starts to get subtle, and depends on which part of the standard behavior needs refinement. For example, when you zoom in using the scroll view, it may scale your image by magnifying pixels. If your underlying data can provide more resolution (and if it matters to the user), you can replace the image with a higher-res image after the zoom has been done.


There is a series of WWDC videos about scroll views, one per year starting back in 2009 and ending in 2014 with this one:


https://developer.apple.com/videos/play/wwdc2014/235/


These are great, informative videos, and they're well worth watching. The only problem to be careful of is that, as they get older, they don't match the current state of the UIScrollView APIs. Some of the techniques they describe (such as pinned content subviews) don't need custom code any more.

Hi QuinceyMorris,


thanks for your elaborated answer. I started a new test project now for this purpose, so that nothing I may have done wrong in my main project file can affect this. It is a Single View Controller Project. I went to the storyboard, drag and dropped a UIScrollView in there and set the constrains of it to fill the whole screen. Following I put a UIImageView inside the UIScrollView and set just two constains in the storyboard:


Image View.centerX = centerX
Image View.centerY = centerY


These are the constains for the UIScrollView


trailing = Scroll View.trailing
Scroll View.top = top
Scroll View.leading = leading
Bottom Layout Guide.top = Scroll View.bottom


Then I ctrl clicked the UIScrollView onto the View Controller in the Storyboard and clicked deligate.


Following the code of this ViewController:


import UIKit
class ViewController: UIViewController {
    @IBOutlet weak var scrollView: UIScrollView!
    @IBOutlet weak var imageView: UIImageView!

    override func viewDidLoad() {
        super.viewDidLoad()

        self.scrollView.minimumZoomScale = 1.0
        self.scrollView.maximumZoomScale = 6.0

        self.imageView.image = UIImage(named: "test.jpg")
    }
    func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
        return self.imageView
    }
}


I loads the image in its original size, so much too big for the small iPhone screen. it also looks like as the center of the UIImageView is positioned at the upper left corner of the UIScrollView, because I can just see the lower right of my image. Also under the image is still white space left that I can scroll.


And when I zoom out to see the whole image it jumps automatically back to the state how it was before i started zooming out. Just zooming in works. So it looks like ZoomScale 1.0 is when displaying the whole image at its actual size and ZoomScale 6.0 allows me to zoom in even further. But I actually want quite the oposite. I want it to be completly visible at the beginning, where 1 iPhone pixel represents in reality 50 image pixel or so and then I want the user to be able to zoom in until 1 pixel of the phone equals one pixel of the image.


Did I maybe misunderstood your autolayout contraints? I thought you ment the storyboard layouts how I set them up earlier.


Thanks for your help.


Best,

Chris

Everything you say sounds correct, except for your min and max zoom scales. By setting the min scale to 1, you make it impossible to get the whole view visible at one time.


You'll need to do something along this line, in viewDidLoad:


Compute the scale needed to display your image in the view. From the point of view of the user, this scale is effectively 100% (at least, I think that's how you want it to seem to the user), so the scroll view's min and max scales need to be relative to this. I guess there's no need to zoom out at all, and you can limit the zoom to 6 times "normal". So you'd have code something like this:


let normalScale = scrollView.bounds.width / imageView.bounds.width
scrollView.zoomScale = normalScale
scrollView.minZoomScale = normalScale * 1 // or, if you want to allow the image to be made smaller, '* 0.5' or '* 0.25' or something
scrollView.maxZoomScale = normalScale * 6


If you want to allow the image be zoomed as far as 1:1, then you could alternatively set maxZoomScale to 1 (or, for safety, max (1, normalScale)). Of course, if the image ever changed, you'd have to repeat this calculation. Similarly, there might be a different calculation if the device is rotated, depending on what you want it to look like.


If the image is initially not centered in the scroll view, you might need to recalculate the scroll view's contentOffset too. Now that I think about it, it seems reasonable that the contentOffset isn't controlled by the centering constraints.


(Above code typed in the forum editor, so no guarantees it's error-free.)

Accepted Answer

Hi QuinceyMorris,


Thank you so much for your help and the time you invested, but I couldn't make to work like that. I used now the following github example and now its working:


https://github.com/evgenyneu/ios-imagescroll-swift


I think my problem was that I did not set the ImageView in the Storyboard to be a Placeholder or something like that. At least that was one difference between my project file and theirs.


Thanks again a lot for your help.


Best,

Chris

Center UIImageView in UIScrollView
 
 
Q