Centra il contenuto di UIScrollView quando è più piccolo


140

Ho un UIImageViewinterno a UIScrollViewche uso per lo zoom e lo scorrimento. Se l'immagine / il contenuto della vista di scorrimento è più grande della vista di scorrimento, tutto funziona correttamente. Tuttavia, quando l'immagine diventa più piccola della vista di scorrimento, si attacca all'angolo in alto a sinistra della vista di scorrimento. Vorrei mantenerlo centrato, come l'app Foto.

Qualche idea o esempio su come mantenere il contenuto UIScrollViewcentrato quando è più piccolo?

Sto lavorando con iPhone 3.0.

Il seguente codice funziona quasi. L'immagine ritorna nell'angolo in alto a sinistra se la pizzico dopo aver raggiunto il livello minimo di zoom.

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

Hai mai risolto completamente questo problema? Sto lottando con lo stesso problema.
Giona,

1
Attenzione: utilizzare il valore inizialeZoom per calcolare l'inser se NON è uno (nessuno zoom). Ad esempio, utilizzare queste righe: float topInset = (maxSize.height - imageSize.height * initialZoom) / 2.0; float sideInset = (maxSize.width - imageSize.width * initialZoom) / 2.0; e infine imposta il valore di zoom iniziale [imageScrollView setZoomScale: initialZoom];
Felix,

Risposte:


228

Ho una soluzione molto semplice! Tutto ciò di cui hai bisogno è aggiornare il centro della tua subview (imageview) mentre esegui lo zoom in ScrollViewDelegate. Se l'immagine ingrandita è più piccola della vista di scorrimento, regolare il centro subview.center altrimenti è (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
Per me questa soluzione è più brusca rispetto all'utilizzo del NYOBetterZoom di Liam. Forse dipende dalla dimensione dell'immagine ecc. La morale; usa la soluzione più adatta alle tue esigenze
wuf810,

2
Stackoverflow GOLD STAR per questo. Stavo lottando con questo, eppure la soluzione è così semplice.
n13

2
per semplificare un po ':CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5, 0.0);
Marek R

6
Questo adattamento mi ha aiutato quando la pergamena aveva inserti: 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
Ho notato che questo, come molte altre tecniche di centraggio, sembra soffrire di problemi durante l'utilizzo zoomToRect:. L'uso contentInsetdell'approccio funziona meglio se hai bisogno di quella funzionalità. Vedi petersteinberger.com/blog/2013/how-to-center-uiscrollview per maggiori dettagli.
Matej Bukovinski,

52

La risposta di @EvelynCordner è stata quella che ha funzionato meglio nella mia app. Molto meno codice rispetto alle altre opzioni.

Ecco la versione Swift se qualcuno ne ha bisogno:

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
Questo funziona alla grande! La vista si è persino animata correttamente dopo la riduzione.
Carter Medlin,

4
Ho inserito questo codice in una funzione e l'ho anche chiamato in viewDidLayoutSubviews per assicurarmi che inizialmente fosse correttamente impostato.
Carter Medlin,

Buona chiamata @CarterMedlin mi ha aiutato così tanto con il mio caricamento iniziale di Swift 3.
AJ Hernandez,

Questo sembra funzionare, ma non riesco a capire perché, dal momento che sembra sempre calcolare gli stessi inserti: i limiti della vista di scorrimento sono fissi, così come le dimensioni del contenuto.
Vaddadi Kartick

contentLe dimensioni cambiano quando si esegue lo zoom scrollLa dimensione del view è fissa
Michał Ziobro

25

Va bene, ho combattuto contro questo negli ultimi due giorni, e finalmente sono arrivato a una soluzione abbastanza affidabile (finora ...) che ho pensato di condividere e salvare gli altri un po 'di dolore. :) Se trovi un problema con questa soluzione, urla!

Ho praticamente analizzato ciò che hanno tutti gli altri: cercando StackOverflow, i forum degli sviluppatori Apple, ho esaminato il codice per tre20, ScrollingMadness, ScrollTestSuite, ecc. Ho provato ad ingrandire il frame UIImageView, giocando con l'offset e / o le inserzioni di UIScrollView dal ViewController, ecc. ma niente ha funzionato alla grande (come hanno scoperto anche tutti gli altri).

Dopo aver dormito su di esso, ho provato un paio di angoli alternativi:

  1. Sottoclasse di UIImageView in modo che modifichi dinamicamente le proprie dimensioni - questo non ha funzionato affatto.
  2. Sottoclasse di UIScrollView in modo da alterare il proprio contenutoOffset in modo dinamico - questo è quello che sembra essere un vincitore per me.

Con questo metodo di sottoclasse UIScrollView ho la precedenza sul mutatore contentOffset, quindi non sta impostando {0,0} quando l'immagine è ridimensionata più piccola della finestra - invece sta impostando l'offset in modo tale che l'immagine venga mantenuta centrata nella finestra. Finora sembra sempre funzionare. L'ho controllato con immagini larghe, alte, minuscole e grandi e non ho il problema "funziona ma pizzica al minimo zoom lo rompe".

Ho caricato un progetto di esempio su github che utilizza questa soluzione, puoi trovarlo qui: http://github.com/nyoron/NYOBetterZoom


2
Per gli interessati: ho aggiornato il progetto sopra collegato, quindi è un po 'meno dipendente dal ViewController che fa "la cosa giusta", lo stesso UIScrollView personalizzato si occupa di più dettagli.
Liam Jones,

2
Liam, rock. Lo dico come autore di ScrollingMadness. A proposito di iPad in 3.2+ impostazione contenutoInset in scrollViewDidZoom (un nuovo metodo delegato 3.2+) Funziona.
Andrey Tarantsov,

Pezzo di codice molto pulito. Ho grattato la testa con questo per un po '. Aggiunto il progetto a handyiphonecode.com
samvermette il

Questo è fantastico Grazie. Non compensava il fatto che il mio UIStatusBar fosse nascosto, quindi ho cambiato la linea anOffset.y = -(scrollViewSize.height - zoomViewSize.height) / 2.0inanOffset.y = (-(scrollViewSize.height - zoomViewSize.height) / 2.0) + 10;
Gordon Fontenot

La soluzione data da Erdemus è molto più semplice secondo me.
Gyozo Kudor,

23

Questo codice dovrebbe funzionare sulla maggior parte delle versioni di iOS (ed è stato testato per funzionare dalla versione 3.1 in poi).

È basato sul codice Apple WWDC per il photoscoller.

Aggiungi quanto segue alla sottoclasse di UIScrollView e sostituisci tileContainerView con la vista contenente l'immagine o i riquadri:

- (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
questa dovrebbe essere la risposta accettata, perché è più semplice. Grazie. Mi hai salvato la vita!!!!!! : D
Duck,

Dopo aver rimosso tutte le chiamate API iOS 3.2+, la logica di centraggio sembra funzionare solo su dispositivi con iOS 3.2+ e non 3.1.3 (jumpy, sfarfallio, offset casuale). Ho confrontato le uscite di origine e dimensioni dei fotogrammi tra 3.1.3 e 3.2+ e anche se corrispondono, per qualche motivo, la vista figlio viene comunque posizionata in modo errato. Molto strano. Solo la risposta di Liam Jone ha funzionato per me.
David H

1
Entrambe le soluzioni @Erdemus e @JosephH funzionano, ma l' UIScrollViewapproccio della sottoclasse sembra preferibile: viene invocato quando la vista viene visualizzata per la prima volta + continuamente mentre è in corso lo zoom (mentre scrollViewDidZoomviene invocato solo una volta per scorrimento e dopo il fatto)
SwiftArchitect

22

Per una soluzione più adatta per le viste di scorrimento che utilizzano il layout automatico, utilizzare gli inserimenti di contenuto della vista di scorrimento anziché aggiornare i frame delle viste secondarie della vista di scorrimento.

- (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
Questo ha funzionato per me, tuttavia se l'immagine è più piccola all'inizio, in qualche modo questo codice (quando chiamato direttamente) non ha aggiornato la vista di scorrimento. Quello che dovevo fare era mettere prima quello UIViewche è dentro UIScrollviewlo stesso centro ( view.center = scrollview.center;) e poi nel scrollViewDidZoomset il view.frame.origin xe ydi 0nuovo.
Morksinaanab,

Funziona bene anche per me, ma ho fatto uno dispatch_asyncalla coda principale in viewWillAppearcui chiamo scrollViewDidZoom:sulla mia vista di scorrimento principale. Questo fa apparire la vista con immagini centrate.
Pascal,

Effettuare una chiamata a questo codice viewDidLayoutSubviewsper assicurarsi che sia impostato correttamente quando la vista viene visualizzata per la prima volta.
Carter Medlin,

21

Attualmente sto eseguendo la sottoclasse UIScrollViewe l'override setContentOffset:per regolare l'offset in base a contentSize. Funziona sia con il pizzico che con lo zoom programmatico.

@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

Oltre ad essere breve e dolce, questo codice produce uno zoom molto più fluido rispetto alla soluzione @Erdemus. Puoi vederlo in azione nella demo di RMGallery .


6
Non è necessario sottoclassare UIScrollView per implementare questo metodo. Apple ti consente di visualizzare gli eventi di scorrimento e zoom nei metodi delegati. In particolare: - (void) scrollViewDidZoom: (UIScrollView *) scrollView;
Rog,

1
La migliore soluzione che abbia mai visto (funziona anche quando si usano i vincoli).
Frizlab,

12

Ho trascorso una giornata a combattere con questo problema e ho finito per implementare scrollViewDidEndZooming: withView: atScale: come segue:

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

Questo assicura che quando l'immagine è più piccola dello schermo, ci sia ancora spazio sufficiente attorno ad essa in modo da poterla posizionare nel punto esatto che desideri.

(supponendo che UIScrollView contenga un UIImageView per contenere l'immagine)

In sostanza, ciò che fa è verificare se la larghezza / altezza della vista dell'immagine è più piccola della larghezza / altezza dello schermo e, in tal caso, creare un inserto di metà della larghezza / altezza dello schermo (probabilmente potresti ingrandirlo se vuoi che l'immagine uscire dai limiti dello schermo).

Si noti che poiché si tratta di un metodo UIScrollViewDelegate , non dimenticare di aggiungerlo alla dichiarazione del controller di visualizzazione, in modo da evitare problemi di compilazione.


7

Apple ha rilasciato i video della sessione WWDC 2010 a tutti i membri del programma per sviluppatori iPhone. Uno degli argomenti discussi è come hanno creato l'app per le foto !!! Costruiscono un'app molto simile passo dopo passo e hanno reso tutto il codice disponibile gratuitamente.

Non utilizza neanche API private. Non posso inserire qui alcun codice a causa dell'accordo di non divulgazione, ma ecco un link per scaricare il codice di esempio. Probabilmente dovrai accedere per accedere.

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

Ed ecco un link alla pagina WWDC di iTunes:

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
L'esempio in questione è MyImagePicker e, esilarante, mostra lo stesso problema.
Colin Barrett,

1
Avrei dovuto essere più chiaro. L'esempio in questione è in realtà "PhotoScroller" non "MyImagePicker". Hai ragione che "MyImagePicker" non funziona correttamente. Ma "PhotoScroller" lo fa. Provalo.
Giona,

Ricordi forse il titolo del video del WWDC in cui discutono di Photoscroller?
Grzegorz Adam Hankiewicz,

1
Si chiama "Progettazione di app con viste di scorrimento".
Giona,

2

Ok, questa soluzione funziona per me. Ho una sottoclasse di UIScrollViewcon un riferimento a UIImageViewche sta visualizzando. Ogni volta che si UIScrollViewingrandisce, la proprietà contentSize viene adattata. È nel setter che ridimensiono in modo UIImageViewappropriato e regola anche la sua posizione centrale.

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

Evertime Ho impostato l'immagine sul UIScrollViewridimensionamento. L' UIScrollViewnel ScrollView è anche una classe personalizzata che ho creato.

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

Con questi due metodi sono in grado di avere una vista che si comporta proprio come l'app per le foto.


Questo sembra fare il trucco, ed è una soluzione abbastanza semplice. Alcune transizioni di zoom non sono perfette, ma credo che possano essere riparate. Sperimenterò ancora.
hpique

La soluzione era chiara prima dell'aggiornamento. Ora è un po 'confuso. Puoi chiarire?
hpique,

2

Il modo in cui l'ho fatto è quello di aggiungere una visione extra nella gerarchia:

UIScrollView -> UIView -> UIImageView

Dai le tue UIViewstesse proporzioni UIScrollViewe centra il tuo UIImageViewin quello.


Grazie, ciuffo. Proverò anche questo. Puoi pubblicare il codice di esempio o modificare il mio codice di esempio per mostrare come costruisci la gerarchia della vista?
hpique,

Questo tipo di opere. Ad eccezione del fatto che poiché UIView ha le dimensioni di UIScrollView, se l'immagine è più piccola (ovvero orizzontale anziché verticale) è possibile scorrere parte dell'immagine dallo schermo. L'app per le foto non lo consente e sembra molto più bella.
Giona,

2

So che alcune risposte sopra sono giuste, ma voglio solo dare la mia risposta con qualche spiegazione, i commenti ti faranno capire perché ci piace così.

Quando carico scrollView per la prima volta, scrivo il seguente codice per renderlo centrale, si prega di notare che abbiamo impostato contentOffsetprima, quindicontentInset

    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)

Quindi, quando pizzico vContent, scrivo il seguente codice per renderlo centrale.

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

Se contentInsetnon è necessario per nient'altro, può essere utilizzato per centrare il contenuto di 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)
    }
}

Vantaggio se questo approccio è che è ancora possibile utilizzare contentLayoutGuideper posizionare il contenuto all'interno di 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)
])

oppure trascina e rilascia il contenuto in Interface Builder di Xcode.


1

È possibile guardare la contentSizeproprietà di UIScrollView (usando l'osservazione del valore-chiave o simile) e regolare automaticamente contentInsetogni volta che le contentSizemodifiche sono inferiori alla dimensione della vista di scorrimento.


Proverò. È possibile farlo con i metodi UIScrollViewDelegate invece di osservare contentSize?
hpique,

Più probabilmente; useresti - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale(descritto su developer.apple.com/iphone/library/documentation/UIKit/… ), ma preferirei osservare contentSizeperché altrimenti finiresti per scartare la nuova scala dello zoom e trovarla comunque dalla vista. (A meno che tu non riesca a fare un bel po 'di matematica in base alle dimensioni relative delle tue opinioni.)
Tim

Grazie Tim. scrollViewDidEndZooming è quello che sto usando. Vedi il codice in questione (l'ho aggiunto dopo la tua prima risposta). Funziona quasi. L'unico problema che rimane se quello se pizzico l'immagine dopo aver raggiunto il minimo di ZoomScala, ritorna nell'angolo in alto a sinistra.
hpique,

Vuoi dire che funziona (centra l'immagine) per pizzicare da qualsiasi scala diversa dalla scala minima dello zoom e si interrompe solo se provi a pizzicarlo di nuovo una volta che hai raggiunto la scala minima? O non funziona affatto per pizzicare alla scala minima?
Tim

Il primo. Centra l'immagine per il pizzicamento da qualsiasi scala diversa dalla scala minima dello zoom e si interrompe se si tenta di pizzicarla di nuovo una volta raggiunta la scala minima. Inoltre, quando raggiunge la scala di zoom minima per la prima volta, il contenuto viene brevemente animato come proveniente dall'alto, il che provoca un breve effetto strano. Questo succede almeno sul simulatore 3.0.
hpique,

1

Un modo elegante per centrare il contenuto UISCrollViewè questo.

Aggiungi un osservatore al contentSize del tuo UIScrollView, quindi questo metodo verrà chiamato ogni volta che cambia il contenuto ...

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

Ora sul tuo metodo di osservazione:

- (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 ); }
  • Dovresti cambiare il [pointer viewWithTag:100]. Sostituisci con la visualizzazione dei contenuti UIView.

    • Cambia anche il imageGallerypuntamento alla dimensione della finestra.

Ciò correggerà il centro del contenuto ogni volta che cambia la sua dimensione.

NOTA: L'unico modo in cui questo contenuto non funziona molto bene è con la funzionalità di zoom standard di UIScrollView.


Impossibile farlo funzionare. Sembra che stai centrando scrollView e non il suo contenuto. Perché le dimensioni della finestra sono importanti? Il codice non dovrebbe correggere anche la posizione x?
Hpique

1

Questa è la mia soluzione a quel problema che funziona abbastanza bene per qualsiasi tipo di vista all'interno di una vista di scorrimento.

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

Ci sono molte soluzioni qui, ma rischierei di mettere qui le mie. Va bene per due motivi: non rovina l'esperienza di zoom, come farebbe l'aggiornamento del frame di visualizzazione delle immagini in corso, e rispetta anche gli inserti di visualizzazione di scorrimento originali (diciamo, definiti in xib o storyboard per la gestione aggraziata di barre degli strumenti semi-trasparenti ecc.) .

Innanzitutto, definisci un piccolo aiuto:

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

Per conservare gli inserti originali, campo definito:

UIEdgeInsets originalScrollViewInsets;

E da qualche parte in vistaDidLoad riempilo:

originalScrollViewInsets = self.scrollView.contentInset;

Per posizionare UIImageView in UIScrollView (supponendo che UIImage stesso sia caricato in Var immagine):

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: da UIScrollViewDelegate per quella vista scroll:

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

Un infine, centrandosi:

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

Non ho ancora testato il cambiamento di orientamento (ovvero la reazione corretta per il ridimensionamento di UIScrollView stesso), ma la correzione per questo dovrebbe essere relativamente facile.


1

Scoprirai che la soluzione pubblicata da Erdemus funziona, ma ... Ci sono alcuni casi in cui il metodo scrollViewDidZoom non viene invocato e l'immagine è bloccata nell'angolo in alto a sinistra. Una soluzione semplice è quella di invocare esplicitamente il metodo quando si visualizza inizialmente un'immagine, come questa:

[self scrollViewDidZoom: scrollView];

In molti casi, potresti invocare questo metodo due volte, ma questa è una soluzione più pulita di alcune delle altre risposte in questo argomento.


1

L'esempio di Photo Scroller di Apple fa esattamente quello che stai cercando. Inseriscilo nella sottoclasse UIScrollView e modifica _zoomView in 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; 
}

Codice di esempio di Photo Scroller per Apple


1

Per far fluire bene l'animazione, imposta

self.scrollview.bouncesZoom = NO;

e usa questa funzione (trovare il centro usando il metodo a questa risposta )

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

Questo crea l'effetto di rimbalzo ma non comporta alcun movimento improvviso in anticipo.


1

Solo la risposta approvata in breve tempo, ma senza sottoclasse utilizzando il delegato

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

Nel caso in cui imageView interno abbia una larghezza specifica iniziale (ad es. 300) e desideri solo centrare la sua larghezza solo su uno zoom più piccolo della sua larghezza iniziale, questo potrebbe aiutarti.

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

0

Ecco come sto facendo questo lavoro. È meglio ma ancora non perfetto. Prova a impostare:

 myScrollView.bouncesZoom = YES; 

per risolvere il problema con la vista non centrata quando su 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];
}

Inoltre, faccio quanto segue per evitare che l'immagine si attacchi a un lato dopo lo zoom indietro.

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

Ciao Giona. Sembra che abbiamo affrontato lo stesso problema per un po '. Controllerà le tue due soluzioni e risponderà presto.
hpique

Ciao Jonah, ho provato la tua ultima soluzione. Tuttavia, che cos'è l'immagine temporanea? Ho provato a mettere temporaryImage = imageView.image; tuttavia, una volta ingrandito, l'immagine scompare. Grazie, Pannag
psanketi il

Pannag, immagine temporanea era scarsamente chiamata. Dovrebbe essere chiamato myImage in quanto è esattamente qualunque immagine tu stia usando. Dispiace per la confusione.
Giona,

0

Ok, penso di aver trovato una soluzione abbastanza buona a questo problema. Il trucco è di regolare costantemente la imageView'scornice. Trovo che funzioni molto meglio che regolare costantemente il contentInsetsocontentOffSets . Ho dovuto aggiungere un po 'di codice extra per ospitare sia le immagini verticali che quelle orizzontali.

Ecco il codice:

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

Disattiva semplicemente l'impaginazione, quindi funzionerà bene:

scrollview.pagingEnabled = NO;

0

Ho avuto lo stesso identico problema. Ecco come ho risolto

Questo codice dovrebbe essere chiamato come risultato di 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

Sebbene la domanda sia un po 'vecchia, il problema esiste ancora. L'ho risolto in Xcode 7 apportando il vincolo di spazio verticale dell'elemento più in alto (in questo caso il topLabel) alle superViste (il scrollView) in alto IBOutlete quindi ricalcolando la sua costante ogni volta che il contenuto cambia a seconda dell'altezza delle sottoview dello scrollView ( topLabele 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()
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.