将 UIScrollView 中未缩放的视图缩放到边界

Zoom unscaled views in UIScrollView to bounds

我需要在滚动视图中将灰色视图内的红色视图缩放到给定的红色边界(在本例中,滚动视图的边界取决于给定的 width/height 比率),同时保持视图可见大小不变。他们还应该始终触摸绿色边框。

我尝试在这个视图上使用缩放变换来实现这一点,以便在放大滚动视图时我使用公式 1 / zoomScale 缩小这个视图并更改它们的锚点,使它们保持在绿色边框。

问题是我不知道如何在所有这些操作之后计算红色视图的目标边界矩形,所以我可以使用适当的 zoomScale.

滚动到它

Please see full demo project here

编辑

灰色视图可能会缩小滚动视图边界(见最后一张图片),主要是为了适应红色视图包围的绿色矩形(你可能认为它们是负面的insets for green area) inside scroll view bounds, 所以我们实际上应该计算绿色矩形的初始和结束大小,红色视图应该是 "pinned".

基本方法

- (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;
}

缩放

- (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 
 * @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;
}

缩放前的初始场景:

缩放后的内容:

缩放后我需要什么:

如果将 anchorPoints 更改为值会怎样:

NSArray *anchorPoints = @[[NSValue valueWithCGPoint:CGPointMake(0.0, 0.0)],
                          [NSValue valueWithCGPoint:CGPointMake(0.5, 0.0)],
                          [NSValue valueWithCGPoint:CGPointMake(1.0, 0.0)],

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

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

因此,您将调整视图以适应缩放视图的边缘并获得您想要的结果:

我下载了您的项目并发现了以下问题:

当您初始化 zoomingView 时,您设置其框架的原点,以便 zoomingView 位于 scrollView 的中间。当您随后缩放 scrollView 时,它也会缩放到原点的偏移量,必须考虑到缩放后 zoomingView 的正确位置。

我觉得原点设为0会更清楚:

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

缩放 zoomingView 后,您必须将其居中。这可以做到,例如通过

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);

那么下面的函数就变得很简单了:

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

通过这些修改,缩放后的 zoomingView 已正确居中。

然而,缩放因子仍然是错误的,因为内部绿色矩形被缩放以填充可用 space。原因是必须考虑到 2 个大小为 30 的框必须额外可见,即可用于缩放的 space 更小。这可以做到,例如通过

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

当然,后面的常量要换成变量了

通过这些修改,您的代码对我来说可以正确运行。

还有一件事:This excellent post 详细讨论了滚动视图。