Reputation: 1841
I was hoping someone could help me out. I am trying to allow a user to pinch zoom on a UIImageView(with a max and min level allowed). But for some reason the it does not work right. The image zooms a little then just bounces back. Thank you.
here is the zoom func
func zoom(sender:UIPinchGestureRecognizer) {
if sender.state == .Ended || sender.state == .Changed {
let currentScale = self.view.frame.size.width / self.view.bounds.size.width
var newScale = currentScale*sender.scale
if newScale < 1 {
newScale = 1
}
if newScale > 9 {
newScale = 9
}
let transform = CGAffineTransformMakeScale(newScale, newScale)
self.imageView?.transform = transform
sender.scale = 1
}
}
Upvotes: 92
Views: 136726
Reputation: 4768
[September 2024, Swift 5]
One of the ways It seems to be working fine for me was embedding the image view into a scroll view which you can have a quick look at the library I'm sharing for better understanding to keep this answer short. I have built up this small library and used it in production apps. You can install or just copy-paste the files into your project. It is very easy to work with as an UIView and listen to its delegate for more options if you need them.
The library is called InteractiveImageView, it supports iOS 11.0 and up, link to GitHub: https://github.com/egzonpllana/InteractiveImageView
Upvotes: 3
Reputation: 12582
1- Don't forget to add the UIScrollViewDelegate
in your vc declaration!
///General purpose apple-Photos-like image viewer.
class Photo: UIViewController, UIScrollViewDelegate {
Put a scroll view in the storyboard. Put an image view inside the scroll view.
Add the absolutely standard SIX constraints from the image view to the scroll view, just as with any scroll view: top, bottom, left, right, equal width, equal heights
Don't forget to make the scroll view delegate, the vc!
In the vc add
func viewForZooming(in scrollView: UIScrollView) -> UIView? { return theImageView }
Set the scroll like this (obviously in SB or code as you prefer).
You're done.
If you're a smarty pants, set the aspect ratio constraint of the image view dynamically, via the arriving image. If you don't do this the scroll will "go to far" horizontally when you zoom around.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
guard let rez else { return print("woe?") }
im.kf.setImage(with: rez) { [weak self] result in
guard let self else { return }
switch result {
case .success(let v):
aspectRatio = v.image.size.width / v.image.size.height
im.heightAnchor
.constraint(equalTo: im.widthAnchor, multiplier: 1.0 / aspectRatio)
.isActive = true
}
}
}
Solving the "pinning" problem (if you want to / need to) is not trivial, but a trivial 95% solution is: https://stackoverflow.com/a/79016163/294884
Upvotes: 1
Reputation: 14835
Confirmed working with Xcode 15 | iOS 17 | Swift 5 | Storyboard
I found this YouTube video (https://www.youtube.com/watch?v=0Tz0vI721c8) to be a much better example of what to do. It helps specifically with setting up the correct constraints.
Upvotes: -1
Reputation: 412
Using Swift 5.0, here is how it works for me:
let myImageView = UIImageView(image: myImage)
myImageView.isUserInteractionEnabled = true
let pinchMethod = UIPinchGestureRecognizer(target: self, action: #selector(pinchImage(sender:)))
myImageView.addGestureRecognizer(pinchMethod)
@objc func pinchImage(sender: UIPinchGestureRecognizer) {
guard let sender = sender.view else { return }
if let scale = (sender.view?.transform.scaledBy(x: sender.scale, y: sender.scale)) {
guard scale.a > 1.0 else { return }
guard scale.d > 1.0 else { return }
sender.view?.transform = scale
sender.scale = 1.0
}
}
You can use scale.a, scale.b, scale.c, scale.d, scale.tx and scale.ty to set your scale limits.
Upvotes: 8
Reputation: 866
Supporting Swift 5.1, You can create an extension of UIImageView
, like this:
extension UIImageView {
func enableZoom() {
let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(startZooming(_:)))
isUserInteractionEnabled = true
addGestureRecognizer(pinchGesture)
}
@objc
private func startZooming(_ sender: UIPinchGestureRecognizer) {
let scaleResult = sender.view?.transform.scaledBy(x: sender.scale, y: sender.scale)
guard let scale = scaleResult, scale.a > 1, scale.d > 1 else { return }
sender.view?.transform = scale
sender.scale = 1
}
}
Upvotes: 52
Reputation: 69
I ended up here, probably searching the wrong way.
I was after having my imageView in contentMode = .centre. But I was judging it too zoomed in and I was searching a way to zoom it out. Here's how:
self.imageView.contentScaleFactor = 3
1 is as if you were doing anything. More that 1 zooms out... 3 works for me but you need to test it out.
Upvotes: 0
Reputation: 275
This is an old question but I don't see any answers that explain what is wrong with the original code.
This line:
let currentScale = self.view.frame.size.width / self.view.bounds.size.width
Is working on the main view rather than the imageView so the scale calculation is always ~1
This simple change makes it behave as expected
let currentScale = sender.view!.frame.size.width / sender.view!.bounds.size.width
by changing self to sender (and forcing view to unwrap) the scale calculation works as expected.
Upvotes: 1
Reputation: 955
UIImageView pinch zoom with UIScrollView || image zooming ios in swift 3 and Xcode 8 letter Youtube video URL
set uiscrollview Delegate in storyboard
class PhotoDetailViewController: UIViewController, UIScrollViewDelegate {
@IBOutlet weak var scrollView: UIScrollView!
@IBOutlet weak var imgPhoto: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
scrollView.minimumZoomScale = 1.0
scrollView.maximumZoomScale = 6.0
// scrollView.delegate = self - it is set on the storyboard.
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imgPhoto
}
Upvotes: 148
Reputation: 15748
Swift 3 solution
By default UIImageView's
userInteration
is disabled. Enable it before adding any gestures in UIImageView
.
imgView.isUserInteractionEnabled = true
The scale factor relative to the points of the two touches in screen coordinates
var lastScale:CGFloat!
func zoom(gesture:UIPinchGestureRecognizer) {
if(gesture.state == .began) {
// Reset the last scale, necessary if there are multiple objects with different scales
lastScale = gesture.scale
}
if (gesture.state == .began || gesture.state == .changed) {
let currentScale = gesture.view!.layer.value(forKeyPath:"transform.scale")! as! CGFloat
// Constants to adjust the max/min values of zoom
let kMaxScale:CGFloat = 2.0
let kMinScale:CGFloat = 1.0
var newScale = 1 - (lastScale - gesture.scale)
newScale = min(newScale, kMaxScale / currentScale)
newScale = max(newScale, kMinScale / currentScale)
let transform = (gesture.view?.transform)!.scaledBy(x: newScale, y: newScale);
gesture.view?.transform = transform
lastScale = gesture.scale // Store the previous scale factor for the next pinch gesture call
}
}
Upvotes: 6
Reputation: 1841
I decided to add the imageView to a UIScrollView. It allows the user to zoom and pan over. Here is the code I used.
in order to set max/min zoom I used :
scrollImg.minimumZoomScale = 1.0
scrollImg.maximumZoomScale = 10.0
here is the rest of the code.
var vWidth = self.view.frame.width
var vHeight = self.view.frame.height
var scrollImg: UIScrollView = UIScrollView()
scrollImg.delegate = self
scrollImg.frame = CGRectMake(0, 0, vWidth!, vHeight!)
scrollImg.backgroundColor = UIColor(red: 90, green: 90, blue: 90, alpha: 0.90)
scrollImg.alwaysBounceVertical = false
scrollImg.alwaysBounceHorizontal = false
scrollImg.showsVerticalScrollIndicator = true
scrollImg.flashScrollIndicators()
scrollImg.minimumZoomScale = 1.0
scrollImg.maximumZoomScale = 10.0
defaultView!.addSubview(scrollImg)
imageView!.layer.cornerRadius = 11.0
imageView!.clipsToBounds = false
scrollImg.addSubview(imageView!)
I also had to add this as well
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return self.imageView
}
Swift 3 & above function prototype
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return self.mainImage
}
Upvotes: 64
Reputation: 406
The option for swift 4
class ViewController: UIViewController, UIScrollViewDelegate {
@IBOutlet weak var scrolView: UIScrollView!
@IBOutlet weak var imgPhoto: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
scrolView.delegate = self
scrolView.minimumZoomScale = 1.0
scrolView.maximumZoomScale = 10.0
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imgPhoto
}
}
Upvotes: 17
Reputation: 127
Swift 3 solution
This is the code I used. I added imageView to scrollView as a subview.
class ZoomViewController: UIViewController,UIScrollViewDelegate {
@IBOutlet weak var scrollView:UIScrollView!
@IBOutlet weak var imageView:UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
scrollView.minimumZoomScale = 1.0
scrollView.maximumZoomScale = 10.0//maximum zoom scale you want
scrollView.zoomScale = 1.0
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageView
}
Upvotes: 6
Reputation: 828
I think the biggest problem is at the end of your func
, you have sender.scale = 1
. If you remove that line of code, your image shouldn't just bounce back each time.
Upvotes: 1
Reputation: 7760
You can use ImageScrollView open source, a zoomable and scrollable image view. http://github.com/huynguyencong/ImageScrollView
Like this opensource, add ImageView to ScrollView
open class ImageScrollView: UIScrollView {
var zoomView: UIImageView? = nil
}
extension ImageScrollView: UIScrollViewDelegate{
public func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return zoomView
}
public func scrollViewDidZoom(_ scrollView: UIScrollView) {
adjustFrameToCenter()
}
}
Upvotes: 15
Reputation: 99
In my view, the problem is your determination of currentScale. It always equals 1, because you change the scale of your imageView. You should assign your currentScale as follows:
let currentScale = self.imageView?.frame.size.width / self.imageView?.bounds.size.width
Upvotes: 6