Creating Custom UITableViewCells programatically in iOS 11

FinHorsleyFinHorsley USMember ✭✭
public class CreateSpaceBaseCell : UITableViewCell
    {
        protected UILabel CellLeftTitlelabel;

        [Export("initWithFrame:")]
        public CreateSpaceBaseCell(CGRect frame) : base (frame)
        {

        }

        public CreateSpaceBaseCell(NSString cellId) : base (UITableViewCellStyle.Default, cellId)
        {
        }

        public CreateSpaceBaseCell(IntPtr handle) : base(handle)
        {
            SelectionStyle = UITableViewCellSelectionStyle.None;


            CellLeftTitlelabel = new UILabel
            {
                TextAlignment = UITextAlignment.Left,
                TranslatesAutoresizingMaskIntoConstraints = false,
                //BackgroundColor = UIColor.LightGray,
                Font = UIFont.BoldSystemFontOfSize(15),
                Text = "Default Space"
            };
        }

        public override void PrepareForReuse()
        {
            base.PrepareForReuse();
        }

        public override void LayoutSubviews()
        {
            base.LayoutSubviews();

            AddSubview(CellLeftTitlelabel);

            CellLeftTitlelabel.TopAnchor.ConstraintEqualTo(TopAnchor, 10).Active = true;
            CellLeftTitlelabel.LeftAnchor.ConstraintEqualTo(LeftAnchor, 10).Active = true;
            CellLeftTitlelabel.BottomAnchor.ConstraintEqualTo(BottomAnchor, -10).Active = true;
            CellLeftTitlelabel.WidthAnchor.ConstraintEqualTo(130).Active = true;
        }

        public void SetLeftTitleLabel(string labelTitle)
        {
            CellLeftTitlelabel.Text = labelTitle;
        }
    }

I have created this custom cell, although when i cell "SetLeftTitleLabel" I am getting a null reference error on CellLeftTitleLabel. Within the tableview source I am not using swift if(cell == null) , as the guidelines for iOS 11 state that this is no longer needed. The problem I have is that my initialisation logic is now in the IntPtr constructor, which is apparently unacceptable. What is the correct way to set up the cell in code (should I use the Draw method?), and dequeue it in the source. None of other constructors are being called as I am no longer creating a new instance of the class if the cell == null.

Best Answer

  • sonny_ppgsonny_ppg US ✭✭
    Accepted Answer

    If you register the cell for reuse then the dequeue function should automatically return a blank instance if a reused one isn't available and you wouldn't need that check for null.

    in the constructor call

    tableView.RegisterClassForCellReuse(typeof(cell), cellID);

    in the get cell event

    var cell = (cell)tableView.DequeueReusableCell (cellID);

Answers

  • TedRogersTedRogers USMember ✭✭✭✭

    @FinHorsley Are you registering this class for reuse?

    tableView.RegisterClassForCellReuse(typeof(ListDataTableCell), ListDataTableCell.Key);

    My constructor:

            protected ListDataTableCell(IntPtr handle) : base(handle)
            {
            }
    

    And the dequeue:

    var cell = tableView.DequeueReusableCell(ListDataTableCell.Key, indexPath) as ListDataTableCell;

  • FinHorsleyFinHorsley USMember ✭✭

    thanks for such as quick response! yeah i have registered the class for reuse, and have a constructor like the one you showed.

    var RecentCell = tableView.DequeueReusableCell(RecentCellId) as ListRoomRecentCell;
    
    if(RecentCell == null)
        {
            RecentCell = new ListRoomRecentCell()
        }
    

    do i need the RecentCell == null ?

    also, can i put my UI elements initialisation within

    protected ListDataTableCell(IntPtr handle) : base(handle)
    {
        CellLeftTitlelabel = new UILabel
                {
                    TextAlignment = UITextAlignment.Left,
                    TranslatesAutoresizingMaskIntoConstraints = false,
                    //BackgroundColor = UIColor.LightGray,
                    Font = UIFont.BoldSystemFontOfSize(15),
                    Text = "Default Space"
                };
    }
    

    and then set the constraints within LayoutSubviews() {} ?

  • TedRogersTedRogers USMember ✭✭✭✭

    I would create constraints wherever you are creating the subviews. In my case, my subviews are dependent on the data so I created them when I bind my data to my cell.

    public void BindDataToCell(List<NameValuePair> data, bool useLargerFontForPrimary)
    {
        //Console.WriteLine("ListDataTableCell:BindDataToCell");
        RemoveOldSubViews();
        int index = 0;
        NSMutableDictionary viewDict = new NSMutableDictionary();
        List<NSLayoutConstraint> constraints = new List<NSLayoutConstraint>();
    
        UIFont secondaryFont = UIFont.PreferredFootnote;
        UIFont primaryFont = useLargerFontForPrimary ? UIFont.PreferredSubheadline : secondaryFont;
        foreach (var kvp in data)
        {
            var indentLabel = new UILabel();
            indentLabel.TranslatesAutoresizingMaskIntoConstraints = false;
            indentLabel.SetContentCompressionResistancePriority(999, UILayoutConstraintAxis.Horizontal);
            indentLabel.Text = "";
            AddToContentView(indentLabel);
            string indentLabelName = "indentLabel" + index;
            viewDict.Add(new NSString(indentLabelName), indentLabel);
    
            var titleLabel = new UILabel();
            titleLabel.TranslatesAutoresizingMaskIntoConstraints = false;
            titleLabel.SetContentCompressionResistancePriority(999, UILayoutConstraintAxis.Horizontal);
            titleLabel.Text = kvp.Name;
            titleLabel.Font = index == 0 ? primaryFont : secondaryFont;
            AddToContentView(titleLabel);
            string titleViewName = "titleLabel" + index;
            viewDict.Add(new NSString(titleViewName), titleLabel);
    
            var valueLabel = new UILabel();
            valueLabel.TranslatesAutoresizingMaskIntoConstraints = false;
            valueLabel.Lines = 0;
            valueLabel.LineBreakMode = UILineBreakMode.WordWrap;
            valueLabel.Text = kvp.Value;
            valueLabel.Font = index == 0 ? primaryFont : secondaryFont;
            valueLabel.TextAlignment = UITextAlignment.Right;
            AddToContentView(valueLabel);
            string valueViewName = "valueLabel" + index;
            viewDict.Add(new NSString(valueViewName), valueLabel);
    
            int indent = (int)IndentationLevel * (int)IndentationWidth;
            //string horzFormat = string.Format("H:|-[{0}]-[{1}]-|", titleViewName, valueViewName);
            string horzFormat = string.Format("H:|-7-[{0}({1})]-[{2}]-(>=4)-[{3}]-15-|", indentLabelName, indent, titleViewName, valueViewName);
            var horzConstraints = NSLayoutConstraint.FromVisualFormat(horzFormat, NSLayoutFormatOptions.AlignAllTop, null, viewDict);
            constraints.AddRange(horzConstraints);
    
            ++index;
        }
        string vertFormat = "V:|-4-";
        for (int i = 0; i < data.Count; ++i)
        {
            if (i > 0)
            {
                vertFormat += "-0-";
            }
            vertFormat += string.Format("[{0}]", "valueLabel" + i);
    
        }
        vertFormat += "-4-|";
        var vertConstraints = NSLayoutConstraint.FromVisualFormat(vertFormat, 0, null, viewDict);
        constraints.AddRange(vertConstraints);
        ContentView.AddConstraints(constraints.ToArray());
    }
    
  • sonny_ppgsonny_ppg USMember, University ✭✭

    You need to add your cell UI elements to the ContentView of the cell in your constructor.

    protected ListDataTableCell(IntPtr handle) : base(handle)
    {
    CellLeftTitlelabel = new UILabel
    {
    TextAlignment = UITextAlignment.Left,
    TranslatesAutoresizingMaskIntoConstraints = false,
    //BackgroundColor = UIColor.LightGray,
    Font = UIFont.BoldSystemFontOfSize(15),
    Text = "Default Space"
    };

    ContentView.Add (CellLeftTitlelabel);
    }

  • FinHorsleyFinHorsley USMember ✭✭
    Whats the difference between adding the UI Elements to the view using AddSubview(), and adding them using ContentView.AddSubview() ?
  • TedRogersTedRogers USMember ✭✭✭✭

    @FinHorsley ah, well ContentView is where your subviews are supposed to go. I think it is so that your custom content will play nice with the accessory views.

  • FinHorsleyFinHorsley USMember ✭✭

    One last thing, do I need to use

    if(cell == null)
    {
        cell = new TableViewCell();
    }
    

    (I think this is no longer needed as of iOS 11 according to the Apple Dev Docs)

  • sonny_ppgsonny_ppg USMember, University ✭✭
    Accepted Answer

    If you register the cell for reuse then the dequeue function should automatically return a blank instance if a reused one isn't available and you wouldn't need that check for null.

    in the constructor call

    tableView.RegisterClassForCellReuse(typeof(cell), cellID);

    in the get cell event

    var cell = (cell)tableView.DequeueReusableCell (cellID);

  • FinHorsleyFinHorsley USMember ✭✭

    thanks thats brilliant!

Sign In or Register to comment.