Dziwne zachowanie uitableview w iOS11. Komórki przewijają się w górę z animacją wypychania nawigacji

116

Niedawno przeprowadziłem migrację części kodu do nowego zestawu SDK iOS 11 beta 5.

UITableView zachowuje się teraz bardzo myląco. Sam widok tabeli nie jest taki wyszukany. Mam niestandardowe komórki, ale w większości są to tylko ich wysokość.

Kiedy naciskam kontroler widoku z widokiem tabeli, otrzymuję dodatkową animację, w której komórki „przewijają się w górę” (lub być może cała ramka widoku tabeli jest zmieniana) iw dół wzdłuż animacji nawigacji push / pop. Proszę zobaczyć gif:

falisty widok tabeli

Tworzę ręcznie tablevieww loadViewmetodzie i ustawiam ograniczenia automatycznego układu, aby były równe wiodącemu, końcowemu, górnemu i dolnemu nadzorowi widoku tabeli. Superview to widok główny kontrolera widoku.

Wyświetl kod wypychania kontrolera jest bardzo standardowy: self.navigationController?.pushViewController(notifVC, animated: true)

Ten sam kod zapewnia normalne zachowanie w systemie iOS 10.

Czy mógłbyś wskazać mi, co jest nie tak?

EDYCJA: Zrobiłem bardzo prosty kontroler widoku tabeli i mogę tam odtworzyć to samo zachowanie. Kod:

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

EDYCJA 2: Udało mi się zawęzić problem do mojego dostosowania UINavigationBar. Mam takie dostosowanie:

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

gdzie createFilledImagetworzy kwadratowy obraz o podanym rozmiarze i kolorze.

Jeśli skomentuję tę linię, wrócę do normalnego zachowania.

Byłbym wdzięczny za wszelkie przemyślenia w tej sprawie.

iur
źródło
Może to nie być problemem przy dostosowywaniu paska nawigacyjnego. Miałem ten sam problem (zaakceptowana odpowiedź go rozwiązała) bez dostosowywania. Myślę, że może to być problem ze sposobem, w jaki iOS obsługuje widok tabeli, gdy jest tworzony ręcznie jako podwidok, zamiast używać UITableViewController.
Mark Leonard,
2
Widzę to zachowanie tylko wtedy, gdy ustawiony navigationBar.isTranslucentna false, inaczej to działa dobrze.
b_ray
5
Wygląda na to, że jest to błąd w iOS11 GM, proszę oszukać ten raport o błędzie, aby ten problem zwrócił uwagę Apple: openradar.appspot.com/34465226
b_ray
1
Wydaje się, że ten problem został rozwiązany w iOS 11.2 beta. Nie ustawiłbym contentInsetAdjustmentBehavior na nigdy, ponieważ przerywa przewijanie iPhone'a X, nie dając dopełnienia u dołu ekranu. Dolna część widoku zawartości pozostaje pod „przyciskiem” ekranu głównego iPhone'a X.
batu

Odpowiedzi:

150

Wynika to z nowej właściwości UIScrollView's (UITableView jest podklasą UIScrollview)contentInsetAdjustmentBehavior , która jest .automaticdomyślnie ustawiona na .

Możesz zastąpić to zachowanie następującym fragmentem kodu w viewDidLoad wszystkich kontrolerów, których dotyczy problem:

    tableView.contentInsetAdjustmentBehavior = .never

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

Maggy Hillen
źródło
2
ten komentarz dotyczący definicji UIScrollViewContentInsetAdjustmentBehavior.automatic mówi: „... w celu zapewnienia zgodności z poprzednimi wersjami dostosuje także górną i dolną zawartość elementuInset, gdy widok przewijania należy do kontrolera widoku z automaticAdjustsScrollViewInsets = YES wewnątrz kontrolera nawigacji, niezależnie od tego, czy przewijanie widok można przewijać ”. Moja teoria mówi, że na contentInset paska nawigacyjnego wpływa ustawienie obrazu tła, który jest następnie dynamicznie dostosowywany.
Maggy Hillen
8
Możesz to również zrobić za pomocą storyboardu. Inspektor rozmiaru -> Wstawki zawartości -> Ustaw „Nigdy”.
Woongbi Kim
4
Jeśli zawartość wykracza poza pasek kart, wyłączenie tableView.contentInsetAdjustmentBehaviorprzerywałoby wstawki.
kean
4
Wyłączenie tej opcji spowoduje również umieszczenie wskaźnika przewijania za (górnym gadżetem) na iPhonie X w orientacji poziomej. Celem tego zachowania jest dostosowanie obszaru zawartości widoków przewijania, tak aby były widoczne na ekranach, które nie są prostokątne. Myślę, że możemy to obecnie zobaczyć tylko na iPhone X Sim.
PhoneyDeveloper
8
Ten problem był spowodowany błędem w iOS 11, gdzie safeAreaInsets widoku kontrolera widoku były ustawione nieprawidłowo podczas przejścia nawigacji, co powinno zostać naprawione w iOS 11.2. Ustawienie wartości contentInsetAdjustmentBehaviorna .nevernie jest dobrym obejściem, ponieważ prawdopodobnie będzie miało inne niepożądane skutki uboczne. Jeśli używasz obejścia, pamiętaj, aby usunąć je dla wersji iOS> = 11.2.
smileyborg
23

Oprócz odpowiedzi Maggy

CEL C

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

Ten problem był spowodowany błędem w iOS 11, gdzie safeAreaInsetswidok kontrolera widoku był ustawiony nieprawidłowo podczas przejścia nawigacji, co powinno zostać naprawione w iOS 11.2. Ustawienie wartości contentInsetAdjustmentBehaviorna .nevernie jest dobrym obejściem, ponieważ prawdopodobnie będzie miało inne niepożądane skutki uboczne. Jeśli używasz obejścia, pamiętaj, aby usunąć je dla wersji iOS> = 11.2

- wspomina smileyborg (inżynier oprogramowania w Apple)

Lal Krishna
źródło
6

Możesz edytować to zachowanie jednocześnie w całej aplikacji, używając NSProxy w, na przykład didFinishLaunchingWithOptions:

if (@available(iOS 11.0, *)) {
      [UIScrollView appearance].contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
} 
BigDanceMouse
źródło
1
Sterownik ten system będzie przerwa, podobnie jak UIImagePickerController
Galway
6

Oto, jak udało mi się rozwiązać ten problem, jednocześnie pozwalając iOS 11 na automatyczne ustawianie wstawek . Używam UITableViewController.

  • Wybierz „Wydłuż krawędzie pod górnymi paskami” i „Wydłuż krawędzie pod nieprzezroczystymi paskami” w kontrolerze widoku w scenorysie (lub programowo ). Wstawki w obszarze bezpiecznym zapobiegną przechodzeniu widoku pod górną belką.
  • Zaznacz przycisk „Wstawki do bezpiecznego obszaru” w widoku tabeli w swoim storyboardzie. (lub tableView.insetsContentViewsToSafeArea = true) - To może nie być konieczne, ale tak właśnie zrobiłem.
  • Ustaw zachowanie dostosowania wstawki zawartości na „Przewijalne osie” (lub tableView.contentInsetAdjustmentBehavior = .scrollableAxes) - .alwaysmoże również działać, ale nie testowałem.

Jeszcze jedna rzecz do wypróbowania, jeśli wszystko inne zawiedzie:

Override viewSafeAreaInsetsDidChange UIViewController, aby widok tabeli wymusił ustawienie wstawek widoku przewijania na wstawki obszaru bezpiecznego. Dzieje się tak w połączeniu z ustawieniem „Nigdy” w odpowiedzi Maggy.

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

Uwaga: self.tableViewi self.viewpowinno być to samo dlaUITableViewController

Święty Mikołaj
źródło
Próbowałem prawie wszystkiego w tym wątku i to zadziałało. TableViewVC osadzone w TabBarVC. Dziękuję Ci!
Josh Wolff,
3

Wygląda to bardziej na błąd niż zamierzone zachowanie. Dzieje się tak, gdy pasek nawigacji nie jest półprzezroczysty lub gdy ustawiony jest obraz tła.

Jeśli ustawisz contentInsetAdjustmentBehavior na .never, wstawki zawartości nie będą ustawione poprawnie na iPhonie X, np. Zawartość trafi do dolnego obszaru, pod paskiem przewijania.

Konieczne jest zrobienie dwóch rzeczy:
1. zapobieganie animacji
scrollView w trybie push / pop 2. zachowanie zachowania .automatic, ponieważ jest to potrzebne dla iPhone'a X. Bez tego, np. W orientacji pionowej, zawartość znajdzie się poniżej dolnego paska przewijania.

Nowe proste rozwiązanie: w XIB: Po prostu dodaj nowy widok UIView na górze głównego widoku z górą, początkiem i końcem do superviewu i wysokością ustawioną na 0. Nie musisz łączyć go z innymi podglądami podrzędnymi ani z niczym.

Stare rozwiązanie:

Uwaga: Jeśli używasz UIScrollView w trybie poziomym, nadal nie ustawia on poprawnie poziomych wstawek (kolejny błąd?), Więc musisz przypiąć początkowe / końcowe scrollView do safeAreaInsets w IB.

Uwaga 2: Poniższe rozwiązanie ma również problem z tym, że jeśli tableView jest przewijane do dołu, a naciśniesz kontroler i wyskakujesz z powrotem, nie będzie już na dole.

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
    }
}
El Horrible
źródło
Co to jest ParentView?
PhoneyDeveloper
Główny widok twojego kontrolera widoku, zaktualizowałem odpowiedź. Dzięki.
El Horrible
Kiedy używam UITableViewController, tableView jest widokiem kontrolera widoku. Ponadto wypustki powinny być inne, gdy urządzenie jest obracane. Chcę korekty. Po prostu nie podoba mi się animacja, gdy pojawia się tableView po raz pierwszy.
PhoneyDeveloper
W twoim przypadku, ponieważ UITableView jest widokiem kontrolera, powinien mieć safeAreaInsets.bottom = 34 (portrait), więc możesz po prostu ustawić tableView.contentInset = tableView.safeAreaInsets.
El Horrible
Na mnie to nie działa. W viewDidLoad wszystkie wstawki są zerowe. Jeśli spróbuję użyć tego kodu w viewWillLayoutSubviews, wskaźnik przewijania zostanie wstawiony, ale sam tableView będzie mógł przewijać w poziomie. Jeśli po prostu spojrzę na wstawki w viewWillLayoutSubviews bez wyłączania regulacji, dolna część modifiedContentInset zmieni się na 21 i to wszystko wydaje się zmieniać.
PhoneyDeveloper
3

Mogę odtworzyć błąd w iOS 11.1, ale wygląda na to, że błąd został naprawiony od iOS 11.2. Zobacz http://openradar.appspot.com/34465226

Linda
źródło
tak, naprawiono na iOS 11.2
Richie Hyatt
2

upewnij się, że wraz z powyższym kodem dodaj dodatkowy kod w następujący sposób. To rozwiązało problem

override func viewDidLayoutSubviews() { 
     super.viewDidLayoutSubviews() 
     tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) // print(thisUISBTV.adjustedContentInset) 
}
Basavaraj Kalaghatagi
źródło
Samo to rozwiązało mój problem !! Dziękuję Ci. Zmarnowałem zbyt wiele godzin w tej sprawie!
Murat Yasar
2

Również jeśli używasz paska kart, dolna wstawka zawartości widoku kolekcji będzie wynosić zero. W tym celu umieść poniższy kod w viewDidAppear:

if #available(iOS 11, *) {
    tableView.contentInset = self.collectionView.safeAreaInsets
}
hasankoza
źródło
2

W moim przypadku to zadziałało (umieść to w viewDidLoad):

self.navigationController.navigationBar.translucent = YES;
nemissm
źródło
1

Usuwanie dodatkowej przestrzeni na górze collectionViewlubtableView

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

Powyżej kodu collectionViewlub tableViewponiżej paska nawigacji.
Poniższy kod uniemożliwia przejście widoku kolekcji pod nawigację

    self.edgesForExtendedLayout = UIRectEdge.bottom

ale uwielbiam używać poniższej logiki i kodu dla UICollectionView

Wartości odsadzenia krawędzi są stosowane do prostokąta w celu zmniejszenia lub rozszerzenia obszaru reprezentowanego przez ten prostokąt. Zazwyczaj podczas układania widoku używa się wstawek krawędzi w celu zmodyfikowania ramki widoku. Wartości dodatnie powodują, że ramka jest wstawiana (lub zmniejszana) o określoną wielkość. Ujemne wartości powodują, że ramka jest rozpoczynana (lub rozszerzana) o określoną wielkość.

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

Najlepszy sposób na UICollectionView

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        return UIEdgeInsets(top: -30, left: 0, bottom: 0, right: 0)
}
Nazmul Hasan
źródło
0

Usuń ten kod działa dla mnie

self.edgesForExtendedLayout = UIRectEdgeNone
weiminghuaa
źródło
0
  if #available(iOS 11, *) {
        self.edgesForExtendedLayout = UIRectEdge.bottom
  }

Używałem UISearchController z niestandardowymi wynikamiControllers, które mają widok tabeli. Wciśnięcie nowego kontrolera na kontroler wyników spowodowało przejście widoku tabeli do wyszukiwania.

Kod wymieniony powyżej całkowicie rozwiązał problem

Vitalii Shvetsov
źródło