2015-07-27 32 views
11

targetting iOS 8.1Korzystanie autoLayout stos ciągu dwóch kolumn o różnej wysokości

używam autoLayout rozłożyć liczbę etykiet w TableCell. Niektóre z tych etykiet są opcjonalne, a niektóre mogą zawijać tekst. Są podzielone na dwie "kolumny", te kolumny są po prostu dwoma UIViews w ContentView ContentCell. Moje ograniczenia są stosowane programowo.

Druga aktualizacja

Bez SwiftArchitect's answer poniżej bym nie rozwiązano to i zaakceptowali jego odpowiedź. Jednak ponieważ kopalnia jest wszystko w kodzie, w niestandardowym tablecell, mam również dodał separate answer below

UPDATE

W próbie powstrzymania etykiety z rozciągania się do rozmiarów większych niż musieli być miałem poprzednio ustawiłem SetContentHuggingPriority i SetContentCompressionResistancePriority na 1000, ponieważ wierzyłem, że jest to odpowiednik słowa "Chcę, aby etykieta obejmowała zawartość na dokładnej wysokości i nie chcę, aby była kiedykolwiek skompresowana w pionie" To żądanie wyraźnie nie było być przestrzegane przez AutoLayout, jak widać w poniższych przykładach czerwonych i różowych.

this.notesLabel.SetContentHuggingPriority(1000, UILayoutConstraintAxis.Vertical); 
this.notesLabel.SetContentCompressionResistancePriority(1000, UILayoutConstraintAxis.Vertical); 

Usunąłem ustawienie tych priorytetów i etykiety nie są już zgniecione, co było moim pierwotnym problemem. Oczywiście teraz niektóre etykiety są rozciągane poza wysokość, którą muszą być.

  1. Dlaczego usunięcie priorytetów ucisku i kompresji rozwiązuje mój problem z numerem ?
  2. Jak mogę uzyskać tekst w czerwonym polu (czerwone pole, a nie część komórki dodanej później), aby nie rozszerzać bez powrotu do poprzedniego wydania?

enter image description here

Oto kilka screenów z czym nie wyglądać, gdy kompresja i priorytetów Hugging gdzie zestawie. Kolory tła są do debugowania

enter image description here

Ogólnym problemem było to, że Zawierające Zobacz'S (w kolorze fioletowym i czerwonym) zostały zaklejania się do mniejszej z dwóch. Jak widać na górze "Priorytet 3" jest wycinany, ponieważ pojemnik lewej kolumny nie musi być wyżej.

W tym następnym przykładzie nie ma etykiety Priority, ale EventDate jest przyciśnięty.

enter image description here

+0

Jakie są ograniczenia w widokach kontenerowych? Nawet jeśli masz 1000 setów, a widok zewnętrzny zdecydował, że musi być mniejszy, _ coś musi dać! Przechwytywanie hierarchii widoków i przeglądanie ograniczeń środowiska wykonawczego może dostarczyć potrzebnych informacji. – Ben

Odpowiedz

5

Następująca odpowiedź została napisana i przetestowana. Działa poprawnie na iPhonie & iPad, portret & krajobraz. Najwyższa kolumna wygrywa, a druga po prostu zabiera przestrzeń, której potrzebuje. Można go nawet zmodyfikować, aby w razie potrzeby ustawić obiekty w pionie. Dotyczy on pionowo obcinanych etykiet, a także skalowania dynamicznego.

enter image description here

rada Wstępne

  • Użyj Storyboard jeśli możesz. Możesz sprawdzić wszystkie swoje ograniczenia wizualnie za pomocą najnowocześniejszego GUI.
  • Czy nie majstrować przy przytulanie, kompresja, czy nawet UILabel wysokości: niech każda etykieta ma przestrzeń potrzebną pionowo, a tylko dodać górne i boczne kotwy
  • stosować dodatkowe widoki jak pojemniki do definiowania szerokości poszczególnych kolumn . Użyj numeru multiplier, aby uzyskać, powiedzmy, 2 trzecie i 1 trzecią.
  • Niech te poglądy obliczenia ich idealnej wysokości dodając jedną wysokość ograniczenie do dołu najniższej etykiecie (leftColumn.bottom Równe lowestLeftLabel.bottom)
  • nie należy dodawać bądź usuwać widoki dynamicznie; raczej ukryj je, aby zachować powiązane więzy.

Rozwiązanie Opis

Dla uproszczenia utworzeniu 2 subviews 1 dla każdej kolumny i umieszczony je obok siebie.Są zakotwiczone w górnej/lewej i górnej/prawej, ich szerokość jest obliczana, a ich wysokość pochodzi od ich treści (*).

Lewy i prawy subviews mają 1/2 multiplier, do którego dodałem constant z 2 pikseli marginesu. Etykiety wewnątrz tych dwóch kolumn są zakotwiczone w lewo i w prawo (leading space do kontenera i trailing space do kontenera) z marginesem 8 pikseli. Dzięki temu żadna etykieta nigdy nie krwawi poza kolumną.

  1. Należy wziąć pod uwagę, że wysokość Twojego UITableViewCell jest największą z dwóch wewnętrznych kolumn. Innymi słowy, containerView.height> = left.height i containerView.height> = right.height.
  2. Upewnij się, że nie usuwasz żadnej z niewidocznych etykiet. view.hidden nie będzie zakłócać twoich ograniczeń i właśnie tego chcesz.
  3. Zakotwicz każdą z nich po lewej i prawej stronie do kontenera, najwyżej do kontenera, ale każdy następny label.top powinien być zakotwiczony do .bottom jednego powyżej. W ten sposób twoja treść będzie przepływ. Możesz dodać marginesy, jeśli chcesz.

(*) Ostatnim kluczem jest powiązanie wysokości każdej kolumny z ograniczeniem na kolumnie, aby była równa .bottom najniższej etykiety dla tej kolumny. W powyższym przykładzie wyraźnie widać, że prawa kolumna z niebieskim tłem jest krótsza niż lewa.

enter image description here

Choć widzę, że chciał rozwiązanie w kodzie, tworzę przykład stosując Storyboard w czasie krótszym niż 15 minut. To nie jest tylko koncepcja, to faktyczna implementacja. Ma dokładnie 0 linii kodu i działa na wszystkich urządzeniach z systemem iOS. Nawiasem mówiąc, ma również 0 błędów.

Lista wszystkich ograniczeń

Zawiadomienie> = posypane tu i tam. Są kluczem do tego, aby twoje kolumny były niezależnie wysokie.

NSLayoutContraint są identyczne dla L i R.

enter image description here

enter image description here

Pobierz Storyboard here i szczegółowy artykuł there.

+0

Jestem świadomy mocy XEode IDE, ale prawie cała nasza aplikacja Xamarin.iOS jest zbudowana z kodu. Posiadam również pojemniki dla kolumn L i R. Usuwam niektóre widoki, ale to przed ustawianiem wiązań i to napędza identyfikator ponownego wykorzystania. Czy możesz wyjaśnić różnicę między ustawieniem Leading \ Tailing space na Left and Right Constraints. Myślę, że robię to, co sugerujesz w "Ostatecznym kluczu". Wrócę do naszego kodu. Dzięki –

+0

Czy możesz zrzuty ekranu właściwości ograniczenia w XCode dla tych "> =" ograniczeń. Jak widać na białym tle "w tle", nie mam już przyciętych etykiet i nie dotykam przytulania ani kompresji. Która część Twojego rozwiązania uniemożliwia etykietom zajmowanie tak dużej ilości miejsca? –

+0

"container.bottom = lowestLabel.bottom" właśnie wyglądało i robię odwrotnie, ustawiam "lowestLabel.bottom = container.bottom". Przejrzyj teraz –

0

Przede wszystkim nie powinniśmy bawić zawartości wit tulenie lub priorytetu kompresji, chyba że jest to sytuacja, w której trzeba zmienić bez opcja lewo. Domyślny stosunek 250: 750 podany przez jabłko będzie odpowiadał 90% scenariusza, jeśli układ automatyczny jest poprawnie skonfigurowany. Tylko w rzadkich przypadkach, gdy istnieje konflikt, aby silnik zmienił rozmiar widoku w oparciu o spełnione ograniczenia, powinniśmy zmienić priorytety przytulania/kompresji.

Twoim pierwotnym problemem jest zgniecenie etykiet. Etykiety domyślnie włączają wewnętrzny rozmiar treści systemu, w którym silnik domyślnie decyduje o szerokości i wysokości etykiety, w zależności od zawartości etykiet i rozmiaru tekstu. Jeśli więc zdecydujesz, że etykieta nie rozszerzy widoku, powinieneś ustawić końcowe ograniczenie z prawej strony etykiety do widoku. Zasadniczo zostanie ustawiona na właściwość "Equals", która nie będzie odpowiadać naszym wymaganiom, ponieważ nasza etykieta jest przekazywana na własność własną i nie powinniśmy podawać standardowej szerokości etykiety. Dlatego zamiast "Równa się", powinna to być właściwość "Większa lub równa" dla właściwości końcowej.

W swoim scenariuszu nie należy ustalać ograniczenia wysokości dla etykiet i włączać funkcji zawijania tekstu wzdłuż właściwości 'with number of line' dla dowolnej wymaganej etykiety z dwóch linii.

jesteś świadomy, że zawsze powinieneś wyświetlać "Żółtą etykietę", "zieloną etykietę" i "fioletową etykietę" w widoku z prawej strony, niezależnie od lewego widoku ma jedną lub dwie linie w "czerwonej etykiecie" .

Napraw więc wysokość statyczną komórki. W widoku z prawej strony, Ustal górne ograniczenie dla etykiety "pomarańczowej" i ograniczenie dolne dla etykiety "żółtej". Tak więc środkowa etykieta "czerwona" uzyska wyraźną wysokość, która może pomieścić jedną/dwie linie zgodnie z wymaganiem. I podaj wystarczające ograniczenia do widoku z prawej strony, aby spełnić twoje wymagania.

Jeśli to nie rozwiązuje problemu lub dyskusji na temat mojego rozwiązania, komentarz poniżej.

3

Zaakceptowałem SwiftArchitect's answer, ale widząc, jak byłem po podejście oparte na kod na TableCell dodaję osobną odpowiedź. Bez jego pomocy nie byłby w stanie dojść tak daleko.

Używam MVVMCross i Xamarin iOS i moją TableCell dziedziczy MvxTableViewCell

Tworzenie subviews

Z ctor komórki tworzę wszystkie niezbędne UILabels i wyłącz AutoResizingMasks ustawiając view.TranslatesAutoresizingMaskIntoConstraints = false

W tym samym czasie tworzę dwa UIViews leftColumnContainer i rightColumnContainer. Te ponownie TranslatesAutoresizingMaskIntoConstraints ustawione na false.

Odpowiednie etykiety są dodawane jako subviews do UIViews leftColumnContainer i rightColumnContainer. Oba pojemniki są następnie dodawane jako subviews do TableCell za contentView

this.ContentView.AddSubviews(this.leftColumnContainer, this.rightColumnContainer); 
this.ContentView.TranslatesAutoresizingMaskIntoConstraints = true; 

W UILabels są wszystkie dane związane poprzez wywołanie MVVMCross DelayBind

Ustawianie Ograniczenia Układ (UpdateConstraints())

układ TableCell jest zależna od danych dla komórki, z których 5 z 8 etykiet jest opcjonalnych, a 4 z 8 muszą obsługiwać zawijanie tekstu:

Pierwsza rzecz Robię to przypinam Górny i Lewy z leftColumnContainer do TableCell.ContentView. Następnie górny i prawy "rightColumnContainer" do TableCell.ContentView. Projekt wymaga, aby prawidłowa kolumna była mniejsza niż lewa, więc odbywa się to za pomocą skalowania. Używam FluentLayout do zastosowania tych ograniczeń

this.ContentView.AddConstraints(
       this.leftColumnContainer.AtTopOf(this.ContentView), 
       this.leftColumnContainer.AtLeftOf(this.ContentView, 3.0f), 
       this.leftColumnContainer.ToLeftOf(this.rightColumnContainer), 
       this.rightColumnContainer.AtTopOf(this.ContentView), 
       this.rightColumnContainer.ToRightOf(this.leftColumnContainer), 
       this.rightColumnContainer.AtRightOf(this.ContentView), 
       this.rightColumnContainer.WithRelativeWidth(this.ContentView, 0.35f)); 

połączenia do ToLeftOf i ToRight of którzy tworzą prawą krawędź lewej kolumnie i lewą krawędź prawej kolumnie obok siebie

Kluczowym kawałek Rozwiązaniem, które pochodziło od SwiftArchitect było ustawienie wysokości ContentView w TableCell na> = na wysokość leftColumnContainer i rightColumnContainer. To nie było tak oczywiste, jak zrobić to z FluentLayout więc są one „longhand”

this.ContentView.AddConstraint(
       NSLayoutConstraint.Create(this.ContentView, NSLayoutAttribute.Height, NSLayoutRelation.GreaterThanOrEqual, this.leftColumnContainer, NSLayoutAttribute.Height, 1.0f, 5.0f)); 

      this.ContentView.AddConstraint(
       NSLayoutConstraint.Create(this.ContentView, NSLayoutAttribute.Height, NSLayoutRelation.GreaterThanOrEqual, this.rightColumnContainer, NSLayoutAttribute.Height, 1.0f, 5.0f)); 

I wtedy ograniczenie górnej, lewej i prawej pierwszej etykiety w każdej kolumnie do kontenera kolumny. Oto przykład z pierwszej etykiety w lewej kolumnie

this.leftColumnContainer.AddConstraints(
       this.categoryLabel.AtTopOf(this.leftColumnContainer, CellPadding), 
       this.categoryLabel.AtRightOf(this.leftColumnContainer, CellPadding), 
       this.categoryLabel.AtLeftOf(this.leftColumnContainer, CellPadding)); 

Dla każdej z etykiet, które są opcjonalne ja najpierw sprawdzić MVVMCross DataContext aby sprawdzić, czy są one widoczne. Jeśli są widoczne podobne ograniczenia dla lewego, górnego i prawego są stosowane z górnym jest ograniczony do dolnej części etykiety powyżej. Jeśli nie są one widoczne są usuwane z widokiem jak tak

this.bodyText.RemoveFromSuperview(); 

Jeśli zastanawiasz się, w jaki sposób komórki te będą pracować z iOSs komórki Ponowne omówię że następny.

Jeśli etykieta będzie ostatnia etykieta w kolumnie (jest to zależne od danych) I zastosować inną naukę klucz od SwiftArcthiect za odpowiedź

Let [kolumny] obliczenia ich idealnej wysokości przez dodanie pojedynczego wysokość ograniczenia do dołu najniższej etykietę (leftColumn.bottom Równe lowestLeftLabel.bottom)

czynienia z komórką Ponowne

Przy tak skomplikowanym zestawie ograniczeń i wielu komórkach opcjonalnych nie chciałem ponownie stosować ograniczeń za każdym razem, gdy komórka była ponownie wykorzystywana z potencjalnie różnymi etykietami opcjonalnymi. W tym celu buduję i ustawiam identyfikator ponownego użycia w czasie wykonywania.

Źródło TableSource pochodzi z MvxTableViewSource. W zamienione na GetOrCreateCellFor sprawdzić zadaniu reuseIdentifier (normalnego użytkowania), a jeśli tak wezwanie DequeueReusableCell jednak w tym przypadku mogę odroczyć do rutynowych zawarta w niestandardowej klasy komórka, która wie, jak należy budować konkretny identyfikator danych

protected override UITableViewCell GetOrCreateCellFor(UITableView tableView, NSIndexPath indexPath, object item) 
     { 
      MvxTableViewCell cell; 

      if (this.reuseIdentifier != null) 
      { 
       cell = (MvxTableViewCell)tableView.DequeueReusableCell(this.reuseIdentifier);  
      } 
      else 
      { 
       // No single reuse identifier, defer to the cell for the identifer 
       string identifier = this.itemCell.GetCellIdentifier(item); 

       if (this.reuseIdentifiers.Contains(identifier) == false) 
       { 
        tableView.RegisterClassForCellReuse(this.tableCellType, identifier); 
        this.reuseIdentifiers.Add(identifier); 
       } 

       cell = (MvxTableViewCell)tableView.DequeueReusableCell(identifier);  
      } 

      return cell; 
     } 

i wezwanie do budowania tożsamości

public string GetCellIdentifier(object item) 
     { 
      StringBuilder cellIdentifier = new StringBuilder(); 

      var entry = item as EntryItemViewModelBase; 

      cellIdentifier.AppendFormat("notes{0}", entry.Notes.HasValue() ? "yes" : "no"); 
      cellIdentifier.AppendFormat("_body{0}", !entry.Body.Any() ? "no" : "yes"); 
      cellIdentifier.AppendFormat("_priority{0}", entry.Priority.HasValue() ? "yes" : "no"); 
      cellIdentifier.AppendFormat("_prop1{0}", entry.Prop1.HasValue() ? "yes" : "no"); 
      cellIdentifier.AppendFormat("_prop2{0}", entry.Prop2.HasValue() ? "yes" : "no"); 
      cellIdentifier.AppendFormat("_warningq{0}", !entry.IsWarningQualifier ? "no" : "yes"); 
      cellIdentifier.Append("_MEIC"); 

      return cellIdentifier.ToString(); 
     }