Strano comportamento di uitableview in iOS11. Le celle scorrono verso l'alto con l'animazione push di navigazione


116

Di recente ho migrato del codice al nuovo SDK iOS 11 beta 5.

Ora ottengo un comportamento molto confuso da UITableView. La vista del tavolo in sé non è così lussuosa. Ho celle personalizzate ma nella maggior parte dei casi è solo per la loro altezza.

Quando spingo il mio controller di visualizzazione con tableview ottengo un'animazione aggiuntiva in cui le celle "scorrono verso l'alto" (o forse l'intero frame tableview viene modificato) e verso il basso lungo l'animazione di navigazione push / pop. Si prega di vedere gif:

tableview ondulato

Creo manualmente tableviewnel loadViewmetodo e imposto i vincoli di layout automatico in modo che siano uguali a leading, trailing, top, bottom della superview di tableview. La superview è la vista principale del controller di visualizzazione.

Il codice di push del controller di visualizzazione è molto standard: self.navigationController?.pushViewController(notifVC, animated: true)

Lo stesso codice fornisce un comportamento normale su iOS 10.

Potresti indicarmi per favore cosa c'è che non va?

EDIT: Ho creato un controller tableview molto semplice e posso riprodurre lo stesso comportamento lì. Codice:

class VerySimpleTableViewController : UITableViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
    }


    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 4
    }


    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)

        cell.textLabel?.text = String(indexPath.row)
        cell.accessoryType = .disclosureIndicator

        return cell
    }


    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)

        let vc = VerySimpleTableViewController.init(style: .grouped)

        self.navigationController?.pushViewController(vc, animated: true)
    }
}

EDIT 2: sono stato in grado di limitare il problema alla mia personalizzazione di UINavigationBar. Ho una personalizzazione come questa:

rootNavController.navigationBar.setBackgroundImage(createFilledImage(withColor: .white, size: 1), for: .default)

dove createFilledImagecrea un'immagine quadrata con dimensioni e colore dati.

Se commento questa riga, riprendo un comportamento normale.

Apprezzerei qualsiasi pensiero su questo argomento.


Potrebbe non essere un problema con la personalizzazione della barra di navigazione. Stavo avendo lo stesso problema (la risposta accettata lo ha risolto) senza alcuna personalizzazione. Penso che potrebbe essere un problema con il modo in cui iOS gestisce la tableview quando viene creata manualmente come sottoview, invece di usare UITableViewController.
Mark Leonard

2
Vedo questo comportamento solo quando impostato navigationBar.isTranslucentsu false, altrimenti funziona bene.
b_ray

5
Sembra essere un bug in iOS11 GM, per favore duplica la segnalazione di bug in modo che questo problema riceva una certa attenzione da Apple: openradar.appspot.com/34465226
b_ray

1
Questo problema sembra essere stato risolto in iOS 11.2 beta. Non imposterei contentInsetAdjustmentBehavior su mai perché interrompe le visualizzazioni di scorrimento di iPhone X non dando riempimento nella parte inferiore dello schermo. La parte inferiore della visualizzazione dei contenuti rimane sotto il "pulsante" Home di iPhone X.
batu

Risposte:


150

Ciò è dovuto alla nuova proprietà UIScrollView's (UITableView è una sottoclasse di UIScrollview)contentInsetAdjustmentBehavior , che è impostata su .automaticper impostazione predefinita.

È possibile ignorare questo comportamento con il seguente frammento di codice nel viewDidLoad di qualsiasi controller interessato:

    tableView.contentInsetAdjustmentBehavior = .never

https://developer.apple.com/documentation/uikit/uiscrollview/2902261-contentinsetadjustmentbehavior


2
questo commento sulla definizione di UIScrollViewContentInsetAdjustmentBehavior.automatic dice: "... per compatibilità con le versioni precedenti regolerà anche il contenuto superiore e inferiore quando la visualizzazione a scorrimento è di proprietà di un controller di visualizzazione con automaticamenteAdjustsScrollViewInsets = YES all'interno di un controller di navigazione, indipendentemente dal fatto che lo scorrimento la vista è scorrevole ". La mia teoria è che il contentInset della barra di navigazione sia influenzato dall'impostazione dell'immagine di sfondo, che viene quindi regolata dinamicamente.
Maggy Hillen

8
Puoi farlo anche con lo storyboard. Impostazioni dimensioni -> Riquadri contenuto -> Imposta "Mai".
Woongbi Kim,

4
Se i tuoi contenuti si estendono dietro la barra delle schede, la disattivazione tableView.contentInsetAdjustmentBehaviorinterromperà gli inserti.
kean

4
Inoltre, la semplice disabilitazione di questo posizionerà l'indicatore di scorrimento dietro il (gizmo superiore) su iPhone X in orizzontale. Il punto centrale di questo comportamento è regolare l'area del contenuto delle visualizzazioni di scorrimento in modo che siano visibili su schermi che non sono rettangolari. Penso che possiamo vederlo solo attualmente su iPhone X Sim.
PhoneyDeveloper

8
Questo problema è stato causato da un bug in iOS 11 in cui i safeAreaInsets della vista del controller di visualizzazione erano impostati in modo errato durante la transizione di navigazione, che dovrebbe essere corretto in iOS 11.2. L'impostazione di contentInsetAdjustmentBehaviorsu .nevernon è un'ottima soluzione alternativa perché probabilmente avrà altri effetti collaterali indesiderati. Se utilizzi una soluzione alternativa, assicurati di rimuoverla per le versioni iOS> = 11.2.
smileyborg

23

Oltre alla risposta di Maggy

Objective-C

if (@available(iOS 11.0, *)) {
    scrollViewForView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}

Questo problema è stato causato da un bug in iOS 11 in cui la safeAreaInsetsvista del controller di visualizzazione era impostata in modo errato durante la transizione di navigazione, che dovrebbe essere risolta in iOS 11.2. L'impostazione di contentInsetAdjustmentBehaviorsu .nevernon è un'ottima soluzione alternativa perché probabilmente avrà altri effetti collaterali indesiderati. Se utilizzi una soluzione alternativa, assicurati di rimuoverla per le versioni iOS> = 11.2

citato da smileyborg (Software Engineer presso Apple)


6

È possibile modificare questo comportamento in una sola volta in tutta l'applicazione utilizzando NSProxy ad esempio didFinishLaunchingWithOptions:

if (@available(iOS 11.0, *)) {
      [UIScrollView appearance].contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
} 

1
Questo interromperà il controller del sistema, come UIImagePickerController
galway

6

Ecco come sono riuscito a risolvere questo problema pur consentendo a iOS 11 di impostare automaticamente gli inserti . Sto usando UITableViewController.

  • Seleziona "Estendi i bordi sotto le barre superiori" e "Estendi i bordi sotto le barre opache" nel controller della visualizzazione nello storyboard (o a livello di programmazione ). Gli inserti dell'area sicura impediranno alla tua vista di andare sotto la barra superiore.
  • Seleziona il pulsante "Inserti in area sicura" nella visualizzazione tabella nello storyboard. (o tableView.insetsContentViewsToSafeArea = true) - Potrebbe non essere necessario, ma è quello che ho fatto.
  • Imposta il comportamento di regolazione del riquadro del contenuto su "Assi scorrevoli" (o tableView.contentInsetAdjustmentBehavior = .scrollableAxes) - .alwayspotrebbe funzionare anche ma non l'ho testato.

Un'altra cosa da provare se tutto il resto fallisce:

Eseguire l'override del viewSafeAreaInsetsDidChange UIViewControllermetodo per fare in modo che la visualizzazione tabella imposti l'impostazione degli inserti della visualizzazione a scorrimento sugli inserti dell'area sicura. Questo è in congiunzione con l'impostazione "Mai" nella risposta di Maggy.

- (void)viewSafeAreaInsetsDidChange {
    [super viewSafeAreaInsetsDidChange];
    self.tableView.contentInset = self.view.safeAreaInsets;
}

Nota: self.tableViewe self.viewdovrebbe essere la stessa cosa perUITableViewController


Ho provato quasi tutto su questo thread e questo ha funzionato per me. TableViewVC incorporato in TabBarVC. Grazie!
Josh Wolff

3

Questo sembra più un bug che un comportamento previsto. Si verifica quando la barra di navigazione non è traslucida o quando è impostata l'immagine di sfondo.

Se imposti contentInsetAdjustmentBehavior su .never, gli inserti di contenuto non verranno impostati correttamente su iPhone X, ad esempio il contenuto andrebbe nell'area inferiore, sotto le barre di scorrimento.

È necessario fare due cose:
1. impedire che scrollView si animi su push / pop
2. mantenere il comportamento .automatic perché è necessario per iPhone X. Senza questo, ad esempio in verticale, il contenuto andrà sotto la barra di scorrimento inferiore.

Nuova semplice soluzione: in XIB: aggiungi semplicemente una nuova UIView in cima alla tua vista principale con in alto, in entrata e in uscita a superview e l'altezza impostata su 0. Non devi collegarlo ad altre sottoview o altro.

Vecchia soluzione:

Nota: se stai usando UIScrollView in modalità orizzontale, ancora non imposta correttamente gli inserti orizzontali (un altro bug?), Quindi devi appuntare la parte iniziale / finale di scrollView su safeAreaInsets in IB.

Nota 2: la soluzione di seguito presenta anche il problema che se tableView viene fatto scorrere verso il basso e si preme il controller e si torna indietro, non sarà più in fondo.

override func viewDidLoad()
{
    super.viewDidLoad()

    // This parts gets rid of animation when pushing
    if #available(iOS 11, *)
    {
        self.tableView.contentInsetAdjustmentBehavior = .never
    }
}

override func viewDidDisappear(_ animated: Bool)
{
    super.viewDidDisappear(animated)
    // This parts gets rid of animation when popping
    if #available(iOS 11, *)
    {
        self.tableView.contentInsetAdjustmentBehavior = .never
    }
}

override func viewDidAppear(_ animated: Bool)
{
    super.viewDidAppear(animated)
    // This parts sets correct behaviour(insets are correct on iPhone X)
    if #available(iOS 11, *)
    {
        self.tableView.contentInsetAdjustmentBehavior = .automatic
    }
}

Cos'è parentView?
PhoneyDeveloper

La vista principale del controller della vista, ho aggiornato la risposta. Grazie.
El Horrible

Quando utilizzo UITableViewController, tableView è la vista del controller di visualizzazione. Inoltre, gli inserti dovrebbero essere diversi quando il dispositivo viene ruotato. Voglio l'adeguamento. Semplicemente non mi piace l'animazione quando appare per la prima volta tableView.
PhoneyDeveloper

Nel tuo caso, poiché UITableView è la vista del controller, dovrebbe avere safeAreaInsets.bottom = 34 (verticale), quindi potresti semplicemente impostare tableView.contentInset = tableView.safeAreaInsets.
El Horrible

Non funziona per me. In viewDidLoad tutti gli inserti sono zero. Se provo a utilizzare quel codice in viewWillLayoutSubviews, l'indicatore di scorrimento viene inserito ma lo stesso tableView diventa in grado di scorrere orizzontalmente. Se guardo solo gli inserti in viewWillLayoutSubviews senza disabilitare la regolazione, la parte inferiore adjustedContentInset diventa 21 e questo è tutto ciò che sembra cambiare.
PhoneyDeveloper


2

assicurati, insieme al codice sopra, di aggiungere codice aggiuntivo come segue. Ha risolto il problema

override func viewDidLayoutSubviews() { 
     super.viewDidLayoutSubviews() 
     tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) // print(thisUISBTV.adjustedContentInset) 
}

Questo da solo ha risolto il mio problema !! Grazie. Ho sprecato troppe ore su questo problema!
Murat Yasar

2

Inoltre, se si utilizza la barra delle schede, il riquadro inferiore del contenuto della vista della raccolta sarà zero. Per questo, inserisci il codice di seguito in viewDidAppear:

if #available(iOS 11, *) {
    tableView.contentInset = self.collectionView.safeAreaInsets
}

2

Nel mio caso ha funzionato (mettilo in viewDidLoad):

self.navigationController.navigationBar.translucent = YES;

1

Rimozione di spazio aggiuntivo nella parte superiore di collectionViewotableView

    if #available(iOS 11.0, *) {
        collectionView.contentInsetAdjustmentBehavior  = .never
        //tableView.contentInsetAdjustmentBehavior  = .never
    } else {
        automaticallyAdjustsScrollViewInsets = false
    }

Sopra il codice collectionViewo tableViewva sotto la barra di navigazione.
Il codice sottostante impedisce alla visualizzazione della raccolta di passare sotto la navigazione

    self.edgesForExtendedLayout = UIRectEdge.bottom

ma mi piace usare la logica e il codice seguenti per UICollectionView

I valori del margine interno vengono applicati a un rettangolo per ridurre o espandere l'area rappresentata da quel rettangolo. In genere, gli inserti del bordo vengono utilizzati durante il layout della vista per modificare la cornice della vista. I valori positivi fanno sì che il frame venga inserito (o ridotto) della quantità specificata. I valori negativi fanno sì che il frame venga iniziato (o espanso) della quantità specificata.

collectionView.contentInset = UIEdgeInsets(top: -30, left: 0, bottom: 0, right: 0)
//tableView.contentInset = UIEdgeInsets(top: -30, left: 0, bottom: 0, right: 0)

Il modo migliore per UICollectionView

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        return UIEdgeInsets(top: -30, left: 0, bottom: 0, right: 0)
}

0

Elimina questo codice funziona per me

self.edgesForExtendedLayout = UIRectEdgeNone

0
  if #available(iOS 11, *) {
        self.edgesForExtendedLayout = UIRectEdge.bottom
  }

Stavo usando UISearchController con resultsController personalizzati con visualizzazione tabella. Spingere il nuovo controller sul controller dei risultati ha causato la visualizzazione della tabella nella ricerca.

Il codice sopra elencato ha risolto completamente il problema

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.