Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Zoom unscaled views in UIScrollView to bounds

I need to zoom red views inside the grey view up to given red bounds (in this case scroll view borders depending on given width/height ratio) inside scroll view while keeping views visible sizes unchanged. They also should always touch green border.

I try to achieve this using scale transforms on this views so that while zooming scroll view up I scale this views down using formula 1 / zoomScale and changing their anchor points so that they stay at green border.

The issue is I don't get how to calculate target bounding rect for red views after all these manipulations so I could scroll to it using appropriate zoomScale.

Please see full demo project here

EDIT

Grey view may zoom out of scroll view borders (see last picture), the main thing is to fit green rectangle surrounded by red views (you may think of them as negative insets for green area) inside scroll view bounds, so we should actually calculate taking into account initial and end size of green rectangle, to which red views should be "pinned".

Essential methods

- (void)adjustScrollPositionAndZoomToFrame:(CGRect)frame
{
    CGFloat viewWidth = frame.size.width;
    CGFloat viewHeight = frame.size.height;

    CGFloat scrollViewWidth = self.scrollView.frame.size.width;
    CGFloat scrollViewHeight = self.scrollView.frame.size.height;

    CGSize newSize = [self scaleSize:frame.size toHeight:scrollViewHeight];

    if (newSize.width > scrollViewWidth) {
        newSize = [self scaleSize:frame.size toWidth:scrollViewWidth];
    }

    CGFloat scaleFactor = newSize.height == scrollViewHeight
        ? scrollViewHeight / viewHeight
        : scrollViewWidth / viewWidth;

    [self scrollRect:frame toCenterInScrollView:self.scrollView animated:NO];

    self.scrollView.zoomScale = scaleFactor;
}

Scaling

- (void)handleZoom:(CGFloat)zoom
{
    NSArray *anchorPoints = @[[NSValue valueWithCGPoint:CGPointMake(1.0, 1.0)],
                              [NSValue valueWithCGPoint:CGPointMake(0.5, 1.0)],
                              [NSValue valueWithCGPoint:CGPointMake(0.0, 1.0)],

                              [NSValue valueWithCGPoint:CGPointMake(1.0, 0.5)],
                              [NSValue valueWithCGPoint:CGPointMake(0.5, 0.5)],
                              [NSValue valueWithCGPoint:CGPointMake(0.0, 0.5)],

                              [NSValue valueWithCGPoint:CGPointMake(1.0, 0.0)],
                              [NSValue valueWithCGPoint:CGPointMake(0.5, 0.0)],
                              [NSValue valueWithCGPoint:CGPointMake(0.0, 0.0)]
                              ];

    for (UILabel *label in _labels) {
        [self setViewAnchorPoint:label value:[anchorPoints[[_labels indexOfObject:label]] CGPointValue]];
        label.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1 / zoom, 1 / zoom);
    }
}

/**
 * @see https://stackoverflow.com/a/9649399/3004003
 * @param value See view.layer.anchorPoint
 */
- (void)setViewAnchorPoint:(UIView *)view value:(CGPoint)value
{
    CGPoint newPoint = CGPointMake(view.bounds.size.width * value.x,
                                   view.bounds.size.height * value.y);
    CGPoint oldPoint = CGPointMake(view.bounds.size.width * view.layer.anchorPoint.x,
                                   view.bounds.size.height * view.layer.anchorPoint.y);

    newPoint = CGPointApplyAffineTransform(newPoint, view.transform);
    oldPoint = CGPointApplyAffineTransform(oldPoint, view.transform);

    CGPoint position = view.layer.position;

    position.x -= oldPoint.x;
    position.x += newPoint.x;

    position.y -= oldPoint.y;
    position.y += newPoint.y;

    view.layer.position = position;
    view.layer.anchorPoint = value;
}

Initial scene before zoom:

enter image description here

What I have after zoom:

enter image description here

What I need after zoom:

enter image description here

like image 796
schmidt9 Avatar asked May 15 '19 08:05

schmidt9


1 Answers

I downloaded your project and found the following issues:

When you initialize zoomingView, you set the origin of its frame so that zoomingView is in the middle of the scrollView. When you then scale the scrollView, it does also scale the offsets to the origin, which had to be taken into account for the correct placement of the scaled zoomingView.

I think it is clearer if you set the origin to 0:

self.zoomingView = [[ZoomingView alloc] initWithFrame:CGRectMake(0, 0, kZoomingViewWidth, kZoomingViewHeight)];

After scaling the zoomingView you had to center it. This can be done e.g. by

self.scrollView.zoomScale = scaleFactor;
    CGRect svb = self.scrollView.bounds;
    CGRect zvf = self.zoomingView.frame;
    self.scrollView.bounds = CGRectMake((zvf.size.width - svb.size.width)/2.0, (zvf.size.height - svb.size.height)/2.0, svb.size.width, svb.size.height);

Then the following function becomes very simple:

- (void)scrollViewDidZoom:(UIScrollView *)scrollView
{
   [self.zoomingView handleZoom:scrollView.zoomScale];
}

With these modifications, the scaled zoomingView is centered correctly.

However, the scaling factor is still wrong since the inner green rect is scaled to fill the available space. The reason is that one has to consider that 2 boxes of size 30 have to be visible additionally, i.e. the available space for scaling is smaller. This can be done e.g. by

CGFloat scaleFactor = newSize.height == scrollViewHeight
    ? (scrollViewHeight - 60.0) / viewHeight
    : (scrollViewWidth - 60.0) / viewWidth;

Of course, the constants had to be replaced by variables later.

With these modifications, your code runs correctly for me.

One more thing: This excellent post discusses in detail scroll views.

like image 51
Reinhard Männer Avatar answered Oct 01 '22 05:10

Reinhard Männer