Центральний вміст UIScrollView, коли менший


140

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

Ви коли-небудь повністю вирішили цю проблему? Я борюся з тим же питанням.
Йона

1
Увага: Використовуйте початкове значення Zom для обчислення вставки, якщо воно НЕ одне (масштабування немає). Наприклад, використовуйте ці рядки: float topInset = (maxSize.height - imageSize.height * початковийZoom) / 2.0; float sideInset = (maxSize.width - imageSize.width * InitiZoom) / 2.0; і, нарешті, встановити початкове значення масштабу [imageScrollView setZoomScale: beginZoom];
Фелікс

Відповіді:


228

У мене дуже просте рішення! Все, що вам потрібно, це оновити центр свого підпогляду (перегляд зображень) під час збільшення масштабу ScrollViewDelegate. Якщо збільшене зображення менше, ніж прокрутка, відрегулюйте 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

2
щоб трохи спростити:CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5, 0.0);
Marek R

6
Це коригування мені допомогло, коли у прокрутки було вставлено вставки: 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);
Kieran Harper,

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
Цей чудово працює! Перегляд навіть анімував належним чином після зменшення.
Картер Медлін

4
Я поставив цей код у функцію, а також назвав його в viewDidLayoutSubviews, щоб переконатися, що він був правильно налаштований спочатку.
Картер Медлін

Гарний дзвінок @CarterMedlin мені дуже допоміг із початковим завантаженням Swift 3.
AJ Ернандес

Це, здається, працює, але я не можу зрозуміти чому, оскільки, здається, завжди обчислюються однакові вставки: межі подання прокрутки виправлені, як і розмір вмісту.
Ваддаді Картік

Зміст розміру зміни при збільшенні розміру scrollView фіксовано
Michał Ziobro

25

Гаразд, я боровся з цим протягом двох останніх днів і знову, і нарешті прийшов до досить надійного (поки що ...) рішення, я подумав, що мушу поділитися цим і врятувати іншим біль. :) Якщо у вас виникли проблеми з цим рішенням, будь ласка, кричіть!

Я в основному переглянув те, що мають усі інші: пошук StackOverflow, Форуми розробників Apple, подивився код на три20, 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. BTW на iPad у встановленому вмістом 3.2+ вмістуInset у scrollViewDidZoom (новий метод делегата 3,2+) Просто працює.
Андрій Таранцов

Дуже акуратний фрагмент коду. Я вже певний час чухаю голову. Додано проект на handyiphonecode.com
samvermette

Це чудово. Дякую. Це не компенсувало те, що мій UIStatusBar ховався, тому я змінив лінію anOffset.y = -(scrollViewSize.height - zoomViewSize.height) / 2.0наanOffset.y = (-(scrollViewSize.height - zoomViewSize.height) / 2.0) + 10;
Gordon Fontenot

На мою думку, рішення Ердемус набагато простіше.
gyozo kudor

23

Цей код повинен працювати на більшості версій iOS (і був протестований для роботи на 3.1 вище).

Він заснований на коді Apple WWDC для фотоколер.

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

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

3
це має бути прийнята відповідь, тому що це простіше. Дякую. Ти врятував мені життя!!!!!! : D
Качка

Після видалення всіх дзвінків API iOS 3.2+ API логіка центрування, здається, працює лише на пристроях із iOS 3.2+ та не 3.1.3 (стрибки, мерехтіння, випадкове зміщення). Я порівнював результати походження та розміру кадру між 3.1.3 та 3.2+ і навіть незважаючи на те, що вони збігаються, чомусь дочірнє уявлення все ще розташоване неправильно. Дуже дивно. Лише відповідь Ліама Джона працювала для мене.
Девід Н

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
Це працювало для мене, однак якщо зображення менше, щоб почати якось, цей код (при безпосередньому виклику) не оновлював перегляд прокрутки. Що мені потрібно було TODO був першим покласти , UIViewщо знаходиться всередині UIScrollviewв той же центр ( view.center = scrollview.center;) , а потім в scrollViewDidZoomсеті view.frame.origin xі yна 0знову.
morksinaanab

Також добре працює для мене, але я зайшов dispatch_asyncдо головної черги, viewWillAppearкуди я закликаю scrollViewDidZoom:основний вид прокрутки. Завдяки цьому подання відображається із зображеннями по центру.
Паскаль

Зателефонуйте на цей код, viewDidLayoutSubviewsщоб переконатися, що він правильно налаштований під час першого перегляду.
Картер Медлін

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 .


6
Вам не потрібно підкласи UIScrollView, щоб реалізувати цей метод. Apple дозволяє переглядати події прокрутки та масштабування в методах делегування. Зокрема: - (недійсний) scrollViewDidZoom: (UIScrollView *) scrollView;
Rog

1
Найкраще рішення, яке я бачив ще (працюйте навіть при використанні обмежень).
Фрізлаб

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 , не забудьте додати його до декларації контролера перегляду, щоб уникнути проблеми з збіркою.


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
Це називається "Проектування програм із переглядами прокрутки".
Йона

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. У 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;
}

За допомогою цих двох методів я маю змогу отримати уявлення, яке веде себе так само, як додаток для фотографій.


Це, здається, робить трюк, і це досить просте рішення. Деякі переходи масштабування не є ідеальними, але я вважаю, що це можна виправити. Я ще експериментую.
hpique

До оновлення рішення було зрозумілим. Зараз це трохи заплутано. Ви можете уточнити?
hpique

2

Я зробив це, щоб додати додатковий вигляд в ієрархію:

UIScrollView -> UIView -> UIImageView

Надайте UIViewте саме співвідношення сторін UIScrollView, що і ваше , і зосередьте його UIImageViewна цьому.


Спасибі, сіняку. Спробую і це. Чи можете ви опублікувати зразок коду чи змінити мій зразок коду, щоб показати, як ви будуєте ієрархію перегляду?
hpique

Цей вид робіт. За винятком того, що оскільки розмір 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)
}

2

Якщо contentInsetнічого іншого не потрібно, його можна використовувати для центрування вмісту прокрутки.

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.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's Interface Builder.


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 .


Не вдалося це зробити. Здається, ви зосереджуєте scrollView, а не його вміст. Чому значення розміру вікна має значення? Чи не повинен код також виправляти позицію x?
hpique

1

Це моє рішення цієї проблеми, яка працює чудово для будь-якого виду прокрутки.

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

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 знаходиться у varImage 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), але виправити це слід досить просто.


1

Ви побачите, що рішення, розміщене Ердемусом, працює, але ... Є деякі випадки, коли метод scrollViewDidZoom не викликається, а ваше зображення застрягає у верхньому лівому куті. Просте рішення - явно викликати метод, коли ви спочатку відображаєте зображення, наприклад:

[self scrollViewDidZoom: scrollView];

У багатьох випадках ви можете скористатися цим методом двічі, але це більш чисте рішення, ніж деякі інші відповіді в цій темі.


1

Приклад Photo Scroller від 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);
    }];
}

Це створює ефект підстрибування, але заздалегідь не передбачає різких рухів.


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

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

Привіт, Йона, я спробував ваше останнє рішення. Однак що таке тимчасовий малюнок? Я спробував поставитиvremeImage = 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 a, 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()
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.