【问题标题】:How do I position my UIScrollView's image properly when switching orientation?切换方向时如何正确定位我的 ScrollView 图像?
【发布时间】:2014-04-30 05:28:59
【问题描述】:

我在弄清楚如何最好地重新定位我的 UIScrollView 的图像视图时遇到了很多麻烦(我现在有一个图库类型的应用程序,类似于 Photos.app,特别是当您查看单个图像)当方向从纵向切换到横向时,反之亦然。

我知道我最好的办法是操纵 contentOffset 属性,但我不确定应该将其更改为什么。

我玩过很多次,无论出于何种原因,128 似乎都非常好用。在我的视图控制器的 viewWillLayoutSubviews 方法中,我有:

    if (UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) {
        CGPoint newContentOffset = self.scrollView.contentOffset;

        if (newContentOffset.x >= 128) {
            newContentOffset.x -= 128.0;
        }
        else {
            newContentOffset.x = 0.0;
        }

        newContentOffset.y += 128.0;

        self.scrollView.contentOffset = newContentOffset;
    }
    else {
        CGPoint newContentOffset = self.scrollView.contentOffset;

        if (newContentOffset.y >= 128) {
            newContentOffset.y -= 128.0;
        }
        else {
            newContentOffset.y = 0.0;
        }

        newContentOffset.x += 128.0;

        self.scrollView.contentOffset = newContentOffset;
    }

而且效果很好非常好。我讨厌它使用幻数的方式,而且我不知道它是从哪里来的。

此外,每当我缩放图像时,我都会将其设置为居中(就像 Photos.app 一样):

- (void)centerScrollViewContent {
    // Keep image view centered as user zooms
    CGRect newImageViewFrame = self.imageView.frame;

    // Center horizontally
    if (newImageViewFrame.size.width < CGRectGetWidth(self.scrollView.bounds)) {
        newImageViewFrame.origin.x = (CGRectGetWidth(self.scrollView.bounds) - CGRectGetWidth(self.imageView.frame)) / 2;
    }
    else {
        newImageViewFrame.origin.x = 0;
    }

    // Center vertically
    if (newImageViewFrame.size.height < CGRectGetHeight(self.scrollView.bounds)) {
        newImageViewFrame.origin.y = (CGRectGetHeight(self.scrollView.bounds) - CGRectGetHeight(self.imageView.frame)) / 2;
    }
    else {
        newImageViewFrame.origin.y = 0;
    }

    self.imageView.frame = newImageViewFrame;
}

所以我需要它来保持正确定位,以便在重新定位时不会在图像周围显示黑色边框。 (这就是第一个代码块中的检查的用途。)

基本上,我很好奇如何实现像 Photos.app 中的功能,在旋转时滚动视图智能地重新定位内容,以便旋转前可见内容的中间是相同的旋转后,所以感觉是连续的.

【问题讨论】:

  • 我期待看到答案...我已经花费了太多时间试图找出这个确切的问题。
  • 您是要显示一组图像(类似缩略图)还是单个图像?您一直在引用 Photos.app,但不清楚您是否指的是图像检查的集合。如果它是您试图展示的图像集合,那么您肯定想要使用 UICollecitonView。如果是图像视图检查,您可能想尝试让缩放始终与设备的较长侧同步拉伸(纵向高度)。
  • 抱歉,这确实是针对单个图像,所以为了保持 Photos.app 的比喻,当您点击图库中的图像时,屏幕就会出现。

标签: ios objective-c cocoa-touch uiscrollview uiimageview


【解决方案1】:

您应该更改UIScrollViewcontentOffset 属性,只要滚动视图在其bounds 值更改后布局其子视图。那么当界面方向发生变化时,UIScrollViewbounds也会随之变化,更新contentOffset

要使事情“正确”,您应该继承UIScrollView 并在那里进行所有调整。这也将允许您轻松地重用您的“特殊”滚动视图。

contentOffset 计算函数应该放在UIScrollViewlayoutSubviews 方法中。问题是这个方法不仅在bounds值改变时被调用,而且在srollView被缩放或滚动时被调用。因此,应跟踪 bounds 值以提示是否调用 layoutSubviews 方法是由于 bounds 因方向变化或平移或捏合手势而发生变化。

所以UIScrollView 子类的第一部分应该是这样的:

- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        // Set the prevBoundsSize to the initial bounds, so the first time
        // layoutSubviews is called we won't do any contentOffset adjustments
        self.prevBoundsSize = self.bounds.size;
    }
    return self;
}


- (void)layoutSubviews {
    [super layoutSubviews];

    if (!CGSizeEqualToSize(self.prevBoundsSize, self.bounds.size)) {
        [self _adjustContentOffset];
        self.prevBoundsSize = self.bounds.size;
    }

    [self _centerScrollViewContent];
}

这里,layoutSubviews 每次平移、缩放或更改其 bounds 时都会调用 layoutSubviews 方法。 _centerScrollViewContent 方法负责在缩放视图的大小变得小于滚动视图边界的大小时将其居中。并且,每次用户平移或缩放滚动视图或旋转设备时都会调用它。它的实现与您在问题中提供的实现非常相似。不同之处在于此方法是在UIScrollView 类的上下文中编写的,因此不是使用self.imageView 属性来引用缩放视图,这在UIScrollView 类的上下文中可能不可用,viewForZoomingInScrollView: 委托使用方法。

- (void)_centerScrollViewContent {
    if ([self.delegate respondsToSelector:@selector(viewForZoomingInScrollView:)]) {
        UIView *zoomView = [self.delegate viewForZoomingInScrollView:self];

        CGRect frame = zoomView.frame;
        if (self.contentSize.width < self.bounds.size.width) {
            frame.origin.x = roundf((self.bounds.size.width - self.contentSize.width) / 2);
        } else {
            frame.origin.x = 0;
        }
        if (self.contentSize.height < self.bounds.size.height) {
            frame.origin.y = roundf((self.bounds.size.height - self.contentSize.height) / 2);
        } else {
            frame.origin.y = 0;
        }
        zoomView.frame = frame;
    }
}

但这里更重要的是_adjustContentOffset 方法。这个方法负责调整contentOffset。这样,当UIScrollViewbounds 值更改时,更改前的中心点将保持在中心。并且由于条件语句,只有在UIScrollViewbounds发生变化时才会调用它(例如:方向变化)。

- (void)_adjustContentOffset {
    if ([self.delegate respondsToSelector:@selector(viewForZoomingInScrollView:)]) {
        UIView *zoomView = [self.delegate viewForZoomingInScrollView:self];

        // Using contentOffset and bounds values before the bounds were changed (e.g.: interface orientation change),
        // find the visible center point in the unscaled coordinate space of the zooming view.
        CGPoint prevCenterPoint = (CGPoint){
            .x = (self.prevContentOffset.x + roundf(self.prevBoundsSize.width / 2) - zoomView.frame.origin.x) / self.zoomScale,
            .y = (self.prevContentOffset.y + roundf(self.prevBoundsSize.height / 2) - zoomView.frame.origin.y) / self.zoomScale,
        };

        // Here you can change zoomScale if required
        // [self _changeZoomScaleIfNeeded];

        // Calculate new contentOffset using the previously calculated center point and the new contentOffset and bounds values.
        CGPoint contentOffset = CGPointMake(0.0, 0.0);
        CGRect frame = zoomView.frame;
        if (self.contentSize.width > self.bounds.size.width) {
            frame.origin.x = 0;
            contentOffset.x = prevCenterPoint.x * self.zoomScale - roundf(self.bounds.size.width / 2);
            if (contentOffset.x < 0) {
                contentOffset.x = 0;
            } else if (contentOffset.x > self.contentSize.width - self.bounds.size.width) {
                contentOffset.x = self.contentSize.width - self.bounds.size.width;
            }
        }
        if (self.contentSize.height > self.bounds.size.height) {
            frame.origin.y = 0;
            contentOffset.y = prevCenterPoint.y * self.zoomScale - roundf(self.bounds.size.height / 2);
            if (contentOffset.y < 0) {
                contentOffset.y = 0;
            } else if (contentOffset.y > self.contentSize.height - self.bounds.size.height) {
                contentOffset.y = self.contentSize.height - self.bounds.size.height;
            }
        }
        zoomView.frame = frame;
        self.contentOffset = contentOffset;
    }
}

奖金

我创建了一个工作 SMScrollView 类(这里是 link to GitHub)实现上述行为和额外的奖励:

  • 您会注意到,在照片应用程序中,缩放照片,然后将其滚动到其边界之一,然后旋转设备不会将中心点保持在原位。相反,它将滚动视图粘贴到该边界。如果滚动到其中一个角然后旋转,scrollView 也会粘在那个角上。
  • 除了调整contentOffset,你可能会发现你还想调整scrollView的zoomScale。例如,假设您正在以纵向模式查看照片,该照片已缩放以适合屏幕尺寸。然后,当您将设备旋转到横向模式时,您可能需要放大照片以利用可用空间。

【讨论】:

  • 是否有可能在没有子类化的情况下做到这一点?
  • 是的。您只需要覆盖包含您的滚动视图的UIViewControllerviewWillLayoutSubviews 方法,并将我的示例中的layoutSubviews 方法的代码移到那里。当然,您需要将所有self 替换为self.scrollView。而不是使用[self.delegate viewForZoomingInScrollView:self] 获取zoomView,您可以使用从该委托方法返回的相同视图,在您的示例中为self.imageView
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多