UIScrollView with Embedded UIImageView; how to get the image to fill the screen
You might find this useful...
It allows you to zoom an image in a scrollView, starting with it centered and maintaining aspect ratio.
Here's a complete implementation. It has two important variables at the top:
// can be .scaleAspectFill or .scaleAspectFit
var fitMode: UIView.ContentMode = .scaleAspectFill
// if fitMode is .scaleAspectFit, allowFullImage is ignored
// if fitMode is .scaleAspectFill, image will start zoomed to .scaleAspectFill
// if allowFullImage is false, image will zoom back to .scaleAspectFill if "pinched in"
// if allowFullImage is true, image can be "pinched in" to see the full image
var allowFullImage: Bool = true
Everything is done via code - no @IBOutlet
or other connections - so just create add a new view controller and assign its custom class to ZoomAspectViewController
(and edit the name of the image you want to use):
class ZoomAspectViewController: UIViewController, UIScrollViewDelegate {
var scrollView: UIScrollView!
var imageView: UIImageView!
var imageViewBottomConstraint: NSLayoutConstraint!
var imageViewLeadingConstraint: NSLayoutConstraint!
var imageViewTopConstraint: NSLayoutConstraint!
var imageViewTrailingConstraint: NSLayoutConstraint!
// can be .scaleAspectFill or .scaleAspectFit
var fitMode: UIView.ContentMode = .scaleAspectFit
// if fitMode is .scaleAspectFit, allowFullImage is ignored
// if fitMode is .scaleAspectFill, image will start zoomed to .scaleAspectFill
// if allowFullImage is false, image will zoom back to .scaleAspectFill if "pinched in"
// if allowFullImage is true, image can be "pinched in" to see the full image
var allowFullImage: Bool = true
override func viewDidLoad() {
super.viewDidLoad()
guard let img = UIImage(named: "myImage") else {
fatalError("Could not load the image!!!")
}
scrollView = UIScrollView()
imageView = UIImageView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleToFill
scrollView.addSubview(imageView)
view.addSubview(scrollView)
// respect safe area
let g = view.safeAreaLayoutGuide
imageViewTopConstraint = imageView.topAnchor.constraint(equalTo: scrollView.topAnchor)
imageViewBottomConstraint = imageView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor)
imageViewLeadingConstraint = imageView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor)
imageViewTrailingConstraint = imageView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor)
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: g.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: g.bottomAnchor),
scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
imageViewTopConstraint,
imageViewBottomConstraint,
imageViewLeadingConstraint,
imageViewTrailingConstraint,
])
scrollView.delegate = self
scrollView.minimumZoomScale = 0.1
scrollView.maximumZoomScale = 5.0
imageView.image = img
imageView.frame.size = img.size
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { _ in
self.updateMinZoomScaleForSize(size, shouldSize: (self.scrollView.zoomScale == self.scrollView.minimumZoomScale))
self.updateConstraintsForSize(size)
}, completion: {
_ in
})
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
updateMinZoomScaleForSize(scrollView.bounds.size)
updateConstraintsForSize(scrollView.bounds.size)
if fitMode == .scaleAspectFill {
centerImageView()
}
}
func updateMinZoomScaleForSize(_ size: CGSize, shouldSize: Bool = true) {
guard let img = imageView.image else {
return
}
var bShouldSize = shouldSize
let widthScale = size.width / img.size.width
let heightScale = size.height / img.size.height
var minScale = min(widthScale, heightScale)
let startScale = max(widthScale, heightScale)
if fitMode == .scaleAspectFill && !allowFullImage {
minScale = startScale
}
if scrollView.zoomScale < minScale {
bShouldSize = true
}
scrollView.minimumZoomScale = minScale
if bShouldSize {
scrollView.zoomScale = fitMode == .scaleAspectFill ? startScale : minScale
}
}
func scrollViewDidZoom(_ scrollView: UIScrollView) {
updateConstraintsForSize(scrollView.bounds.size)
}
func centerImageView() -> Void {
let yOffset = (scrollView.frame.size.height - imageView.frame.size.height) / 2
let xOffset = (scrollView.frame.size.width - imageView.frame.size.width) / 2
scrollView.contentOffset = CGPoint(x: -xOffset, y: -yOffset)
}
func updateConstraintsForSize(_ size: CGSize) {
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()
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageView
}
}
Edit
As an example, I used this image (2560 x 1440):
and I get this result on launch:
and maximum zoom in (5.0) scrolled to top-center:
Edit 2
Same image, at launch, with:
var fitMode: UIView.ContentMode = .scaleAspectFill
instead of .scaleAspectFit
:
How can I fill image in scroll view to fill screen?
In this case you need to enable the parent view clipsToBounds. Set
UIScrollview
clipsToBounds
property to True.
Programmatically scrollView.clipsToBounds = true
In UIStoryBoard
- Click the view->Attributes Inspector
If you would like to see the whole screen, make sure to add the topConstraint
of scrollView
assigned superView
and hide the navigationBar
in viewWillAppear
,
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.setNavigationBarHidden(true, animated: animated)
}
Make sure to remove the status bar by
override var prefersStatusBarHidden: Bool {
return true
}Update the Y position of Image.
imageView.frame = CGRect(x: x, y: **self.scrollView.frame.minY**, width: self.view.frame.width, height: self.view.frame.height)
Update the
scrollView
topConstraint by -20.
How do I get the position of the UIImageView in my UIScrollView and only allow it to be panned?
Is it possible to make it so only the image can be panned, not the full scroll view?
Implement - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
in your gesture recogniser's delegate, then test that UITouch
's position against that of your image view.
So, probably something like:
CGPoint touchLocation = [touch locationInView:scrollView];
return CGRectContainsPoint(imageView.frame, touchLocation);
How do I get the location the image is at currently?
According to which coordinate space? Assuming you mean relative to a view controller's top-level view, use [viewController.view convertRect:imageView.frame fromView:scrollView]
.
Scroll views scroll their content by changing their bounds
. They don't change anything on the included views. UIView -convertRect:fromView
and the others consolidate all the logic for mapping from the internal coordinate space of one view to another, adjusting for transforms and internal scrolling and everything.
UIImage in UIImageView that is in UIScrollView is automatically zoomed in - how to initially view whole UIImage?
You want to set the .minimumZoomScale
to the ratio between your image size and the scroll view size, and then set the .zoomScale
to that value to start with.
Here's your code, with a few modifications:
class ViewImageViewController: UIViewController, UIScrollViewDelegate {
var imageToPresent: UIImage!
let scrollView: UIScrollView = {
let view = UIScrollView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let imageView: UIImageView = {
let imgView = UIImageView()
imgView.backgroundColor = UIColor.black
imgView.contentMode = .scaleAspectFit
imgView.isUserInteractionEnabled = true
imgView.translatesAutoresizingMaskIntoConstraints = false
return imgView
}()
override func viewDidLoad() {
super.viewDidLoad()
print("ViewImageViewController.viewDidLoad")
if let img = UIImage(named: "background") {
imageToPresent = img
}
view.backgroundColor = .gray // UIColor.appGrayForLabels
setupViews()
scrollView.delegate = self
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let scrollViewFrame = scrollView.frame
let scaleWidth = scrollViewFrame.size.width / imageToPresent.size.width
let scaleHeight = scrollViewFrame.size.height / imageToPresent.size.height
let minScale = min(scaleWidth, scaleHeight)
scrollView.minimumZoomScale = minScale
scrollView.maximumZoomScale = 4.0
scrollView.zoomScale = minScale
}
private func setupViews(){
scrollView.isScrollEnabled = true
view.addSubview(scrollView)
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
scrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
guard let img = imageToPresent else{return}
imageView.image = img
scrollView.addSubview(imageView)
let g = scrollView.contentLayoutGuide
imageView.topAnchor.constraint(equalTo: g.topAnchor).isActive = true
imageView.bottomAnchor.constraint(equalTo: g.bottomAnchor).isActive = true
imageView.leadingAnchor.constraint(equalTo: g.leadingAnchor).isActive = true
imageView.trailingAnchor.constraint(equalTo: g.trailingAnchor).isActive = true
imageView.widthAnchor.constraint(equalToConstant: imageToPresent.size.width).isActive = true
imageView.heightAnchor.constraint(equalToConstant: imageToPresent.size.height).isActive = true
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageView
}
}
Can't scroll UIImageView with AspectFill in UIScrollView and AutoLayout
the child imageview is also pinned to all sides
Instead, turn off auto layout for the child image view (set its translates...
to YES). You are already setting the scroll view's contentSize
to the image size, so now scrollability will leap into life and will interface correctly with zooming.
Related Topics
Simultaneous Gesture Recognition for Specific Gestures
How to Check the Network Speed Using Swift
Are Lazy Vars in Swift Computed More Than Once
What's the Difference Between Using or Not Using the 'Where' Clause with Generics
How to Convert a String to a Cstring in the Swift Language
Rxswift Merge Different Kind of Observables
Extending Collection with a Recursive Property/Method That Depends on the Element Type
Unexpectedly Large Realm File Size
Autolayout Contraints for a View from Xib
How to Track More Than 4 Images at a Time with Arkit
Firebase Observe Called After Following Command
Today Extension Failed to Inherit Coremedia Permissions From
Obj-C Cocoapods + Swift Framework
How to Change Font Size and Font Name of Uisegmentedcontrol Programmatically on Swift
How to Link a .Sks File to a .Swift File in Spritekit
Passing Data from Tableview to Viewcontroller in Swift
Swift Dictionary: Remove Time Complexity
iOS 8 Beta Today Extension Widget Not Showing in a Swift App