UIScrollView - Custom Map - Prevent marker subview

2019-08-18 23:01发布

问题:

I have a custom map of a limited area, and have it set up to correctly show the users' location. The map is a 1600px square image within a UIScrollView.

I have a crosshair image to show the current location of the user, which at zoomScale 1.0 is the desired size. When I pinch and zoom the scrollView, the crosshair scales with it. I would like to have the subview remain the same size on screen.

I haven't been able to find any information on this, what would be the best way to go about this?

If there is anything I can provide you with to help the answer, please let me know.

Many thanks!

EDIT -

Having looked in to this further, there is a UIScrollViewDelegate method - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale which I tried using to take the marker's current center and size, then adjust, but this only scales at the end of the zoom. I would prefer to have the marker remain the same size while the user is zooming.

EDIT 2-

Cake has provided a great answer below, but I haven't been able to implement this in the way I imagined it would be.

I have the UIImageView as a placeholder, with alpha set to 0. This placeholder moves around relative to the map to show the user location. This operates as I expect it to. Unfortunately, this resizes with the map, as it is a subview of the map (so it stays in place).

Taking Cake's below answer, I have created the non-scaling crosshair image, and added it as a sibling subview to the scrollview. The maths, once Cake had pointed them out, were quite simple to get the new frame for the crosshair:

    CGPoint ULPC = userLocationPlaceholder.center;
    float zs = scrollView.zoomScale;
    CGRect newFrame = CGRectMake(((ULPC.x * zs) - scrollView.contentOffset.x) - 20, ((ULPC.y * zs) - scrollView.contentOffset.y) - 20, 40, 40);

Where the image is 40points wide. This matches the centers perfectly.

The problem I now have is that I cannot get the crosshair image to stay locked to the placeholder.

I have tried using a self calling animation as such:

-(void)animeUserLocationAttachment
{
[UIView animateWithDuration:0.05 
                      delay:0 
                    options:(UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionCurveLinear )
                 animations:^{
                     userLocationDotContainer.frame = newFrame;
               } completion:^(BOOL finished){
                     // Call self
                     [self animateUserLocationAttachment];
                 }];
}

As soon as I start scrolling/zooming, this locks the animation so that the crosshair just sits in place until I release the scrolling/zooming, then it correctly updates it's location.

Is there any way I can get around this, or an alternative method I can apply?

Many thanks

EDIT 3 -

I've re-accepted Cake's answer as it covers 90% of the issue. Further to his answer I have implemented the ScrollViewDelegate methods scrollViewWillBeginDragging: andscrollViewWillBeginDecelerating: to scale the placeholder to match the current size of the crosshair relative to the map, show the placeholder (that is a subview of the map image) and hide the crosshair image. The delegate method scrollviewWillBeginZooming:withView: does not show the placeholder because it scales with the map. As Cake recommends, I'll make a new question for this issue.

The counterpart methods (scrollViewDidEndZooming:withView:atScale:, scrollViewDidEndDragging:willDecelerate: and -scrollViewDidEndDecelerating:`) all hide the placeholder, and re-show the crosshair.

回答1:

Create another crosshair image that's associated with the view or view controller that contains the scrollview. Then have this one always snap to the center of the crosshair image you already have. Then, hide your original crosshair image. Then you can avoid having the scrollview scale the disassociated crosshair, and it should stay the same size.

Relative coordinate systems

Each view in cocoa touch has a frame property that has an origin. In order to position an object owned by one view properly relative to another view, all you have to do is figure out the differences in their origins. If one view is a subview of another, then this isn't too difficult.

  • Get the origin of the container view
  • Get the location of the subview inside of the container view
  • Get the origin of the subview
  • Calculate the difference in the positions of the origins
  • Get the location of the object you want to overlap (relative to the subview)
  • Calculate the location of the object you want to overlap relative to the container view
  • Move your crosshair to this position


回答2:

The question is old but for the future similar questions I've recently resolved a similar problem applying the hint of Andrew Madsen of another post.

I'had a UIScrollView, with an UIImageView in it. Attached to the UIImageView I had many MKAnnotationView (those are my subviews that I didn't want scaling with the superview).

I did subclass UIImageView and implement setTransform: method like here:

#import "SLImageView.h"

@implementation SLImageView


    - (void)setTransform:(CGAffineTransform)transform
    {
        [super setTransform:transform];

        CGAffineTransform invertedTransform = CGAffineTransformInvert(transform);

        for (id obj in self.subviews)
        {
            if ([obj isKindOfClass:[MKAnnotationView class]])
            {
                [((UIView *)obj) setTransform:invertedTransform]; 
            }
        }
    }

    @end

This works perfectly! Mick.



回答3:

Swift equivalent for Mick's answer:

class MapContainerView:UIView {
    @IBOutlet var nonScalingViews: [UIView]!

    override var transform: CGAffineTransform {
        didSet {
            guard let nonScalingViews = nonScalingViews else {
                return
            }
            let invertedTransform = CGAffineTransformInvert(transform)
            for view in nonScalingViews {
                view.transform = invertedTransform
            }
        }
    }
}