Центрировать содержимое UIScrollView, когда меньше - PullRequest
135 голосов
/ 22 августа 2009

У меня есть UIImageView внутри UIScrollView, который я использую для масштабирования и прокрутки. Если изображение / содержимое представления прокрутки больше, чем представление прокрутки, все работает нормально. Однако когда изображение становится меньше, чем представление с прокруткой, оно прикрепляется к верхнему левому углу представления с прокруткой. Я бы хотел, чтобы он был в центре, как приложение «Фотографии».

Какие-нибудь идеи или примеры о том, как сохранить содержимое UIScrollView по центру, когда оно меньше?

Я работаю с iPhone 3.0.

Следующий код почти работает. Изображение вернется в верхний левый угол, если я ущипну его после достижения минимального уровня увеличения.

- (void)loadView {
    [super loadView];

    // set up main scroll view
    imageScrollView = [[UIScrollView alloc] initWithFrame:[[self view] bounds]];
    [imageScrollView setBackgroundColor:[UIColor blackColor]];
    [imageScrollView setDelegate:self];
    [imageScrollView setBouncesZoom:YES];
    [[self view] addSubview:imageScrollView];

    UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"WeCanDoIt.png"]];
    [imageView setTag:ZOOM_VIEW_TAG];
    [imageScrollView setContentSize:[imageView frame].size];
    [imageScrollView addSubview:imageView];

    CGSize imageSize = imageView.image.size;
    [imageView release];

    CGSize maxSize = imageScrollView.frame.size;
    CGFloat widthRatio = maxSize.width / imageSize.width;
    CGFloat heightRatio = maxSize.height / imageSize.height;
    CGFloat initialZoom = (widthRatio > heightRatio) ? heightRatio : widthRatio;

    [imageScrollView setMinimumZoomScale:initialZoom];
    [imageScrollView setZoomScale:1];

    float topInset = (maxSize.height - imageSize.height) / 2.0;
    float sideInset = (maxSize.width - imageSize.width) / 2.0;
    if (topInset < 0.0) topInset = 0.0;
    if (sideInset < 0.0) sideInset = 0.0;
    [imageScrollView setContentInset:UIEdgeInsetsMake(topInset, sideInset, -topInset, -sideInset)];
}

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
    return [imageScrollView viewWithTag:ZOOM_VIEW_TAG];
}

/************************************** NOTE **************************************/
/* The following delegate method works around a known bug in zoomToRect:animated: */
/* In the next release after 3.0 this workaround will no longer be necessary      */
/**********************************************************************************/
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {
    [scrollView setZoomScale:scale+0.01 animated:NO];
    [scrollView setZoomScale:scale animated:NO];
    // END Bug workaround

    CGSize maxSize = imageScrollView.frame.size;
    CGSize viewSize = view.frame.size;
    float topInset = (maxSize.height - viewSize.height) / 2.0;
    float sideInset = (maxSize.width - viewSize.width) / 2.0;
    if (topInset < 0.0) topInset = 0.0;
    if (sideInset < 0.0) sideInset = 0.0;
    [imageScrollView setContentInset:UIEdgeInsetsMake(topInset, sideInset, -topInset, -sideInset)];
}

Ответы [ 24 ]

227 голосов
/ 22 мая 2010

У меня очень простое решение! Все, что вам нужно, это обновить центр вашего подпредставления (изображения) при увеличении ScrollViewDelegate. Если увеличенное изображение меньше, чем прокрутка, настройте subview.center, иначе центр будет (0,0).

- (void)scrollViewDidZoom:(UIScrollView *)scrollView 
{
    UIView *subView = [scrollView.subviews objectAtIndex:0];

    CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5, 0.0);
    CGFloat offsetY = MAX((scrollView.bounds.size.height - scrollView.contentSize.height) * 0.5, 0.0);

    subView.center = CGPointMake(scrollView.contentSize.width * 0.5 + offsetX, 
                                 scrollView.contentSize.height * 0.5 + offsetY);
}
39 голосов
/ 23 марта 2016

@ EvelynCordner ответ был тем, который работал лучше всего в моем приложении. Гораздо меньше кода, чем другие опции.

Вот версия Swift, если кому-то это нужно:

func scrollViewDidZoom(_ scrollView: UIScrollView) {
    let offsetX = max((scrollView.bounds.width - scrollView.contentSize.width) * 0.5, 0)
    let offsetY = max((scrollView.bounds.height - scrollView.contentSize.height) * 0.5, 0)
    scrollView.contentInset = UIEdgeInsetsMake(offsetY, offsetX, 0, 0)
}
25 голосов
/ 14 апреля 2010

Хорошо, я боролся с этим последние два дня и, наконец, придя к довольно надежному (пока что ...) решению, я подумал, что должен поделиться им и избавить других от некоторой боли. :) Если вы нашли проблему с этим решением, пожалуйста, кричите!

Я в основном прошел через то, что у всех остальных: поиск StackOverflow, форумы разработчиков Apple, просмотр кода для Three20, ScrollingMadness, ScrollTestSuite и т. Д. Я попытался увеличить фрейм UIImageView, поиграв со смещением UIScrollView / или вставки из ViewController и т. д., но ничего не получилось (как все узнали).

После сна я попробовал несколько альтернативных углов:

  1. Создание подкласса UIImageView для динамического изменения его собственного размера - это не сработало совсем.
  2. Создание подкласса UIScrollView, чтобы он динамически изменял свой собственный contentOffset - это тот, который кажется мне победителем.

С помощью этого метода UIScrollView с подклассами я переопределяю мутатор contentOffset, поэтому он не устанавливает {0,0}, когда изображение масштабируется меньше, чем область просмотра, - вместо этого он устанавливает смещение таким образом, чтобы изображение сохранялось в центре окно просмотра. До сих пор, кажется, всегда работает. Я проверил это с широкими, высокими, крошечными и большими изображениями, и у меня нет проблемы "работает, но при минимальном увеличении ломает это".

Я загрузил пример проекта в github, который использует это решение, вы можете найти его здесь: http://github.com/nyoron/NYOBetterZoom

23 голосов
/ 13 августа 2010

Этот код должен работать на большинстве версий iOS (и был протестирован на работу с версией 3.1 и выше).

Он основан на коде Apple WWDC для фотоколлера.

Добавьте приведенное ниже в свой подкласс UIScrollView и замените tileContainerView видом, содержащим ваше изображение или плитки:

- (void)layoutSubviews {
    [super layoutSubviews];

    // center the image as it becomes smaller than the size of the screen
    CGSize boundsSize = self.bounds.size;
    CGRect frameToCenter = tileContainerView.frame;

    // center horizontally
    if (frameToCenter.size.width < boundsSize.width)
        frameToCenter.origin.x = (boundsSize.width - frameToCenter.size.width) / 2;
    else
        frameToCenter.origin.x = 0;

    // center vertically
    if (frameToCenter.size.height < boundsSize.height)
        frameToCenter.origin.y = (boundsSize.height - frameToCenter.size.height) / 2;
    else
        frameToCenter.origin.y = 0;

    tileContainerView.frame = frameToCenter;
}
20 голосов
/ 15 июля 2015

Для решения, более подходящего для представлений прокрутки, использующих автоматическую разметку, используйте вставки содержимого представления прокрутки, а не обновляйте кадры подпредставлений представления прокрутки.

- (void)scrollViewDidZoom:(UIScrollView *)scrollView
{
    CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5, 0.0);
    CGFloat offsetY = MAX((scrollView.bounds.size.height - scrollView.contentSize.height) * 0.5, 0.0);

    self.scrollView.contentInset = UIEdgeInsetsMake(offsetY, offsetX, 0.f, 0.f);
}
20 голосов
/ 21 марта 2014

В настоящее время я создаю подклассы UIScrollView и переопределяю setContentOffset:, чтобы настроить смещение на основе contentSize Работает как с масштабированием, так и с программным увеличением.

@implementation HPCenteringScrollView

- (void)setContentOffset:(CGPoint)contentOffset
{
    const CGSize contentSize = self.contentSize;
    const CGSize scrollViewSize = self.bounds.size;

    if (contentSize.width < scrollViewSize.width)
    {
        contentOffset.x = -(scrollViewSize.width - contentSize.width) / 2.0;
    }

    if (contentSize.height < scrollViewSize.height)
    {
        contentOffset.y = -(scrollViewSize.height - contentSize.height) / 2.0;
    }

    [super setContentOffset:contentOffset];
}

@end

В дополнение к тому, что этот код короткий и приятный, он обеспечивает гораздо более плавное масштабирование, чем решение @Erdemus. Вы можете увидеть это в действии в демоверсии RMGallery .

12 голосов
/ 25 января 2011

Я потратил день на борьбу с этой проблемой и в итоге реализовал scrollViewDidEndZooming: withView: atScale: следующим образом:

- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {
    CGFloat screenWidth = [[UIScreen mainScreen] bounds].size.width;
    CGFloat screenHeight = [[UIScreen mainScreen] bounds].size.height;
    CGFloat viewWidth = view.frame.size.width;
    CGFloat viewHeight = view.frame.size.height;

    CGFloat x = 0;
    CGFloat y = 0;

    if(viewWidth < screenWidth) {
        x = screenWidth / 2;
    }
    if(viewHeight < screenHeight) {
        y = screenHeight / 2 ;
    }

    self.scrollView.contentInset = UIEdgeInsetsMake(y, x, y, x);
}

Это гарантирует, что когда изображение меньше экрана, вокруг него остается достаточно места, чтобы вы могли расположить его в нужном месте.

(при условии, что ваш UIScrollView содержит UIImageView для хранения изображения)

По сути, это проверяет, меньше ли ширина / высота вашего изображения по сравнению с шириной / высотой экрана, и, если это так, создайте вставку, равную половине ширины / высоты экрана (вы, возможно, можете увеличить ее, если хотите изображение, выходящее за пределы экрана).

Обратите внимание, что, поскольку это метод UIScrollViewDelegate , не забудьте добавить его в объявление контроллера представления, чтобы избежать проблем со сборкой.

7 голосов
/ 20 июня 2010

Apple выпустила видео сессий WWDC 2010 для всех участников программы для разработчиков iphone. Одна из обсуждаемых тем - как они создали приложение для фотографий !!! Они шаг за шагом создают очень похожее приложение и сделали весь код бесплатным.

Он также не использует приватный API. Я не могу поместить здесь какой-либо код из-за соглашения о неразглашении, но здесь есть ссылка на пример загрузки кода. Вам, вероятно, понадобится войти, чтобы получить доступ.

http://connect.apple.com/cgi-bin/WebObjects/MemberSite.woa/wa/getSoftware?code=y&source=x&bundleID=20645

И вот ссылка на страницу iTunes WWDC:

http://insideapple.apple.com/redir/cbx-cgi.do?v=2&la=en&lc=&a=kGSol9sgPHP%2BtlWtLp%2BEP%2FnxnZarjWJglPBZRHd3oDbACudP51JNGS8KlsFgxZto9X%2BTsnqSbeUSWX0doe%2Fzv%2FN5XV55%2FomsyfRgFBysOnIVggO%2Fn2p%2BiweDK%2F%2FmsIXj

2 голосов
/ 16 марта 2010

Хорошо, это решение работает для меня. У меня есть подкласс UIScrollView со ссылкой на UIImageView, который он отображает. Всякий раз, когда UIScrollView увеличивается, свойство contentSize корректируется. Именно в установщике я соответствующим образом масштабирую UIImageView, а также регулирую его центральное положение.

-(void) setContentSize:(CGSize) size{
CGSize lSelfSize = self.frame.size;
CGPoint mid;
if(self.zoomScale >= self.minimumZoomScale){
    CGSize lImageSize = cachedImageView.initialSize;
    float newHeight = lImageSize.height * self.zoomScale;

    if (newHeight < lSelfSize.height ) {
        newHeight = lSelfSize.height;
    }
    size.height = newHeight;

    float newWidth = lImageSize.width * self.zoomScale;
    if (newWidth < lSelfSize.width ) {
        newWidth = lSelfSize.width;
    }
    size.width = newWidth;
    mid = CGPointMake(size.width/2, size.height/2);

}
else {
    mid = CGPointMake(lSelfSize.width/2, lSelfSize.height/2);
}

cachedImageView.center = mid;
[super  setContentSize:size];
[self printLocations];
NSLog(@"zoom %f setting size %f x %f",self.zoomScale,size.width,size.height);
}

Всегда устанавливаю изображение на UIScrollView, меняю его размеры. UIScrollView в просмотре прокрутки также является пользовательским классом, который я создал.

-(void) resetSize{
    if (!scrollView){//scroll view is view containing imageview
        return;
    }

    CGSize lSize = scrollView.frame.size;

    CGSize lSelfSize = self.image.size; 
    float lWidth = lSize.width/lSelfSize.width;
    float lHeight = lSize.height/lSelfSize.height;

    // choose minimum scale so image width fits screen
    float factor  = (lWidth<lHeight)?lWidth:lHeight;

    initialSize.height = lSelfSize.height  * factor;
    initialSize.width = lSelfSize.width  * factor;

    [scrollView setContentSize:lSize];
    [scrollView setContentOffset:CGPointZero];
    scrollView.userInteractionEnabled = YES;
}

С помощью этих двух методов я могу получить представление, которое ведет себя так же, как приложение для фотографий.

1 голос
/ 01 мая 2013

Пример Apple Photo Scroller делает именно то, что вы ищете. Поместите это в ваш подкласс UIScrollView и измените _zoomView на UIImageView.

-(void)layoutSubviews{
  [super layoutSubviews];
  // center the zoom view as it becomes smaller than the size of the screen
  CGSize boundsSize = self.bounds.size;
  CGRect frameToCenter = self.imageView.frame;
  // center horizontally
  if (frameToCenter.size.width < boundsSize.width){
     frameToCenter.origin.x = (boundsSize.width - frameToCenter.size.width) / 2;
  }else{
    frameToCenter.origin.x = 0;
  }
  // center vertically
  if (frameToCenter.size.height < boundsSize.height){
     frameToCenter.origin.y = (boundsSize.height - frameToCenter.size.height) / 2;
  }else{
    frameToCenter.origin.y = 0;
  }
  self.imageView.frame = frameToCenter; 
}

Образец кода Apple Photo Scroller

...