Search code examples
swiftuiscrollviewuikitswift5

Zoom to location on scrollview on iPhone (portrait); not quite right


In my app the user can add images (a map) and then add pins (small images + attached note) to that image. The map is inside a scrollview. Adding the pins etc works fine, with one small exception: on Iphones in portrait mode (or more precise traitcollection == .compact) I can't get the pin to be and centered and a bit zoomed out. All of this is in a custom splitview controller; notes in the left viewcontroller, map (images + pin) in the right viewcontroller.

On iPad/iPhone in landscape this works just fine by calling:

  mapImageScrollView.zoom(to: mapImageLocationPin!.frame, animated: false)
  mapImageScrollView.setZoomScale(1.0, animated: true)

with the following result enter image description here

On the iPhone in portrait mode the user has to click the double arrow button to navigate to the right viewcontroller with the map. using the same to lines of code as above will result in the map being zoomed in, but NOT centered on the pin. The best I could get it is by using the following code (note that for this to work I have to set the Zoomscale before I zoom!)

    mapImageScrollView.setZoomScale(0.85, animated: false)
    mapImageScrollView.zoom(to: mapImageLocationPin!.frame, animated: false)

This results in centered, but NOT zoomed out

enter image description here I think it is due to in the second case the right viewcontroller hasn't appeared yet (will do so only after the user taps the double arrow button), but I can't seem to find a better solution ...

Any help appreciated!

The code I use to add the pin to the map:

private func addMapImageLocationPin() {
        if let location = annotation?.location {
            //Activate the corresponding Map Image and update picker
            selectedMapImage = location.map
            mapImagePickerView.selectRow(mapImagesController.getRowFor(location.map), inComponent: 0, animated: true)
            
            //Instantiate new Pin with annotation
            mapImageLocationPin = MapImageLocationPin(with: annotation!, andMapImageSize: mapImageView.image!.size)
            mapImageView.addSubview(mapImageLocationPin!)
            
            //Desired height and width
            let desiredWidth: CGFloat = 160
            let desiredHeight: CGFloat = 80
            
            //compensate for scrollView zoomscale, but never bigger than desired width & height!
            let minZoomScale = mapImageScrollView.minimumZoomScale
            let width = min(desiredWidth, desiredWidth / minZoomScale)
            let height = min(desiredHeight, desiredHeight / minZoomScale)
            
            //center the pin image horizontal on location
            let y = location.coordinateY
            let x = location.coordinateX - (width / 2)
            
            //position (frame) the pin
            mapImageLocationPin?.frame = CGRect(x: x, y: y, width: width, height: height)
            
            //zoom to the pin, different approach for smaller screen sizes
            if traitCollection.horizontalSizeClass == .compact {
                mapImageScrollView.setZoomScale(0.85, animated: false)
                mapImageScrollView.zoom(to: mapImageLocationPin!.frame, animated: false)
            } else {
                mapImageScrollView.zoom(to: mapImageLocationPin!.frame, animated: false)
                mapImageScrollView.setZoomScale(1.0, animated: true)
            }
        }
    }

Solution

  • At what point in the layout cycle is addMapImageLocationPin being called? If the child view isn't loaded immediately in some instances then you need to ensure that you call the positioning code after the main view has been laid out, such as in viewDidAppear:

       override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
            mapImageScrollView.zoom(to: mapImageLocationPin!.frame, animated: false)
        }