skip to Main Content

I have an UIImageView in an UIScrollView, and the image has size say 2000×3000. I can pinch to zoom and drag to pan on the screen. (I followed this video https://www.youtube.com/watch?v=0Tz0vI721c8).

My question is if I want to center the display at a point on the image programmatically (i.e. 600, 800), I think I need to use scrollView.setContentOffset(CGPoint), how do I figure out the CGPoint?

2

Answers


  1. // Assuming your image is the same width and height of 
    // the scrollview, the CGPoint for the center of the image will be
    
    let scrollViewCenterX: CGFloat = scrollView.bounds.width / 2
    let scrollViewCenterY: CGFloat = scrollView.bounds.height / 2
    let imageCenterPoint = CGPoint(x: scrollViewCenterX, y: scrollViewCenterY)
    scrollView.setContentOffset(imageCenterPoint)
    
    
    // if we want to center the scrollview on `point`
    let point = CGPoint(x: 600, y: 800)
    let newPoint = CGPoint(x: point.x / 2, y: point.y / 2)
    scrollView.setContentOffset(newPoint)
    
    Login or Signup to reply.
  2. You have to take a couple things into account, beyond just setting the .contentOffset to match the "target center point" …

    First, the goal…

    Using this 2000 x 3000 image:

    "

    With this set of "center points":

    let points: [CGPoint] = [
        CGPoint(x:  100, y:  100),
        CGPoint(x:  800, y:  600),
        CGPoint(x: 1600, y: 1200),
        CGPoint(x:  600, y: 2000),
        CGPoint(x: 1900, y: 2900),
    ]
    

    We want to programmatically set the centers to the center of the scroll view:

    enter image description here

    If all we did was set the .contentOffset to the "target point" for #3, we’d get this:

    enter image description here

    So, we need to subtract one-half of the width and height of the scroll view frame:

    scrollView.contentOffset.x = points[2].x - (scrollView.frame.width * 0.5)
    scrollView.contentOffset.y = points[2].y - (scrollView.frame.height * 0.5)
    

    That puts our "target point" in the center of the scroll view, but…

    If we’ve zoomed out to, say, 90% (scrollView.zoomScale = 0.9), we get this:

    enter image description here

    So, we need to translate the "target point" to the zoom scale:

    // translate target point to zoomScale
    var x: CGFloat = points[2].x * scrollView.zoomScale
    var y: CGFloat = points[2].y * scrollView.zoomScale
    
    x = x - (scrollView.frame.width * 0.5)
    y = y - (scrollView.frame.height * 0.5)
    
    scrollView.contentOffset.x = x
    scrollView.contentOffset.y = y
    

    and, woo hoo, we have this:

    enter image description here

    The next problem, though, is that we don’t want to center the "target point" if that would exceed the scroll view limits.

    For example, if we try to center on "1" or "5" we get:

    enter image description here

    and:

    enter image description here

    the Points are centered in the scroll view, but as soon as we touch it to scroll or zoom, it will snap to the corner.

    We need to limit the .contentOffset to avoid that.

    So, for point "1" (points[0]):

    // translate target point to zoomScale
    var x: CGFloat = points[0].x * scrollView.zoomScale
    var y: CGFloat = points[0].y * scrollView.zoomScale
        
    // don't want to set offset below Zero
    let minOffset: CGPoint = .zero
        
    // don't want to set offset greater than what will fit in the scroll view
    let maxOffset: CGPoint = CGPoint(x: imgView.frame.width - scrollView.frame.width, y: imgView.frame.height - scrollView.frame.height)
        
    // this prevents x and from being negative
    x = max(minOffset.x, x - (scrollView.frame.width * 0.5))
    y = max(minOffset.y, y - (scrollView.frame.height * 0.5))
    
    // this prevents x and y from exceeding the frame of the scroll view
    x = min(maxOffset.x, x)
    y = min(maxOffset.y, y)
    
    scrollView.contentOffset.x = x
    scrollView.contentOffset.y = y
    

    Here’s a complete example… as in the above images, tapping a "Number Button" will scroll that center point to the center of the scroll view (or as close as possible, if it would exceed the limits). All you need is to add that 2000×3000 image (named "img2000x3000") … no @IBOutlet or @IBAction connections needed:

    class ScrollPosViewController: UIViewController, UIScrollViewDelegate {
        
        let points: [CGPoint] = [
            CGPoint(x:  100, y:  100),
            CGPoint(x:  800, y:  600),
            CGPoint(x: 1600, y: 1200),
            CGPoint(x:  600, y: 2000),
            CGPoint(x: 1900, y: 2900),
        ]
    
        let scrollView: UIScrollView = {
            let v = UIScrollView()
            v.backgroundColor = .yellow
            v.translatesAutoresizingMaskIntoConstraints = false
            return v
        }()
        let imgView: UIImageView = {
            let v = UIImageView()
            v.translatesAutoresizingMaskIntoConstraints = false
            return v
        }()
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            view.backgroundColor = .systemGreen
            
            // make sure we can load the image
            guard let img = UIImage(named: "img2000x3000") else {
                print("Could not load image!!!")
                return
            }
            
            // assing image to image view
            imgView.image = img
        
            // create a buttons stack view
            let stack: UIStackView = {
                let v = UIStackView()
                v.distribution = .fillEqually
                v.spacing = 20
                v.translatesAutoresizingMaskIntoConstraints = false
                return v
            }()
            
            for i in 0..<points.count {
                let b = UIButton()
                b.setTitle("(i + 1)", for: [])
                b.setTitleColor(.white, for: .normal)
                b.setTitleColor(.lightGray, for: .highlighted)
                b.backgroundColor = .systemBlue
                b.layer.cornerRadius = 8
                b.addTarget(self, action: #selector(centerOn(_:)), for: .touchUpInside)
                stack.addArrangedSubview(b)
            }
            
            // add image view to scroll view
            scrollView.addSubview(imgView)
            
            // add scroll view to view
            view.addSubview(scrollView)
            
            // add buttons stack view to view
            view.addSubview(stack)
            
            let g = view.safeAreaLayoutGuide
            let c = scrollView.contentLayoutGuide
            
            NSLayoutConstraint.activate([
                
                // constrain background image view
                //  Leading / Trailing at 20-pts
                scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
                scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
                
                // height proportional to image size
                scrollView.heightAnchor.constraint(equalTo: scrollView.widthAnchor, multiplier: img.size.height / img.size.width),
                
                // centered vertically
                scrollView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
                
                // constrain image view to all 4 sides of scroll view's Content Layout Guide
                imgView.topAnchor.constraint(equalTo: c.topAnchor),
                imgView.leadingAnchor.constraint(equalTo: c.leadingAnchor),
                imgView.trailingAnchor.constraint(equalTo: c.trailingAnchor),
                imgView.bottomAnchor.constraint(equalTo: c.bottomAnchor),
    
                // constrain image view's width/height to image width/height
                imgView.widthAnchor.constraint(equalToConstant: img.size.width),
                imgView.heightAnchor.constraint(equalToConstant: img.size.height),
                
                // constrain buttons stack view at bottom
                stack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
                stack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
                stack.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
                
            ])
            
            scrollView.delegate = self
    
            scrollView.minimumZoomScale = 1.0
            scrollView.maximumZoomScale = 2.0
            
        }
        
        override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
            
            // update min zoom scale so we can only "zoom out" until
            //  the content view fits the scroll view frame
            if scrollView.minimumZoomScale == 1.0 {
                let xScale = scrollView.frame.width / imgView.frame.width
                let yScale = scrollView.frame.height / imgView.frame.height
                scrollView.minimumZoomScale = min(xScale, yScale)
            }
            
        }
        
        @objc func centerOn(_ sender: Any?) -> Void {
            guard let btn = sender as? UIButton,
                  let t = btn.currentTitle,
                  let n = Int(t),
                  n > 0,
                  n <= points.count
            else {
                return
            }
            
            // translate target point to zoomScale
            var x: CGFloat = points[n - 1].x * scrollView.zoomScale
            var y: CGFloat = points[n - 1].y * scrollView.zoomScale
            
            // don't want to set offset below Zero
            let minOffset: CGPoint = .zero
            
            // don't want to set offset greater than what will fit in the scroll view
            let maxOffset: CGPoint = CGPoint(x: imgView.frame.width - scrollView.frame.width, y: imgView.frame.height - scrollView.frame.height)
            
            // this prevents x and from being negative
            x = max(minOffset.x, x - (scrollView.frame.width * 0.5))
            y = max(minOffset.y, y - (scrollView.frame.height * 0.5))
    
            // this prevents x and y from exceeding the frame of the scroll view
            x = min(maxOffset.x, x)
            y = min(maxOffset.y, y)
    
            // if we want to animate the point to the new offset
            UIView.animate(withDuration: 0.5, delay: 0.0, options: [.curveEaseInOut], animations: {
                // set the new content offset
                self.scrollView.contentOffset = CGPoint(x: x, y: y)
            }, completion: nil)
    
            // or, without animation
            // set the new content offset
            //scrollView.contentOffset = CGPoint(x: x, y: y)
            
        }
    
        func viewForZooming(in scrollView: UIScrollView) -> UIView? {
            return imgView
        }
        
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search