Using AutoLayout to stack within two Columns of varying Heights

前端 未结 3 439
佛祖请我去吃肉
佛祖请我去吃肉 2021-01-02 04:06

Targetting iOS 8.1

I am using AutoLayout to lay out a number of Labels in a TableCell. Some of those Labels are optional and some can wrap their Text. They are split

3条回答
  •  不知归路
    2021-01-02 04:57

    I've accepted SwiftArchitect's answer however seeing as I was after a code based approach for a TableCell I will add a separate answer. Without his help I would not have been able to get this far.

    I am using MVVMCross and Xamarin iOS and my TableCell inherits from MvxTableViewCell

    Creation of SubViews

    From the ctor of the Cell I create all the necessary UILabels and turn off AutoResizingMasks by setting view.TranslatesAutoresizingMaskIntoConstraints = false

    At the same time I create two UIViews leftColumnContainer and rightColumnContainer. These again TranslatesAutoresizingMaskIntoConstraints set to false.

    The relevant labels are added as subviews to the leftColumnContainer and rightColumnContainer UIViews. The two containers are then added as SubViews to the TableCell's ContentView

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

    The UILabels are all data bound via an MVVMCross DelayBind call

    Setting Layout Constraints (UpdateConstraints())

    The layout of the TableCell is conditional on the data for the cell with 5 of the 8 labels being optional and 4 of the 8 needing to support wrapping of text

    The First thing I do is pin the Top and Left of the leftColumnContainer to the TableCell.ContentView. Then the Top and Right of the 'rightColumnContainer' to the TableCell.ContentView. The design requires the right column to be smaller than the left so this is done using scaling. I am using FluentLayout to apply these constraints

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

    The calls to ToLeftOf and ToRight of are laying the right edge of the Left Column and the left edge of the Right Column next to each other

    A key piece of the solution that came from SwiftArchitect was to set the height of the TableCell's ContentView to >= to the height of the leftColumnContainer and the rightColumnContainer. It wasn't so obvious how to do these with FluentLayout so they are "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 then constraint the top, left and right of the first label in each column to the column container. Here is an example of from the first label in the left column

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

    For each of the labels that are optional I first check the MVVMCross DataContext to see if they are visible. If they are visible similar constraints for Left, Top and Right are applied with the Top being constrained to the Bottom of the label above. If they are not visible the are removed from the View like so

    this.bodyText.RemoveFromSuperview();
    

    If you are wondering how these cells are going to work with iOSs Cell Reuse I will cover that next.

    If the label is going to be the last label in the column (this is dependant on the data) I apply the other key learning from SwiftArcthiect's answer

    Let [the columns] calculate their ideal height by adding a single height constraint to the bottom of the lowest label (leftColumn.bottom Equal lowestLeftLabel.bottom)

    Dealing with cell Reuse

    With such a complicated set of Constraints and many optional cells I did not want to have to reapply the constraints everytime the cell was reused with potentially different optional labels. To do this I am building and setting the reuse identifier at runtime.

    The TableSource inherits from MvxTableViewSource. In the overridden GetOrCreateCellFor I check for a specific reuseIdentifier (normal use) and if so call DequeueReusableCell however in this instance I defer to a routine encapsulated in the custom Cell class that knows how to be build a data specific id

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

    and the call to build the id

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

提交回复
热议问题