Załóżmy, że dodałem więcej widoków w UIStackView, które można wyświetlić, jak mogę zrobić UIStackView
przewijanie?
Załóżmy, że dodałem więcej widoków w UIStackView, które można wyświetlić, jak mogę zrobić UIStackView
przewijanie?
Odpowiedzi:
W przypadku, gdy ktoś szuka rozwiązania bez kodu , stworzyłem przykład, aby zrobić to całkowicie w serii ujęć, używając Auto Layout.
Możesz go zdobyć z github .
Zasadniczo, aby odtworzyć przykład (do przewijania w pionie):
UIScrollView
i ustaw jego ograniczenia.UIStackView
doUIScrollView
Leading
, Trailing
, Top
i Bottom
powinny być równe tym, jakie odUIScrollView
Width
ograniczenie między UIStackView
i UIScrollView
.UIStackView
UIViews
doUIStackView
Wymiana Width
na Height
w kroku 4, a zestaw Axis
= Horizontal
w kroku 5, aby uzyskać poziomą UIStackView.
Przewodnik automatycznego układu Apple zawiera całą sekcję dotyczącą pracy z widokami przewijania . Niektóre istotne fragmenty:
- Przypnij górne, dolne, wiodące i końcowe krawędzie widoku zawartości do odpowiednich krawędzi widoku przewijania. Widok zawartości definiuje teraz obszar zawartości widoku przewijania.
- (Opcjonalnie) Aby wyłączyć przewijanie w poziomie, ustaw szerokość widoku zawartości równą szerokości widoku przewijania. Widok zawartości wypełnia teraz widok przewijania w poziomie.
- (Opcjonalnie) Aby wyłączyć przewijanie w pionie, ustaw wysokość widoku zawartości równą wysokości widoku przewijania. Widok zawartości wypełnia teraz widok przewijania w poziomie.
Ponadto:
Twój układ musi w pełni określać rozmiar widoku treści (z wyjątkiem przypadków określonych w krokach 5 i 6). … Gdy widok zawartości jest wyższy niż widok przewijania, widok przewijania umożliwia przewijanie w pionie. Gdy widok zawartości jest szerszy niż widok przewijania, widok przewijania umożliwia przewijanie w poziomie.
Podsumowując, widok zawartości widoku przewijania (w tym przypadku widok stosu) musi być przypięty do jego krawędzi i mieć ograniczoną szerokość i / lub wysokość. Oznacza to, że zawartość widoku stosu musi być ograniczona (bezpośrednio lub pośrednio) w kierunku (kierunkach), w którym pożądane jest przewijanie, co może na przykład oznaczać dodanie ograniczenia wysokości do każdego widoku wewnątrz widoku stosu przewijanego pionowo. Oto przykład, w jaki sposób zezwolić na pionowe przewijanie widoku przewijania zawierającego widok stosu:
// Pin the edges of the stack view to the edges of the scroll view that contains it
stackView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
// Set the width of the stack view to the width of the scroll view for vertical scrolling
stackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
Need constraints for: Y position or height.
Ten błąd znika tylko wtedy, gdy ustawisz ograniczenie szerokości i wysokości dla widoku stosu, co wyłącza przewijanie w pionie. Czy ten kod nadal działa dla Ciebie?
Ograniczenia w najczęściej głosowanej odpowiedzi tutaj działały dla mnie i wkleiłem obraz ograniczeń poniżej, utworzony w moim scenariuszu.
Dotknąłem dwóch kwestii, o których inni powinni wiedzieć:
Po dodaniu ograniczeń podobnych do tych w zaakceptowanej odpowiedzi otrzymałem czerwony błąd autolayout Need constraints for: X position or width
. Zostało to rozwiązane przez dodanie UILabel jako widoku podrzędnego widoku stosu.
Dodam podview programowo, więc pierwotnie nie miałem podviewów w serii ujęć. Aby pozbyć się błędów autoukładu, dodaj widok podrzędny do scenorysu, a następnie usuń go podczas ładowania przed dodaniem prawdziwych widoków podrzędnych i ograniczeń.
Początkowo próbowałem dodać UIButtons
do UIStackView. Przyciski i widoki zostaną załadowane, ale widok przewijania nie przewinie się . Zostało to rozwiązane poprzez dodanie UILabels
do widoku stosu zamiast przycisków. Korzystając z tych samych ograniczeń, ta hierarchia widoku ze zwojami UILabels przewija się, ale UIButtons nie.
Jestem zdezorientowany tym problemem, ponieważ UIButtons nie wydają się mieć IntrinsicContentSize (używany przez widok stosu). Jeśli ktoś wie, dlaczego przyciski nie działają, chciałbym wiedzieć, dlaczego.
Oto moja hierarchia widoku i ograniczenia w celach informacyjnych:
Jak mówi Eik UIStackView
i UIScrollView
bawcie się dobrze, patrz tutaj .
Kluczem jest to, że UIStackView
obsługuje zmienną wysokość / szerokość dla różnych treści, a UIScrollView
następnie dobrze wykonuje przewijanie / odbijanie tej zawartości:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
scrollView.contentSize = CGSize(width: stackView.frame.width, height: stackView.frame.height)
}
Chciałem zrobić to samo i natknąłem się na ten znakomity post. Jeśli chcesz to zrobić programowo przy użyciu interfejsu API kotwicy, jest to odpowiedni sposób.
Podsumowując, umieść swoją UIStackView
w swoim UIScrollView
i ustaw ograniczenia kotwicy, UIStackView
aby pasowały do UIScrollView
:
stackView.leadingAnchor.constraintEqualToAnchor(scrollView.leadingAnchor).active = true
stackView.trailingAnchor.constraintEqualToAnchor(scrollView.trailingAnchor).active = true
stackView.bottomAnchor.constraintEqualToAnchor(scrollView.bottomAnchor).active = true
stackView.topAnchor.constraintEqualToAnchor(scrollView.topAnchor).active = true
stackView.widthAnchor.constraintEqualToAnchor(scrollView.widthAnchor).active = true
Miej pustą scenę pełnoekranową
Dodaj widok przewijania. Przeciągnij z wciśniętym klawiszem Control z widoku przewijania do widoku podstawowego , dodaj lewy-prawy-górny-dolny, wszystkie zero.
Dodaj widok stosu w widoku przewijania. Przeciągnij z wciśniętym klawiszem Control z widoku stosu do widoku przewijania , dodaj lewy-prawy-górny-dolny, wszystkie zero.
Umieść dwie lub trzy etykiety w widoku stosu.
Dla jasności zmień kolor tła etykiety na czerwony. Ustaw wysokość etykiety na 100.
Teraz ustaw szerokość każdego UILabel:
Nieoczekiwanie przeciągnij z wciśniętym klawiszem Control UILabel
do widoku przewijania, a nie do stosu i wybierz równe szerokości .
Powtarzać:
To takie proste. To jest sekret.
Tajna wskazówka - błąd Apple:
Nie będzie działać tylko z jednym przedmiotem! Dodaj kilka etykiet, aby demo działało.
Jesteś skończony.
Wskazówka: musisz dodać wysokość do każdego nowego elementu . Każdy element w dowolnym przewijanym widoku stosu musi mieć rzeczywisty rozmiar (na przykład etykietę) lub musi zawierać wyraźne ograniczenie wysokości.
Alternatywne podejście:
Powyżej: nieoczekiwanie, ustaw szerokość UILabels na szerokość widoku przewijania (nie widoku stosu).
Na przemian...
Masz więc dwie opcje:
lub
Żeby było jasne, wykonaj JEDNĄ z tych metod, NIE rób obu.
Do przewijania w poziomie. Najpierw utwórz a UIStackView
i a UIScrollView
i dodaj je do swojego widoku w następujący sposób:
let scrollView = UIScrollView()
let stackView = UIStackView()
scrollView.addSubview(stackView)
view.addSubview(scrollView)
Pamiętaj, aby ustawić translatesAutoresizingMaskIntoConstraints
opcję false
na UIStackView
i UIScrollView
:
stackView.translatesAutoresizingMaskIntoConstraints = false
scrollView.translatesAutoresizingMaskIntoConstraints = false
Aby wszystko działało, kotwice końcowe, wiodące, górne i dolne UIStackView
powinny być równe UIScrollView
kotwicom:
stackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
stackView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
Ale szerokość kotwicy UIStackView
moszczu jest równa lub większa niż szerokość UIScrollView
kotwicy:
stackView.widthAnchor.constraint(greaterThanOrEqualTo: scrollView.widthAnchor).isActive = true
Teraz zakotwicz swoje UIScrollView
, na przykład:
scrollView.heightAnchor.constraint(equalToConstant: 80).isActive = true
scrollView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo:view.safeAreaLayoutGuide.bottomAnchor).isActive = true
scrollView.leadingAnchor.constraint(equalTo:view.leadingAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo:view.trailingAnchor).isActive = true
Następnie sugeruję wypróbowanie następujących ustawień UIStackView
wyrównywania i dystrybucji:
topicStackView.axis = .horizontal
topicStackView.distribution = .equalCentering
topicStackView.alignment = .center
topicStackView.spacing = 10
Na koniec musisz użyć tej addArrangedSubview:
metody, aby dodać widoki podrzędne do swojego UIStackView.
Jedną dodatkową funkcją, która może okazać się przydatna, jest to, że ponieważ UIStackView
jest ona trzymana w środku, UIScrollView
masz teraz dostęp do wstawek tekstowych, aby wyglądać nieco ładniej.
let inset:CGFloat = 20
scrollView.contentInset.left = inset
scrollView.contentInset.right = inset
// remember if you're using insets then reduce the width of your stack view to match
stackView.widthAnchor.constraint(greaterThanOrEqualTo: topicScrollView.widthAnchor, constant: -inset*2).isActive = true
Po prostu dodaj to do viewdidload
:
let insets = UIEdgeInsetsMake(20.0, 0.0, 0.0, 0.0)
scrollVIew.contentInset = insets
scrollVIew.scrollIndicatorInsets = insets
Możesz wypróbować ScrollableStackView : https://github.com/gurhub/ScrollableStackView
Jest to biblioteka zgodna z Objective-C i Swift. Jest dostępny za pośrednictwem CocoaPods .
Przykładowy kod (Swift)
import ScrollableStackView
var scrollable = ScrollableStackView(frame: view.frame)
view.addSubview(scrollable)
// add your views with
let rectangle = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 55))
rectangle.backgroundColor = UIColor.blue
scrollable.stackView.addArrangedSubview(rectangle)
// ...
Przykładowy kod (Objective-C)
@import ScrollableStackView
ScrollableStackView *scrollable = [[ScrollableStackView alloc] initWithFrame:self.view.frame];
scrollable.stackView.distribution = UIStackViewDistributionFillProportionally;
scrollable.stackView.alignment = UIStackViewAlignmentCenter;
scrollable.stackView.axis = UILayoutConstraintAxisVertical;
[self.view addSubview:scrollable];
UIView *rectangle = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 55)];
[rectangle setBackgroundColor:[UIColor blueColor]];
// add your views with
[scrollable.stackView addArrangedSubview:rectangle];
// ...
Jeśli masz ograniczenie do wyśrodkowania widoku stosu pionowo w widoku przewijania, po prostu go usuń.
Przykład pionowego widoku stosu / widoku przewijania (przy użyciu opcji EasyPeasy
autolayout):
let scrollView = UIScrollView()
self.view.addSubview(scrollView)
scrollView <- [
Edges(),
Width().like(self.view)
]
let stackView = UIStackView(arrangedSubviews: yourSubviews)
stackView.axis = .vertical
stackView.distribution = .fill
stackView.spacing = 10
scrollView.addSubview(stackView)
stackView <- [
Edges(),
Width().like(self.view)
]
Upewnij się tylko, że wysokość każdego widoku jest zdefiniowana!
Przede wszystkim zaprojektuj swój widok, najlepiej w coś takiego jak Szkic lub dowiedz się, czego chcesz jako przewijanej zawartości.
Następnie ustaw dowolny kontroler widoku (wybierz z Inspektora atrybutów) i ustaw wysokość i szerokość zgodnie z wewnętrznym rozmiarem zawartości twojego widoku (do wyboru z Inspektora rozmiarów).
Po tym w kontrolerze widoku umieść widok przewijania i jest to logika, która, jak się okazało, działa prawie cały czas w iOS (może wymagać przejrzenia dokumentacji tej klasy widoku, którą można uzyskać za pomocą polecenia + kliknięcie na ta klasa lub przez googling)
Jeśli pracujesz z dwoma lub więcej widokami, najpierw zacznij od widoku, który został wprowadzony wcześniej lub jest bardziej prymitywny, a następnie przejdź do widoku, który został wprowadzony później lub jest bardziej nowoczesny. Więc tutaj, ponieważ najpierw wprowadzono widok przewijania, zacznij od widoku przewijania, a następnie przejdź do widoku stosu. Tutaj ustaw zerowane ograniczenia widoku przewijania we wszystkich kierunkach względem jego super widoku. Umieść wszystkie swoje widoki w tym widoku przewijania, a następnie umieść je w widoku stosu.
Podczas pracy z widokiem stosu
Najpierw zacznij od podstaw (podejście do dołu), tj. Jeśli masz w widoku etykiety, pola tekstowe i obrazy, a następnie najpierw ułóż te widoki (wewnątrz widoku przewijania), a następnie umieść je w widoku stosu.
Następnie dostosuj właściwość widoku stosu. Jeśli pożądany widok nadal nie został osiągnięty, użyj innego widoku stosu.
Po utworzeniu widoku umieść ograniczenie między widokiem stosu macierzystego a widokiem przewijania, a widok stosu potomnego z wiązaniem z widokiem stosu macierzystego. Mam nadzieję, że do tego czasu powinno działać dobrze lub możesz otrzymać ostrzeżenie od Xcode podające sugestie, przeczytaj to, co mówi i zaimplementuj je. Mam nadzieję, że teraz powinieneś mieć widok roboczy zgodny ze swoimi oczekiwaniami :).
W przypadku zagnieżdżonego lub pojedynczego widoku stosu należy przewijać widok przewijania w widoku głównym. Widok głównego stosu wewnątrz widoku przewijania musi mieć tę samą szerokość. [Mój widok przewijania jest poniżej widoku, zignoruj go]
Ustaw równe ograniczenie szerokości między UIStackView i UIScrollView.
Jeśli ktoś szuka widoku przewijanego w poziomie
func createHorizontalStackViewsWithScroll() {
self.view.addSubview(stackScrollView)
stackScrollView.translatesAutoresizingMaskIntoConstraints = false
stackScrollView.heightAnchor.constraint(equalToConstant: 85).isActive = true
stackScrollView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
stackScrollView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
stackScrollView.bottomAnchor.constraint(equalTo: visualEffectViews.topAnchor).isActive = true
stackScrollView.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.topAnchor.constraint(equalTo: stackScrollView.topAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: stackScrollView.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: stackScrollView.trailingAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: stackScrollView.bottomAnchor).isActive = true
stackView.heightAnchor.constraint(equalTo: stackScrollView.heightAnchor).isActive = true
stackView.distribution = .equalSpacing
stackView.spacing = 5
stackView.axis = .horizontal
stackView.alignment = .fill
for i in 0 ..< images.count {
let photoView = UIButton.init(frame: CGRect(x: 0, y: 0, width: 85, height: 85))
// set button image
photoView.translatesAutoresizingMaskIntoConstraints = false
photoView.heightAnchor.constraint(equalToConstant: photoView.frame.height).isActive = true
photoView.widthAnchor.constraint(equalToConstant: photoView.frame.width).isActive = true
stackView.addArrangedSubview(photoView)
}
stackView.setNeedsLayout()
}
Umieść przewijany widok na scenie i zmień go tak, aby wypełnił scenę. Następnie umieść widok stosu w widoku przewijania i umieść przycisk dodawania elementu w widoku stosu. Jak tylko wszystko będzie na swoim miejscu, ustaw następujące ograniczenia:
Scroll View.Leading = Superview.LeadingMargin
Scroll View.Trailing = Superview.TrailingMargin
Scroll View.Top = Superview.TopMargin
Bottom Layout Guide.Top = Scroll View.Bottom + 20.0
Stack View.Leading = Scroll View.Leading
Stack View.Trailing = Scroll View.Trailing
Stack View.Top = Scroll View.Top
Stack View.Bottom = Scroll View.Bottom
Stack View.Width = Scroll View.Width
kod: Stack View.Width = Scroll View.Width
jest kluczem.
Dodanie nowej perspektywy dla macOS Catalyst. Ponieważ aplikacje macOS obsługują zmianę rozmiaru okna, możliwe jest, że UIStackView
zmienisz stan z niekodowalnego na przewijalny lub odwrotnie. Są tu dwie subtelne rzeczy:
UIStackView
jest zaprojektowany tak, aby pasował do wszystkich obszarów, w których może.UIScrollView
spróbuje zmienić rozmiar swoich granic, aby uwzględnić nowo uzyskany / utracony obszar pod paskiem nawigacji (lub paskiem narzędzi w przypadku aplikacji macOS).To niestety stworzy nieskończoną pętlę. Nie jestem zbytnio zaznajomiony z tym UIScrollView
i jego adjustedContentInset
, ale z logu w jego layoutSubviews
metodzie widzę następujące zachowanie:
UIScrollView
próbuje zmniejszyć swoje granice (ponieważ nie ma potrzeby obszaru pod paskiem narzędzi).UIStackView
następuje.UIScrollView
jest niezadowolony i postanawia przywrócić do większych granic. To wydaje mi się bardzo dziwne, ponieważ to, co widzę w dzienniku, jest takie UIScrollView.bounds.height == UIStackView.bounds.height
.UIStackView
następuje.Wydaje mi się, że dwa kroki rozwiązałyby ten problem:
UIStackView.top
do UIScrollView.topMargin
.contentInsetAdjustmentBehavior
na .never
.Mam tu na myśli przewijany pionowo widok z rosnącym pionowo UIStackView
. W przypadku pary poziomej odpowiednio zmień kod.
Mam nadzieję, że pomoże każdemu w przyszłości. Nie mogłem znaleźć nikogo, kto wspominałby o tym w Internecie, a ustalenie, co się stało, kosztowało mnie sporo czasu.