Центрировать содержимое UIScrollView, когда меньше

У меня 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)];
}

person hpique    schedule 22.08.2009    source источник
comment
Вы когда-нибудь решали эту проблему полностью? Я борюсь с той же проблемой.   -  person Jonah    schedule 20.09.2009
comment
Внимание: используйте значение initialZoom для вычисления вставки, если это НЕ один (без масштабирования). Например. используйте эти строки: float topInset = (maxSize.height - imageSize.height * initialZoom) / 2.0; float sideInset = (maxSize.width - imageSize.width * initialZoom) / 2.0; и, наконец, установите начальное значение масштабирования [imageScrollView setZoomScale: initialZoom];   -  person Felix    schedule 17.09.2010


Ответы (26)


У меня очень простое решение! Все, что вам нужно, это обновить центр вашего подпредставления (изображения) при масштабировании ScrollViewDelegate. Если увеличенное изображение меньше, чем scrollview, отрегулируйте subview. Center else 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);
}
person Erdemus    schedule 22.05.2010
comment
Очень просто и удобно! Спасибо! - person Krumelur; 08.03.2011
comment
Это работает несколько лучше, чем регулировка contentInset, по крайней мере, для тех применений, которые я пробовал. Параметр настройки contentInset довольно резкий, но очень плавный. - person mxcl; 19.03.2011
comment
Для меня это решение более отрывистое, чем использование NYOBetterZoom Лиама. Может быть, это зависит от размера изображения и т. Д. Мораль; используйте решение, которое лучше всего соответствует вашим потребностям - person wuf810; 15.04.2011
comment
Хотел бы я дать этому ответу более одного голоса. ПРОСТО И ЭФФЕКТИВНО. - person Kenny Winker; 21.08.2011
comment
Чувак, ты молодец !!! Я уже вычислил соотношение между моим scrollView и моим imageView, и все работало, но это ... потрясающе! избавило меня от кодирования дополнительных вычислений! - person geekinit; 29.11.2011
comment
Из всех решений, которые я пробовал для этой проблемы, это единственное, которое я могу заставить работать должным образом, и его ооочень проще реализовать, чем некоторые другие, которые я видел. Большое спасибо за эту экономию времени! - person ozz; 03.01.2012
comment
Stackoverflow GOLD STAR для этого. Я боролся с этим, но решение очень простое. - person n13; 01.03.2013
comment
чтобы немного упростить: CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5, 0.0); - person Marek R; 14.01.2014
comment
Эта настройка выручила меня, когда в scrollview были вставки: CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentInset.left - scrollView.contentInset.right - scrollView.contentSize.width) * 0.5, 0.0); CGFloat offsetY = MAX((scrollView.bounds.size.height - scrollView.contentInset.top - scrollView.contentInset.bottom - scrollView.contentSize.height) * 0.5, 0.0); - person Kieran Harper; 21.06.2014
comment
Я заметил, что с этим, как и со многими другими методами центрирования, возникают проблемы при использовании zoomToRect:. Использование подхода contentInset работает лучше, если вам понадобится такая функциональность. См. petersteinberger.com/blog/2013/how-to-center-uiscrollview для более подробной информации. - person Matej Bukovinski; 26.01.2015
comment
Это на самом деле также помогает удерживать изображение в нужном месте при увеличении, что было более серьезной проблемой в моем конкретном случае. - person Julian F. Weinert; 30.06.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)
}
person William T.    schedule 23.03.2016
comment
Этот отлично работает! Изображение даже после сжатия правильно анимировалось. - person Carter Medlin; 08.07.2016
comment
Я поместил этот код в func, а также вызвал его в viewDidLayoutSubviews, чтобы убедиться, что он был правильно настроен изначально. - person Carter Medlin; 08.07.2016
comment
Хороший звонок @CarterMedlin очень помог мне с начальной загрузкой Swift 3. - person A.J. Hernandez; 25.10.2016
comment
Кажется, это работает, но я не могу понять почему, поскольку он всегда, кажется, вычисляет одни и те же вставки: границы представления прокрутки фиксированы, как и размер содержимого. - person Kartick Vaddadi; 09.03.2017
comment
contentSize изменяется при масштабировании scrollView size фиксируется только - person Michał Ziobro; 27.06.2018

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

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

Выспавшись на нем, я попробовал несколько альтернативных ракурсов:

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

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

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

person Liam Jones    schedule 14.04.2010
comment
Для тех, кто заинтересован - я обновил связанный выше проект, поэтому он немного меньше зависит от того, что ViewController делает «правильные вещи», пользовательский UIScrollView сам позаботится о большей части деталей. - person Liam Jones; 24.04.2010
comment
Лиам, ты молодец. Я говорю это как автор ScrollingMadness. Кстати, на iPad в 3.2+ настройка contentInset в scrollViewDidZoom (новый метод делегата версии 3.2+) Just Works. - person Andrey Tarantsov; 05.05.2010
comment
Очень аккуратный фрагмент кода. Я уже давно ломаю голову над этим. Добавлен проект на handyiphonecode.com - person samvermette; 04.06.2010
comment
Отлично. Спасибо. Это не компенсировало того, что мой UIStatusBar был скрыт, поэтому я изменил строку anOffset.y = -(scrollViewSize.height - zoomViewSize.height) / 2.0 на anOffset.y = (-(scrollViewSize.height - zoomViewSize.height) / 2.0) + 10; - person Gordon Fontenot; 25.06.2010
comment
На мой взгляд, решение, данное bi Erdemus, намного проще. - person gyozo kudor; 02.09.2010
comment
@LiamJones, у вас нет лицензии в репозитории GitHub? Таким образом, все права защищены. Вы бы открыли его в Массачусетском технологическом институте или в чем-то подобном? - person Julian F. Weinert; 30.06.2016
comment
очевидно, @LiamJones больше не на SO, только с этим ответом. Тем не менее, я говорю, что вы проделали и поделились отличной работой с этим scrollView, который по-прежнему помогает людям через 7 лет и работает безупречно. Аплодисменты. - person user1244109; 29.01.2018

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

- (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);
}
person Evelyn Cordner    schedule 15.07.2015
comment
Это сработало для меня, однако, если изображение меньше для начала, этот код (при прямом вызове) не обновлял scrollview. Мне нужно было сначала поместить UIView, который находится внутри UIScrollview, в тот же центр (view.center = scrollview.center;), а затем в scrollViewDidZoom снова установить view.frame.origin x и y на 0. - person morksinaanab; 14.04.2016
comment
У меня тоже хорошо работает, но я сделал dispatch_async в главную очередь в viewWillAppear, где я вызываю scrollViewDidZoom: в основном режиме прокрутки. Это позволяет отображать вид с изображениями по центру. - person Pascal; 16.05.2016
comment
Выполните вызов этого кода в viewDidLayoutSubviews, чтобы убедиться, что он правильно настроен, когда представление отображается в первый раз. - person Carter Medlin; 08.07.2016

Этот код должен работать в большинстве версий 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;
}
person JosephH    schedule 13.08.2010
comment
это должен быть принятый ответ, потому что он проще. Спасибо. Вы спасли мою жизнь!!!!!! : D - person Duck; 24.11.2010
comment
После удаления всех вызовов API iOS 3.2+ логика центрирования работает только на устройствах с iOS 3.2+, а не 3.1.3 (скачкообразно, мерцает, получает случайное смещение). Я сравнил выходные данные о происхождении и размере кадра между версиями 3.1.3 и 3.2+, и, хотя они совпадают, по какой-то причине дочернее представление все равно позиционируется неправильно. Очень странно. Только ответ Лиама Джонса сработал для меня. - person David H; 18.03.2011
comment
Оба решения @Erdemus и @JosephH работают, но подход подкласса UIScrollView кажется предпочтительным: он вызывается при первом отображении представления + непрерывно во время масштабирования (тогда как scrollViewDidZoom вызывается только один раз за прокрутку и после факта) - person SwiftArchitect; 14.03.2012

В настоящее время я создаю подкласс 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.

person hpique    schedule 21.03.2014
comment
Вам не нужно создавать подкласс UIScrollView для реализации этого метода. Apple позволяет вам видеть события прокрутки и масштабирования в методах делегата. В частности: - (void) scrollViewDidZoom: (UIScrollView *) scrollView; - person Rog; 04.12.2014
comment
Лучшее решение, которое я когда-либо видел (работает даже при использовании ограничений). - person Frizlab; 27.04.2015

Я потратил день на борьбу с этой проблемой и в итоге реализовал 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, не забудьте добавить его в объявление вашего контроллера представления, чтобы избежать проблем со сборкой.

person Vicarius    schedule 25.01.2011

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»отн=

person Jonah    schedule 20.06.2010
comment
Рассматриваемый пример - MyImagePicker, и он, как ни странно, демонстрирует ту же проблему. - person Colin Barrett; 23.08.2010
comment
Я должен был быть более ясным. Рассматриваемый пример на самом деле PhotoScroller, а не MyImagePicker. Вы правы, что MyImagePicker работает некорректно. Но PhotoScroller умеет. Попытайся. - person Jonah; 24.08.2010
comment
Вы помните, может быть, название видео WWDC, где обсуждают Photoscroller? - person Grzegorz Adam Hankiewicz; 27.08.2011
comment
Это называется «Разработка приложений с помощью прокручиваемых представлений». - person Jonah; 12.09.2011

Если contentInset не нужен ни для чего другого, его можно использовать для центрирования содержимого scrollview.

class ContentCenteringScrollView: UIScrollView {

    override var bounds: CGRect {
        didSet { updateContentInset() }
    }

    override var contentSize: CGSize {
        didSet { updateContentInset() }
    }

    private func updateContentInset() {
        var top = CGFloat(0)
        var left = CGFloat(0)
        if contentSize.width < bounds.width {
            left = (bounds.width - contentSize.width) / 2
        }
        if contentSize.height < bounds.height {
            top = (bounds.height - contentSize.height) / 2
        }
        contentInset = UIEdgeInsets(top: top, left: left, bottom: top, right: left)
    }
}

Преимущество этого подхода в том, что вы все равно можете использовать contentLayoutGuide для размещения содержимого внутри scrollview.

scrollView.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
    imageView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor),
    imageView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor),
    imageView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
    imageView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor)
])

или просто перетащите содержимое в Интерфейсный Разработчик Xcode.

person Wojciech Nagrodzki    schedule 14.04.2020

Хорошо, это решение работает для меня. У меня есть подкласс 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;
}

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

person AHA    schedule 16.03.2010
comment
Кажется, это помогает, и это довольно простое решение. Некоторые переходы масштабирования не идеальны, но я считаю, что это можно исправить. Я еще поэкспериментирую. - person hpique; 07.04.2010
comment
Решение было ясным до обновления. Теперь это немного сбивает с толку. Вы можете уточнить? - person hpique; 28.04.2010

Я сделал это так, чтобы добавить дополнительное представление в иерархию:

UIScrollView -> UIView -> UIImageView

Задайте для вашего UIView такое же соотношение сторон, что и для UIScrollView, и сконцентрируйте UIImageView на этом.

person hatfinch    schedule 24.08.2009
comment
Спасибо, шляпка. Я тоже попробую это. Можете ли вы опубликовать образец кода или изменить мой образец кода, чтобы показать, как вы строите иерархию представлений? - person hpique; 24.08.2009
comment
Такого рода работы. За исключением того факта, что, поскольку UIView является размером UIScrollView, если изображение меньше (т.е. альбомная, а не портретная), вы можете прокрутить часть изображения за пределы экрана. Приложение для фотографий не позволяет этого и выглядит намного лучше. - person Jonah; 19.09.2009

Просто одобренный ответ быстро, но без подкласса с использованием делегата

func centerScrollViewContents(scrollView: UIScrollView) {
    let contentSize = scrollView.contentSize
    let scrollViewSize = scrollView.frame.size;
    var contentOffset = scrollView.contentOffset;

    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
    }

    scrollView.setContentOffset(contentOffset, animated: false)
}

// UIScrollViewDelegate    
func scrollViewDidZoom(scrollView: UIScrollView) {
    centerScrollViewContents(scrollView)
}
person LightMan    schedule 12.02.2016

Я знаю, что некоторые ответы выше верны, но я просто хочу дать свой ответ с некоторыми пояснениями, комментарии заставят вас понять, почему нам это нравится.

Когда я загружаю scrollView в первый раз, я пишу следующий код, чтобы сделать его по центру. Обратите внимание, что мы сначала устанавливаем contentOffset, а затем contentInset

    scrollView.maximumZoomScale = 8
    scrollView.minimumZoomScale = 1

    // set vContent frame
    vContent.frame = CGRect(x: 0,
                            y: 0  ,
                            width: vContentWidth,
                            height: vContentWidth)
    // set scrollView.contentSize
    scrollView.contentSize = vContent.frame.size

    //on the X direction, if contentSize.width > scrollView.bounds.with, move scrollView from 0 to offsetX to make it center(using `scrollView.contentOffset`)
    // if not, don't need to set offset, but we need to set contentInset to make it center.(using `scrollView.contentInset`)
    // so does the Y direction.
    let offsetX = max((scrollView.contentSize.width - scrollView.bounds.width) * 0.5, 0)
    let offsetY = max((scrollView.contentSize.height - scrollView.bounds.height) * 0.5, 0)
    scrollView.contentOffset = CGPoint(x: offsetX, y: offsetY)

    let topX = max((scrollView.bounds.width - scrollView.contentSize.width) * 0.5, 0)
    let topY = max((scrollView.bounds.height - scrollView.contentSize.height) * 0.5, 0)
    scrollView.contentInset = UIEdgeInsets(top: topY, left: topX, bottom: 0, right: 0)

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

func scrollViewDidZoom(_ scrollView: UIScrollView) {
    //we just need to ensure that the content is in the center when the contentSize is less than scrollView.size.
    let topX = max((scrollView.bounds.width - scrollView.contentSize.width) * 0.5, 0)
    let topY = max((scrollView.bounds.height - scrollView.contentSize.height) * 0.5, 0)
    scrollView.contentInset = UIEdgeInsets(top: topY, left: topX, bottom: 0, right: 0)
}
person Changwei    schedule 01.07.2019

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

person Tim    schedule 22.08.2009
comment
Попытаюсь. Можно ли это сделать с помощью методов UIScrollViewDelegate вместо наблюдения за contentSize? - person hpique; 22.08.2009
comment
Наверняка; вы бы использовали - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale (описано на developer.apple.com/iphone/library/documentation/UIKit/), но я бы предпочел наблюдать contentSize, потому что в противном случае вы откажетесь от новой шкалы масштабирования и в любом случае найти его из вида. (Если только вы не сможете провести отличную математику, основанную на относительных размерах ваших представлений.) - person Tim; 23.08.2009
comment
Спасибо, Тим. scrollViewDidEndZooming - это то, что я использую. См. Рассматриваемый код (я добавил его после вашего первого ответа). Это почти работает. Единственная проблема, которая остается, если я ущипну изображение после того, как будет достигнуто минимальное масштабирование, оно вернется в верхний левый угол. - person hpique; 24.08.2009
comment
Вы имеете в виду, что он работает (центрирует изображение) для сжатия с любым масштабом, кроме минимального масштаба, и ломается только при попытке ущипнуть его снова, когда вы находитесь на минимальном масштабе? Или вообще не работает прищипка до минимального масштаба? - person Tim; 24.08.2009
comment
Первый. Он центрирует изображение для сжатия с любого масштаба, кроме минимального масштаба, и ломается, если вы попытаетесь ущипнуть его снова, когда вы достигнете минимального масштаба. Кроме того, когда он достигает минимального масштаба в первый раз, контент ненадолго анимируется, как будто идет сверху, что вызывает кратковременный странный эффект. По крайней мере, это происходит на симуляторе 3.0. - person hpique; 24.08.2009
comment
Хм. Может быть, вы можете использовать BOOL ivar, чтобы помнить, когда вы уже находитесь на минимальном масштабе масштабирования, и не применять свою собственную логику вставки (или действительно делать многое из чего-либо), когда он получает щепотку на минимальном масштабе? А что касается эффекта анимации, то это может быть либо причуда симулятора, либо реальная проблема - можете ли вы попробовать его на устройстве и посмотреть, что это такое? - person Tim; 24.08.2009
comment
Пытался ничего не делать, если был достигнут минимальный масштаб масштабирования, но все равно ломается. Какой бы код ни нарушал, он не исходит от scrollViewDidEndZooming. Попробую на устройстве убедиться, что это не симулятор. - person hpique; 24.08.2009
comment
В таком случае hgpc, я как бы зациклился на идеях :) Извините! - person Tim; 02.01.2010

Один из элегантных способов центрировать содержимое UISCrollView - это.

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

[myScrollView addObserver:delegate 
               forKeyPath:@"contentSize"
                  options:(NSKeyValueObservingOptionNew) 
                  context:NULL];

Теперь о вашем методе наблюдателя:

- (void)observeValueForKeyPath:(NSString *)keyPath   ofObject:(id)object   change:(NSDictionary *)change   context:(void *)context { 

    // Correct Object Class.
    UIScrollView *pointer = object;

    // Calculate Center.
    CGFloat topCorrect = ([pointer bounds].size.height - [pointer viewWithTag:100].bounds.size.height * [pointer zoomScale])  / 2.0 ;
            topCorrect = ( topCorrect < 0.0 ? 0.0 : topCorrect );

    topCorrect = topCorrect - (  pointer.frame.origin.y - imageGallery.frame.origin.y );

    // Apply Correct Center.
    pointer.center = CGPointMake(pointer.center.x,
                                 pointer.center.y + topCorrect ); }
  • Вам следует изменить [pointer viewWithTag:100]. Замените представлением содержимого UIView.

    • Also change imageGallery pointing to your window size.

Это будет корректировать центр содержимого каждый раз, когда его размер изменится.

ПРИМЕЧАНИЕ. Единственный способ, при котором это содержимое работает не очень хорошо, - это использование стандартных функций масштабирования UIScrollView.

person SEQOY Development Team    schedule 03.11.2009
comment
Не удалось заставить это работать. Кажется, вы центрируете scrollView, а не его содержимое. Почему размер окна имеет значение? Разве код не должен исправлять и положение x? - person hpique; 02.01.2010

Это мое решение этой проблемы, которое отлично работает для любого вида внутри scrollview.

-(void)scrollViewDidZoom:(__unused UIScrollView *)scrollView 
    {
    CGFloat top;
    CGFloat left;
    CGFloat bottom;
    CGFloat right;

    if (_scrollView.contentSize.width < scrollView.bounds.size.width) {
        DDLogInfo(@"contentSize %@",NSStringFromCGSize(_scrollView.contentSize));

        CGFloat width = (_scrollView.bounds.size.width-_scrollView.contentSize.width)/2.0;

        left = width;
        right = width;


    }else {
        left = kInset;
        right = kInset;
    }

    if (_scrollView.contentSize.height < scrollView.bounds.size.height) {

        CGFloat height = (_scrollView.bounds.size.height-_scrollView.contentSize.height)/2.0;

        top = height;
        bottom = height;

    }else {
        top = kInset;
        right = kInset;
    }

    _scrollView.contentInset = UIEdgeInsetsMake(top, left, bottom, right);



  if ([self.tiledScrollViewDelegate respondsToSelector:@selector(tiledScrollViewDidZoom:)])
  {
        [self.tiledScrollViewDelegate tiledScrollViewDidZoom:self];
  }
}
person beat843796    schedule 07.01.2013

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

Сначала определите небольшого помощника:

CGSize CGSizeWithAspectFit(CGSize containerSize, CGSize contentSize) {
    CGFloat containerAspect = containerSize.width / containerSize.height,
            contentAspect = contentSize.width / contentSize.height;

    CGFloat scale = containerAspect > contentAspect
                    ? containerSize.height / contentSize.height
                    : containerSize.width / contentSize.width;

    return CGSizeMake(contentSize.width * scale, contentSize.height * scale);
}

Чтобы сохранить исходные вставки, определенное поле:

UIEdgeInsets originalScrollViewInsets;

И где-нибудь в viewDidLoad залейте его:

originalScrollViewInsets = self.scrollView.contentInset;

Чтобы разместить UIImageView в UIScrollView (при условии, что сам UIImage находится в loadedImage var):

CGSize containerSize = self.scrollView.bounds.size;
containerSize.height -= originalScrollViewInsets.top + originalScrollViewInsets.bottom;
containerSize.width -= originalScrollViewInsets.left + originalScrollViewInsets.right;

CGSize contentSize = CGSizeWithAspectFit(containerSize, loadedImage.size);

UIImageView *imageView = [[UIImageView alloc] initWithFrame:(CGRect) { CGPointZero, contentSize }];
imageView.autoresizingMask = UIViewAutoresizingNone;
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.image = loadedImage;

[self.scrollView addSubview:imageView];
self.scrollView.contentSize = contentSize;

[self centerImageViewInScrollView];

scrollViewDidZoom: из UIScrollViewDelegate для этого представления прокрутки:

- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
    if (scrollView == self.scrollView) {
        [self centerImageViewInScrollView];
    }
}

Наконец, самоцентрирование:

- (void)centerImageViewInScrollView {
    CGFloat excessiveWidth = MAX(0.0, self.scrollView.bounds.size.width - self.scrollView.contentSize.width),
            excessiveHeight = MAX(0.0, self.scrollView.bounds.size.height - self.scrollView.contentSize.height),
            insetX = excessiveWidth / 2.0,
            insetY = excessiveHeight / 2.0;

    self.scrollView.contentInset = UIEdgeInsetsMake(
            MAX(insetY, originalScrollViewInsets.top),
            MAX(insetX, originalScrollViewInsets.left),
            MAX(insetY, originalScrollViewInsets.bottom),
            MAX(insetX, originalScrollViewInsets.right)
    );
}

Я еще не тестировал изменение ориентации (т.е. правильную реакцию на изменение размера самого UIScrollView), но исправить это должно быть относительно легко.

person jazzcat    schedule 20.03.2013

Вы обнаружите, что решение, опубликованное Erdemus, действительно работает, но ... В некоторых случаях метод scrollViewDidZoom не вызывается и ваше изображение застревает в верхнем левом углу. Простое решение - явно вызвать метод при первоначальном отображении изображения, например:

[self scrollViewDidZoom: scrollView];

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

person russes    schedule 07.09.2011

Пример скроллера фотографий Apple делает именно то, что вы ищете. Поместите это в свой подкласс 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; 
}

rel> Пример кода скроллера фотографий от Apple

person Korey Hinton    schedule 01.05.2013

Чтобы анимация плавно отображалась, установите

self.scrollview.bouncesZoom = NO;

и используйте эту функцию (поиск центра с помощью метода, приведенного в этом ответе)

- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale {
    [UIView animateWithDuration:0.2 animations:^{
        float offsetX = MAX((scrollView.bounds.size.width-scrollView.contentSize.width)/2, 0);
        float offsetY = MAX((scrollView.bounds.size.height-scrollView.contentSize.height)/2, 0);
        self.imageCoverView.center = CGPointMake(scrollView.contentSize.width*0.5+offsetX, scrollView.contentSize.height*0.5+offsetY);
    }];
}

Это создает эффект подпрыгивания, но не требует заранее никаких резких движений.

person ldanilek    schedule 18.03.2015

Если ваш внутренний imageView имеет начальную конкретную ширину (например, 300), и вы просто хотите центрировать его ширину только при масштабировании меньше его начальной ширины, это также может вам помочь.

 func scrollViewDidZoom(scrollView: UIScrollView){
    if imageView.frame.size.width < 300{
        imageView.center.x = self.view.frame.width/2
    }
  }
person dejix    schedule 26.09.2016

Вот как я сейчас работаю. Это лучше, но все же не идеально. Попробуйте установить:

 myScrollView.bouncesZoom = YES; 

чтобы исправить проблему с нецентрированием обзора при установке minZoomScale.

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGSize screenSize = [[self view] bounds].size;//[[UIScreen mainScreen] bounds].size;//
CGSize photoSize = [yourImage size];
CGFloat topInset = (screenSize.height - photoSize.height * [myScrollView zoomScale]) / 2.0;
CGFloat sideInset = (screenSize.width - photoSize.width * [myScrollView zoomScale]) / 2.0;

if (topInset < 0.0)
{ topInset = 0.0; }
if (sideInset < 0.0)
{ sideInset = 0.0; } 
[myScrollView setContentInset:UIEdgeInsetsMake(topInset, sideInset, -topInset, -sideInset)];
ApplicationDelegate *appDelegate = (ApplicationDelegate *)[[UIApplication sharedApplication] delegate];

CGFloat scrollViewHeight; //Used later to calculate the height of the scrollView
if (appDelegate.navigationController.navigationBar.hidden == YES) //If the NavBar is Hidden, set scrollViewHeight to 480
{ scrollViewHeight = 480; }
if (appDelegate.navigationController.navigationBar.hidden == NO) //If the NavBar not Hidden, set scrollViewHeight to 360
{ scrollViewHeight = 368; }

imageView.frame = CGRectMake(0, 0, CGImageGetWidth(yourImage)* [myScrollView zoomScale], CGImageGetHeight(yourImage)* [myScrollView zoomScale]);

[imageView setContentMode:UIViewContentModeCenter];
}

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

- (void) scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {
myScrollView.frame = CGRectMake(0, 0, 320, 420);
 //put the correct parameters for your scroll view width and height above
}
person Jonah    schedule 05.01.2010
comment
Привет, Иона. Кажется, мы уже какое-то время занимаемся той же проблемой. Мы проверим два ваших решения и скоро ответим. - person hpique; 06.01.2010
comment
Привет, Джона, я попробовал твое последнее решение. Однако что такое временное изображение? Я пробовал поставить временное изображение = imageView.image; однако, как только я увеличиваю масштаб, изображение исчезает. Спасибо, Паннаг - person psanketi; 26.01.2011
comment
Паннаг, временное изображение было неудачно названо. Его следует называть myImage, так как это любое изображение, которое вы используете. Извините за путаницу. - person Jonah; 26.01.2011

Ладно, думаю, я нашел неплохое решение этой проблемы. Уловка состоит в том, чтобы постоянно подстраивать imageView's фрейм. Я считаю, что это работает намного лучше, чем постоянная регулировка contentInsets или contentOffSets. Мне пришлось добавить немного дополнительного кода для размещения как портретных, так и альбомных изображений.

Вот код:

- (void) scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {

CGSize screenSize = [[self view] bounds].size;

if (myScrollView.zoomScale <= initialZoom +0.01) //This resolves a problem with the code not working correctly when zooming all the way out.
{
    imageView.frame = [[self view] bounds];
    [myScrollView setZoomScale:myScrollView.zoomScale +0.01];
}

if (myScrollView.zoomScale > initialZoom)
{
    if (CGImageGetWidth(temporaryImage.CGImage) > CGImageGetHeight(temporaryImage.CGImage)) //If the image is wider than tall, do the following...
    {
        if (screenSize.height >= CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale]) //If the height of the screen is greater than the zoomed height of the image do the following...
        {
            imageView.frame = CGRectMake(0, 0, 320*(myScrollView.zoomScale), 368);
        }
        if (screenSize.height < CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale]) //If the height of the screen is less than the zoomed height of the image do the following...
        {
            imageView.frame = CGRectMake(0, 0, 320*(myScrollView.zoomScale), CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale]);
        }
    }
    if (CGImageGetWidth(temporaryImage.CGImage) < CGImageGetHeight(temporaryImage.CGImage)) //If the image is taller than wide, do the following...
    {
        CGFloat portraitHeight;
        if (CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale] < 368)
        { portraitHeight = 368;}
        else {portraitHeight = CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale];}

        if (screenSize.width >= CGImageGetWidth(temporaryImage.CGImage) * [myScrollView zoomScale]) //If the width of the screen is greater than the zoomed width of the image do the following...
        {
            imageView.frame = CGRectMake(0, 0, 320, portraitHeight);
        }
        if (screenSize.width < CGImageGetWidth (temporaryImage.CGImage) * [myScrollView zoomScale]) //If the width of the screen is less than the zoomed width of the image do the following...
        {
            imageView.frame = CGRectMake(0, 0, CGImageGetWidth(temporaryImage.CGImage) * [myScrollView zoomScale], portraitHeight);
        }
    }
    [myScrollView setZoomScale:myScrollView.zoomScale -0.01];
}
person Jonah    schedule 04.10.2009

Просто отключите разбиение на страницы, и все будет нормально:

scrollview.pagingEnabled = NO;
person Nagaraj    schedule 21.03.2014

У меня была точно такая же проблема. Вот как я решил

Этот код должен вызываться как результат scrollView:DidScroll:

CGFloat imageHeight = self.imageView.frame.size.width * self.imageView.image.size.height / self.imageView.image.size.width;
BOOL imageSmallerThanContent = (imageHeight < self.scrollview.frame.size.height) ? YES : NO;
CGFloat topOffset = (self.imageView.frame.size.height - imageHeight) / 2;

// If image is not large enough setup content offset in a way that image is centered and not vertically scrollable
if (imageSmallerThanContent) {
     topOffset = topOffset - ((self.scrollview.frame.size.height - imageHeight)/2);
}

self.scrollview.contentInset = UIEdgeInsetsMake(topOffset * -1, 0, topOffset * -1, 0);
person aryaxt    schedule 03.02.2015

Хотя вопрос немного устарел, но проблема все еще существует. Я решил это в Xcode 7, установив ограничение вертикального пространства для самого верхнего элемента (в данном случае topLabel) до superViews (scrollView) на вершине IBOutlet, а затем пересчитав его константу каждый раз, когда содержимое изменяется. в зависимости от высоты подпредставлений scrollView (topLabel и bottomLabel).

class MyViewController: UIViewController {

    @IBOutlet weak var scrollView: UIScrollView!
    @IBOutlet weak var topLabel: UILabel!
    @IBOutlet weak var bottomLabel: UILabel!
    @IBOutlet weak var toTopConstraint: NSLayoutConstraint!

    override func viewDidLayoutSubviews() {
        let heightOfScrollViewContents = (topLabel.frame.origin.y + topLabel.frame.size.height - bottomLabel.frame.origin.y)
        // In my case abs() delivers the perfect result, but you could also check if the heightOfScrollViewContents is greater than 0.
        toTopConstraint.constant = abs((scrollView.frame.height - heightOfScrollViewContents) / 2)
    }

    func refreshContents() {
        // Set the label's text …

        self.view.layoutIfNeeded()
    }
}
person Nick Podratz    schedule 05.10.2015