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

141

У меня есть 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)];
}
hpique
источник
Вы когда-нибудь решали эту проблему полностью? Я борюсь с той же проблемой.
Иона
1
Внимание: используйте значение initialZoom для вычисления вставки, если это НЕ один (без масштабирования). Например, используйте следующие строки: float topInset = (maxSize.height - imageSize.height * initialZoom) / 2.0; float sideInset = (maxSize.width - imageSize.width * initialZoom) / 2.0; и, наконец, установите начальное значение масштабирования [imageScrollView setZoomScale: initialZoom];
Феликс

Ответы:

228

У меня очень простое решение! Все, что вам нужно, это обновить центр вашего подпредставления (изображения) при увеличении масштаба 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);
}
Эрдемус
источник
5
Для меня это решение более отрывистое, чем использование NYOBetterZoom Лиама. Может быть, это зависит от размера изображения и т. Д. Мораль; используйте решение, которое лучше всего соответствует вашим потребностям
wuf810
2
Stackoverflow GOLD STAR для этого. Я боролся с этим, но решение очень простое.
n13 01
2
чтобы немного упростить:CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5, 0.0);
Marek R
6
Эта настройка помогла мне, когда в 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);
Киран Харпер,
3
Я заметил, что при использовании этого метода, как и многих других методов центрирования, возникают проблемы zoomToRect:. Этот contentInsetподход работает лучше, если вам понадобится эта функциональность. См. Petersteinberger.com/blog/2013/how-to-center-uiscrollview для получения более подробной информации.
Матей Буковински
52

Ответ @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)
}
Уильям Т.
источник
4
Этот отлично работает! Изображение даже после сжатия правильно анимировалось.
Carter Medlin
4
Я поместил этот код в func, а также вызвал его в viewDidLayoutSubviews, чтобы убедиться, что он был правильно настроен изначально.
Carter Medlin
Хороший звонок @CarterMedlin мне очень помог с начальной загрузкой Swift 3.
AJ Hernandez
Кажется, это работает, но я не могу понять почему, поскольку он всегда, кажется, вычисляет одни и те же вставки: границы просмотра прокрутки фиксированы, как и размер содержимого.
Kartick Vaddadi
contentSize изменяется при масштабировании scrollView size фиксируется только
Michał Ziobro
25

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

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

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

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

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

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

Лиам Джонс
источник
2
Для тех, кто заинтересован - я обновил связанный выше проект, поэтому он немного меньше зависит от того, что ViewController делает «правильные вещи», пользовательский UIScrollView сам позаботится о большей части деталей.
Лиам Джонс,
2
Лиам, ты молодец. Я говорю это как автор ScrollingMadness. Кстати, на iPad в 3.2+ настройка contentInset в scrollViewDidZoom (новый метод делегата версии 3.2+) просто работает.
Андрей Таранцов
Очень аккуратный фрагмент кода. Я уже давно ломаю голову над этим. Добавлен проект на handyiphonecode.com
samvermette 04
Это круто. Спасибо. Это не компенсировало того, что мой UIStatusBar был скрыт, поэтому я изменил строку anOffset.y = -(scrollViewSize.height - zoomViewSize.height) / 2.0наanOffset.y = (-(scrollViewSize.height - zoomViewSize.height) / 2.0) + 10;
Gordon Fontenot
На мой взгляд, решение, данное bi Erdemus, намного проще.
gyozo kudor 02
23

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

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

- (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);
}
Эвелин Корднер
источник
1
Это сработало для меня, однако, если изображение меньше для начала, этот код (при прямом вызове) не обновлял scrollview. Что нужно TODO был первым положить , UIViewчто находится внутри UIScrollviewв тот же центр ( view.center = scrollview.center;) , а затем в scrollViewDidZoomсете view.frame.origin xи yна 0снова.
morksinaanab
У меня тоже хорошо работает, но я выполнил операцию dispatch_asyncс основной очередью, в viewWillAppearкоторой я вызываю scrollViewDidZoom:свой основной вид прокрутки. Это заставляет представление отображаться с центрированными изображениями.
Паскаль
Выполните вызов этого кода, viewDidLayoutSubviewsчтобы убедиться, что он правильно настроен, когда представление отображается в первый раз.
Carter Medlin
21

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

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

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

Vicarius
источник
7

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

Иона
источник
1
Рассматриваемый пример - MyImagePicker, и он, как ни странно, демонстрирует ту же проблему.
Колин Барретт,
1
Я должен был быть более ясным. Рассматриваемый пример на самом деле «PhotoScroller», а не «MyImagePicker». Вы правы, что «MyImagePicker» работает некорректно. Но «PhotoScroller» умеет. Попытайся.
Иона
Вы помните, может быть, название видео WWDC, где обсуждают Photoscroller?
Grzegorz Adam Hankiewicz
1
Это называется «Разработка приложений с помощью прокручиваемых представлений».
Иона,
3

Если он 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.

Войцех Нагродски
источник
2

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

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

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

АГА
источник
Кажется, это помогает, и это довольно простое решение. Некоторые переходы масштабирования не идеальны, но я считаю, что это можно исправить. Я еще поэкспериментирую.
hpique
Решение было ясным до обновления. Теперь это немного сбивает с толку. Вы можете уточнить?
hpique
2

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

UIScrollView -> UIView -> UIImageView

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

шляпник
источник
Спасибо, шляпка. Попробую и это. Можете ли вы опубликовать образец кода или изменить мой образец кода, чтобы показать, как вы строите иерархию представлений?
hpique
1
Такого рода работы. За исключением того факта, что поскольку UIView имеет размер UIScrollView, если изображение меньше (т.е. альбомная, а не портретная), вы можете прокрутить часть изображения за пределы экрана. Приложение для фотографий не позволяет этого и выглядит намного лучше.
Иона
2

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

Когда я загружаю 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)
}
Чанвэй
источник
1

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

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

Один из элегантных способов центрировать содержимое 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.

    • Также измените imageGalleryуказатель на размер вашего окна.

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

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

Команда разработчиков SEQOY
источник
Не удалось заставить это работать. Кажется, вы центрируете scrollView, а не его содержимое. Почему размер окна имеет значение? Разве код не должен исправлять и положение x?
hpique 02
1

Это мое решение этой проблемы, которое отлично работает для любого вида внутри 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];
  }
}
бит843796
источник
1

Здесь есть масса решений, но я бы рискнул поставить здесь свое. Это хорошо по двум причинам: это не мешает масштабированию, как и обновление кадра просмотра изображения в процессе, а также уважает оригинальные вставки прокрутки (скажем, определенные в 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), но исправить это должно быть относительно легко.

jazzcat
источник
1

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

[self scrollViewDidZoom: scrollView];

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

руссы
источник
1

Пример скроллера фотографий 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; 
}

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

Кори Хинтон
источник
1

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

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

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

ldanilek
источник
1

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

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)
}
Светлый человек
источник
1

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

 func scrollViewDidZoom(scrollView: UIScrollView){
    if imageView.frame.size.width < 300{
        imageView.center.x = self.view.frame.width/2
    }
  }
dejix
источник
0

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

 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
}
Иона
источник
Привет, Иона. Кажется, мы уже какое-то время занимаемся той же проблемой. Мы проверим два ваших решения и скоро ответим.
hpique 06
Привет, Джона, я попробовал твое последнее решение. Однако что такое временное изображение? Я пробовал поставить временное изображение = imageView.image; однако, как только я увеличиваю масштаб, изображение исчезает. Спасибо, Паннаг
psanketi
Паннаг, временное изображение было плохо названо. Его следует называть myImage, так как это любое изображение, которое вы используете. Извините за путаницу.
Иона
0

Ладно, думаю, я нашел неплохое решение этой проблемы. Хитрость заключается в том, чтобы постоянно подправлять 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];
}
Иона
источник
0

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

scrollview.pagingEnabled = NO;
Нагарадж
источник
0

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

Этот код должен вызываться в результате 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);
арьякст
источник
0

Хотя вопрос немного устарел, но проблема все еще существует. Я решил это в Xcode 7 , установив ограничение вертикального пространства для самого верхнего элемента (в данном случае topLabel) на superViews (the scrollView) top an, IBOutletа затем пересчитав его константу каждый раз, когда содержимое изменяется в зависимости от высоты subviews 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()
    }
}
Ник Подрац
источник