2

I am developing a UITableViewCell that starts as a xib, has views added to it programmatically, and has a dynamically sized height. However, it looks like when adding the programatic views with constraints, it is conflicting with the auto-resize constraint initially applied to the xib, and causing issues. Please see below:

Dequeuing my cells:

//Table Delegate/Datasource
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

    var cell:S360SSessionMatchTableCell? = tableView.dequeueReusableCellWithIdentifier(XIBFiles.SESSIONMATCHTABLECELL + String(indexPath.row)) as? S360SSessionMatchTableCell

    if ((cell == nil)){
        tableView.registerNib(UINib(nibName: XIBFiles.SESSIONMATCHTABLECELL, bundle: nil), forCellReuseIdentifier: XIBFiles.SESSIONMATCHTABLECELL + String(indexPath.row))
        cell = tableView.dequeueReusableCellWithIdentifier(XIBFiles.SESSIONMATCHTABLECELL + String(indexPath.row)) as? S360SSessionMatchTableCell
    }

    cell!.setupEvents(sessionMatches[indexPath.row]["sessions"]! as! [[String:String]])

    return cell!
}

Setup Events Method in Custom UITableViewCell:

func setupEvents(events:[[String:String]]){

    //Set up start and end times
    self.startTimeLbl.text = events[0]["startTime"]!
    self.endTimeLbl.text = events[events.count - 1]["endTime"]!

    //Set up events
    var pastEventView:S360SScheduledEventView? = nil
    var pastEvent:[String:String]? = nil
    for (index, event) in events.enumerate(){
        var topAnchor:NSLayoutConstraint!

        //Create event view
        let eventView:S360SScheduledEventView = NSBundle.mainBundle().loadNibNamed(XIBFiles.SCHEDULEDEVENTVIEW, owner: self, options: nil)[0] as! S360SScheduledEventView

        //Deal with first view added
        if pastEvent == nil{

            //Top anchor setup for first view
            topAnchor = NSLayoutConstraint(item: eventView, attribute: .Top, relatedBy: .Equal, toItem: toLbl, attribute: .Bottom, multiplier: 1, constant: 10)
        }
        else{

            //Check for a break
            let timeFormatter:NSDateFormatter = NSDateFormatter()
            timeFormatter.dateFormat = "hh:mm a"
            let startTime = timeFormatter.dateFromString(pastEvent!["endTime"]!)
            let endTime = timeFormatter.dateFromString(event["startTime"]!)
            if startTime != endTime {

                //Create break view
                let breakView = NSBundle.mainBundle().loadNibNamed(XIBFiles.SCHEDULEDBREAKVIEW, owner: self, options: nil)[0] as! S360SScheduledBreakView

                //Setup breakview constraints
                let bTopAnchor = NSLayoutConstraint(item: breakView, attribute: .Top, relatedBy: .Equal, toItem: pastEventView, attribute: .Bottom, multiplier: 1, constant: 0)
                let bLeftAnchor = NSLayoutConstraint(item: breakView, attribute: .Leading, relatedBy: .Equal, toItem: self.contentView, attribute: .LeadingMargin, multiplier: 1, constant: 0)
                let bRightAnchor = NSLayoutConstraint(item: breakView, attribute: .Trailing, relatedBy: .Equal, toItem: self.contentView, attribute: .TrailingMargin, multiplier: 1, constant: 0)
                let bHeightAnchor = NSLayoutConstraint(item: breakView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 30)

                //Add break view and constraints
                self.addSubview(breakView)
                self.addConstraints([bTopAnchor, bLeftAnchor, bRightAnchor, bHeightAnchor])

                //Top anchor setup for subsequent view
                topAnchor = NSLayoutConstraint(item: eventView, attribute: .Top, relatedBy: .Equal, toItem: breakView, attribute: .Bottom, multiplier: 1, constant: 0)
            }
            else{

                //Top anchor setup for subsequent views
                topAnchor = NSLayoutConstraint(item: eventView, attribute: .Top, relatedBy: .Equal, toItem: pastEventView, attribute: .Bottom, multiplier: 1, constant: 0)
            }

        }

        //Setup other anchors
        let leftAnchor = NSLayoutConstraint(item: eventView, attribute: .Leading, relatedBy: .Equal, toItem: self.contentView, attribute: .LeadingMargin, multiplier: 1, constant: 0)
        let rightAnchor = NSLayoutConstraint(item: eventView, attribute: .Trailing, relatedBy: .Equal, toItem: self.contentView, attribute: .TrailingMargin, multiplier: 1, constant: 0)
        let heightAnchor = NSLayoutConstraint(item: eventView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 60)

        //Setup event view
        eventView.iconImg.image = Images.get_event_image(event["title"]!)
        eventView.titleLbl.text = event["title"]!
        eventView.courtLbl.text = "court" + event["court"]!
        eventView.timeLbl.text = event["startTime"]! + " to " + event["endTime"]!

        //Add event view and constraints
        self.addSubview(eventView)
        self.addConstraints([topAnchor, leftAnchor, rightAnchor, heightAnchor])

        //Prepare for next iteration
        pastEventView = eventView
        pastEvent = event

        //Set up last cell with bottom bound
        if index == events.count - 1 {
            let bottomAnchor = NSLayoutConstraint(item: eventView, attribute: .Bottom, relatedBy: .Equal, toItem: self.contentView, attribute: .BottomMargin, multiplier: 1, constant: 0)
            self.addConstraint(bottomAnchor)
        }

    }
}

Constraints in xib: enter image description here

This is the error I get (pasted once, but it occurs for each cell):

   Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.
2016-07-05 15:13:01.654 Shoot360 Scheduler[32779:642808] Unable to simultaneously satisfy constraints.
    Probably at least one of the constraints in the following list is one you don't want. 
    Try this: 
        (1) look at each constraint and try to figure out which you don't expect; 
        (2) find the code that added the unwanted constraint or constraints and fix it. 
    (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 
(
    "<NSAutoresizingMaskLayoutConstraint:0x7fedd85d5590 h=--& v=--& V:[UITableViewCellContentView:0x7fedda431120(44)]>",
    "<NSLayoutConstraint:0x7fedda43a7e0 V:[Shoot360_Scheduler.S360SScheduledEventView:0x7fedda438b20(60)]>",
    "<NSLayoutConstraint:0x7fedda436590 UITableViewCellContentView:0x7fedda431120.topMargin == UILabel:0x7fedda4312a0'10:00 AM'.top - 15>",
    "<NSLayoutConstraint:0x7fedda436630 UILabel:0x7fedda431c00'to'.top == UILabel:0x7fedda4312a0'10:00 AM'.top>",
    "<NSLayoutConstraint:0x7fedda433b60 V:[UILabel:0x7fedda431c00'to']-(10)-[Shoot360_Scheduler.S360SScheduledEventView:0x7fedda438b20]>",
    "<NSLayoutConstraint:0x7fedda445910 V:[Shoot360_Scheduler.S360SScheduledEventView:0x7fedda4443f0(60)]>",
    "<NSLayoutConstraint:0x7fedda448310 V:[Shoot360_Scheduler.S360SScheduledEventView:0x7fedda438b20]-(0)-[Shoot360_Scheduler.S360SScheduledEventView:0x7fedda4443f0]>",
    "<NSLayoutConstraint:0x7fedda449a00 V:[Shoot360_Scheduler.S360SScheduledEventView:0x7fedda448540(60)]>",
    "<NSLayoutConstraint:0x7fedda4479e0 V:[Shoot360_Scheduler.S360SScheduledEventView:0x7fedda4443f0]-(0)-[Shoot360_Scheduler.S360SScheduledEventView:0x7fedda448540]>",
    "<NSLayoutConstraint:0x7fedda44a100 Shoot360_Scheduler.S360SScheduledEventView:0x7fedda448540.bottom == UITableViewCellContentView:0x7fedda431120.bottomMargin>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x7fedda436590 UITableViewCellContentView:0x7fedda431120.topMargin == UILabel:0x7fedda4312a0'10:00 AM'.top - 15>

Row height is being set to dynamic:

override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()

        //Styling
        showAllBtn.layer.cornerRadius = Numbers.CORNERRADIUS

        sessionsTbl.rowHeight = UITableViewAutomaticDimension
        sessionsTbl.estimatedRowHeight = 500
        sessionsTbl.layer.borderColor = Colors.REALLIGHTGREY.CGColor
        sessionsTbl.layer.borderWidth = Numbers.BORDERREG
        sessionsTbl.layer.cornerRadius = Numbers.CORNERRADIUS
        sessionsTbl.separatorStyle = UITableViewCellSeparatorStyle.None
    }

 func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
        return UITableViewAutomaticDimension
    }
steventnorris
  • 5,656
  • 23
  • 93
  • 174
  • The problem is that your table has row height set to `44pt` not to dynamic height. – Sulthan Jul 05 '16 at 20:36
  • Views which are created programatically automatically adopt layout constraints from the auto resizing mask, which can conflict with any constraints you add. Disable this on all the views by setting `someView.translatesAutoresizingMaskIntoConstraints = false`. – Luke Van In Jul 12 '16 at 09:55
  • Another potential issue with dynamic sizing is that all of the constraints in the horizontal direction need to be constant. For example, your text labels should only resize in one direction. Either use single line label sizing horizontally with fixed height, or multi-line labels sizing vertically with fixed width. – Luke Van In Jul 12 '16 at 09:59

2 Answers2

1

The constraint

V:[UITableViewCellContentView:0x7fedda431120(44)

means that rowHeight in your table is set to the default value of 44pt while you want the cell height to be dynamic. You will have to set rowHeight to UITableViewAutomaticDimension and also set estimatedRowHeight.

Also note that cells are reused therefore you will have to remove all previously added views everytime you call setupEvents.

Also note you should not call tableView.registerNib(...) from inside cellForRow method. The good place to register cells is inside viewDidLoad.

Sulthan
  • 128,090
  • 22
  • 218
  • 270
  • Currently my rowHeight is set to UITableViewAutomaticDimension and estimatedRowHeight is 500, well over what it needs to be to accommodate the views. I did change the cell reuse identifier to be unique for each row (baseString + index), but that did not change the issues I'm encountering. – steventnorris Jul 05 '16 at 20:50
  • Dug a little more, and that height of 44 is not a default or anything set by me, it is an autoresizing constraint. Likely it is the height of the xib's content before the programatic views are added. – steventnorris Jul 05 '16 at 20:53
  • @steventnorris Autoresizing constraint means that the frame height is set from the table view (the parent view) and that means the `rowHeight` is `44` or you are returning `44` from the delegate method `heightForRow`. – Sulthan Jul 05 '16 at 21:10
  • It is not. I've added my row height methods and calls above. No where do I set it to 44. That constraint is not explicitly set anywhere. – steventnorris Jul 06 '16 at 14:46
0

It seems you've made life much more complicated for yourself than it needs to be.

If we look at what you currently have:

  1. a table with 1 section and many rows
  2. 1 cell subclass
  3. each row has an arbitrary number of subviews added on the fly
  4. each subview is pinned to each other with constraints
  5. each cell is explicitly instantiated for each row, not properly reused
  6. cells don't have their subviews removed when they are reused

this doesn't fit well with a table view and means you're writing a lot of code and trying to cram it all into one place.

Looking at your data it would be better to have something like:

  1. a table with multiple sections, each section having multiple rows
  2. 1 section per session
  3. 1 row per 'event'
  4. 1 section header class with date labels
  5. 2 cell subclasses, 1 for an event and one for a break
  6. no views added on the fly

In this scenario your constraints are trivial and there are no constraints being added in code, you just set a bit of data and everything else just works. This scheme also breaks down your concerns and separates out the code for each different part into logical parts.

Rather than try to fix your existing issue you should step back and look at your approach.

Wain
  • 118,658
  • 15
  • 128
  • 151
  • That's fair. Given that it's a rewrite, it'll take a little time to put in place, but it would probably be the cleaner approach if I can get the styling to do as I want it to. Now, the constraints issue is a matter of frustration, so I'd still like to know where the issue is with that approach. If no one suggests a constraints answer soon, though, the bounty is yours good sir or madam. – steventnorris Jul 13 '16 at 12:45
  • (sir :-) ) what version of iOS are you running on? the conflict is between the height of the cell as set by the auto layout constraint and the contents you add. but, it isn't the forced height set by the table view. there is a bug: http://stackoverflow.com/questions/19132908/auto-layout-constraints-issue-on-ios7-in-uitableviewcell – Wain Jul 13 '16 at 13:54
  • I'm targeting iOS 8.2 at the moment. – steventnorris Jul 13 '16 at 15:37
  • Checked into that bug, seems to be an iOS 7 bug. I tried setting the autoresize mask in the init, just to see if that was the issue, but it didn't seem to affect anything. – steventnorris Jul 13 '16 at 15:51