iosswiftuitableviewuiview

Tableview height issue after device orientation changed


I am showing a view Controller with a self sizing tableview inside a Container view. See green color view in image 1. enter image description here

If the device orientation changed to Landscape mode, the UI has no issues and shows exactly as I want. See below image. enter image description here

But if I changed orientation back to portrait mode, then the height of the tableview inside Containerview doesn't get updated.I have added a purple background color to tableview. See below image. enter image description here

I have tried view.setNeedsLayout(), containerView.setNeedsLayout(), view.layoutIfNeeded() inside the viewWillTransition function. But still no success. Please some one help to fix this issue.

Update: Here is how I add subview to the container view

func showMCQs() {
    mcqKit = MCQKit()
    mcqKit!.configureMCQV2()
    addChild(MCQKit.mcqV2Controller)
    containerView.addSubview(MCQKit.mcqV2Controller.view)
    MCQKit.mcqV2Controller.didMove(toParent: self)
    MCQKit.mcqV2Controller.view.anchor(top: containerView.topAnchor, left: containerView.leftAnchor, bottom: containerView.bottomAnchor, right: containerView.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: nil, height: nil, centerX: nil, centerY: nil)
}
extension UIView {
func anchor(top: NSLayoutYAxisAnchor?, left: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, right: NSLayoutXAxisAnchor?,  paddingTop: CGFloat?, paddingLeft: CGFloat?, paddingBottom: CGFloat?, paddingRight: CGFloat?, width: CGFloat?, height: CGFloat?, centerX: NSLayoutXAxisAnchor?, centerY: NSLayoutYAxisAnchor?) {
    translatesAutoresizingMaskIntoConstraints = false
    
    if let safeTop = top, let safePaddingTop = paddingTop {
        topAnchor.constraint(equalTo: safeTop, constant: safePaddingTop).isActive = true
    }
    
    if let safeLeft = left, let safePaddingLeft = paddingLeft {
        leftAnchor.constraint(equalTo: safeLeft, constant: safePaddingLeft).isActive = true
    }
    
    if let safeBottom = bottom, let safePaddingBottom = paddingBottom  {
        bottomAnchor.constraint(equalTo: safeBottom, constant: -safePaddingBottom).isActive = true
    }
    
    if let safeRight = right, let safePaddingRight = paddingRight  {
        rightAnchor.constraint(equalTo: safeRight, constant: -safePaddingRight).isActive = true
    }
    
    if let safeWidth = width, safeWidth != 0 {
        widthAnchor.constraint(equalToConstant: safeWidth).isActive = true
    }
    
    if let safeHeight = height, safeHeight != 0{
        heightAnchor.constraint(equalToConstant: safeHeight).isActive = true
    }
    
    if let centerX = centerX {
        centerXAnchor.constraint(equalTo: centerX).isActive = true
    }
    
    if let centerY = centerY {
        centerYAnchor.constraint(equalTo: centerY).isActive = true
    }
}

Below is the code for the self sizing tableview.

class CustomTableView: UITableView {

// Self sizing tableView
override public func layoutSubviews() {
    super.layoutSubviews()
    if bounds.size != intrinsicContentSize {
        invalidateIntrinsicContentSize()
    }
}

override public var intrinsicContentSize: CGSize {
    layoutIfNeeded()
    return contentSize
}

}


Solution

  • Really difficult to try and guess where you're setup is failing, without seeing all of your layout / constraints / etc.

    So, here is a (really, really) quick example - with 3 "Quiz Questions" - that I think comes close to what you're going for... rotating the device is properly handled.

    ScrollView background is green, "container" view background is yellow, child controller view background is light-blue. Each element is constrained with small constants to make it easier to see the framing.

    enter image description here

    Rotated:

    enter image description here

    Scrolling, when needed:

    enter image description here


    struct QuizQuestion {
        var title: String = ""
        var answers: [String] = []
    }
    
    class AnswerCell: UITableViewCell {
        
        static let identifier: String = "AnswerCell"
        
        public var theData: String = "" {
            didSet {
                label.text = theData
            }
        }
        
        private let label = UILabel()
        
        override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
            super.init(style: style, reuseIdentifier: reuseIdentifier)
            commonInit()
        }
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            commonInit()
        }
        private func commonInit() {
            label.translatesAutoresizingMaskIntoConstraints = false
            contentView.addSubview(label)
            let g = contentView.layoutMarginsGuide
            NSLayoutConstraint.activate([
                label.topAnchor.constraint(equalTo: g.topAnchor),
                label.leadingAnchor.constraint(equalTo: g.leadingAnchor),
                label.trailingAnchor.constraint(equalTo: g.trailingAnchor),
                label.bottomAnchor.constraint(equalTo: g.bottomAnchor),
            ])
            
            // it's a multiline label
            label.numberOfLines = 0
            
            label.font = .systemFont(ofSize: 16.0, weight: .regular)
        }
        
    }
    
    class CustomTableView: UITableView {
        
        // Self sizing tableView
        override public func layoutSubviews() {
            super.layoutSubviews()
            if bounds.size != intrinsicContentSize {
                invalidateIntrinsicContentSize()
            }
        }
        
        override public var intrinsicContentSize: CGSize {
            layoutIfNeeded()
            return contentSize
        }
    }
    
    class TheParentVC: UIViewController {
        
        var someData: [QuizQuestion] = []
        let containerView = UIView()
        var childVC: TheChildVC!
        var curCard: Int = -1
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            view.backgroundColor = .systemBackground
            
            // get some sample data
            someData = sampleData()
            
            let scrollView = UIScrollView()
            
            var cfg = UIButton.Configuration.filled()
            cfg.title = "Next Card"
            let btn = UIButton(configuration: cfg, primaryAction: UIAction() { _ in
                self.showNextCard()
            })
    
            for v in [scrollView, containerView, btn] {
                v.translatesAutoresizingMaskIntoConstraints = false
            }
            
            scrollView.addSubview(containerView)
            scrollView.addSubview(btn)
            view.addSubview(scrollView)
            
            let g = view.safeAreaLayoutGuide
            let cg = scrollView.contentLayoutGuide
            let fg = scrollView.frameLayoutGuide
            
            NSLayoutConstraint.activate([
    
                scrollView.topAnchor.constraint(equalTo: g.topAnchor, constant: 8.0),
                scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 8.0),
                scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -8.0),
                scrollView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -8.0),
    
                containerView.topAnchor.constraint(equalTo: cg.topAnchor, constant: 8.0),
                containerView.leadingAnchor.constraint(equalTo: cg.leadingAnchor, constant: 8.0),
                containerView.trailingAnchor.constraint(equalTo: cg.trailingAnchor, constant: -8.0),
                
                btn.topAnchor.constraint(equalTo: containerView.bottomAnchor, constant: 20.0),
                btn.centerXAnchor.constraint(equalTo: containerView.centerXAnchor, constant: 0.0),
                btn.bottomAnchor.constraint(equalTo: cg.bottomAnchor, constant: -20.0),
                
                containerView.widthAnchor.constraint(equalTo: fg.widthAnchor, constant: -16.0),
            ])
            
            showMCQs()
            
            // so we can see the framing
            scrollView.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
            scrollView.backgroundColor = UIColor(red: 0.6, green: 0.8, blue: 0.6, alpha: 1.0)
            containerView.backgroundColor = .systemYellow
            
            // setup the first card
            showNextCard()
        }
        
        func showMCQs() {
            childVC = TheChildVC()
            guard let cView = childVC.view else { fatalError("Invalid setup!") }
            cView.translatesAutoresizingMaskIntoConstraints = false
            containerView.addSubview(cView)
            NSLayoutConstraint.activate([
                cView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 8.0),
                cView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 8.0),
                cView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -8.0),
                cView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -8.0),
            ])
            childVC.didMove(toParent: self)
            
            // see if you can find something in MCQKit that is not being setup
            //  the same way as above
    //      mcqKit = MCQKit()
    //      mcqKit!.configureMCQV2()
    //      addChild(MCQKit.mcqV2Controller)
    //      containerView.addSubview(MCQKit.mcqV2Controller.view)
    //      MCQKit.mcqV2Controller.didMove(toParent: self)
    //      MCQKit.mcqV2Controller.view.anchor(top: containerView.topAnchor, left: containerView.leftAnchor, bottom: containerView.bottomAnchor, right: containerView.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: nil, height: nil, centerX: nil, centerY: nil)
        }
        
        func showNextCard() {
            curCard += 1
            childVC.currentData = someData[curCard % someData.count]
        }
        
        func sampleData() -> [QuizQuestion] {
            let questions: [String] = [
                "Is this the first question?",
                "How many answers are there to choose from for this question (number 2 of 3 from our sample quiz data)?",
                "If this is the last question, does it look like the layout is working as desired?",
            ]
            let answers: [[String]] = [
                ["Yes", "No"],
                ["1", "2", "3", "4"],
                [
                    "Yes, it looks like it.",
                    "Yes, it even appears that variable-height cells are being handled correctly.",
                    "No, I though it would work someother way, but I can't explain how.",
                    "Maybe, but I'd rather not commit.",
                    "I'm just here to show it works with more than 4 answers (so now we've seen 2, 4 and 5 answer quiz questions).",
                ]
            ]
            var a: [QuizQuestion] = []
            for (q, aa) in zip(questions, answers) {
                a.append(QuizQuestion(title: q, answers: aa))
            }
            return a
        }
    }
    
    class TheChildVC: UIViewController, UITableViewDataSource, UITableViewDelegate {
        
        var currentData: QuizQuestion = QuizQuestion() {
            didSet {
                titleLabel.text = currentData.title
                theAnswers = currentData.answers
                tableView.reloadData()
            }
        }
        var theAnswers: [String] = []
        
        let titleLabel = UILabel()
        let tableView = CustomTableView()
        
        override func viewDidLoad() {
            super.viewDidLoad()
            view.backgroundColor = .init(red: 0.6, green: 0.85, blue: 1.0, alpha: 1.0)
        
            var cfg = UIButton.Configuration.filled()
            cfg.title = "Submit"
            let btn = UIButton(configuration: cfg, primaryAction: UIAction() { _ in
                
            })
            
            titleLabel.numberOfLines = 0
            titleLabel.font = .systemFont(ofSize: 20.0, weight: .regular)
    
            for v in [titleLabel, tableView, btn] {
                v.translatesAutoresizingMaskIntoConstraints = false
                view.addSubview(v)
            }
            
            NSLayoutConstraint.activate([
    
                titleLabel.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 12.0),
                titleLabel.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 8.0),
                titleLabel.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -8.0),
    
                tableView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 12.0),
                tableView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 8.0),
                tableView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -8.0),
    
                btn.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
                btn.topAnchor.constraint(equalTo: tableView.bottomAnchor, constant: 12.0),
                btn.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -12.0),
                
            ])
            
            tableView.register(AnswerCell.self, forCellReuseIdentifier: AnswerCell.identifier)
            tableView.dataSource = self
            tableView.delegate = self
            tableView.isScrollEnabled = false
            
            titleLabel.text = "Sample Title"
        }
        
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return theAnswers.count
        }
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let c = tableView.dequeueReusableCell(withIdentifier: AnswerCell.identifier, for: indexPath) as! AnswerCell
            c.theData = theAnswers[indexPath.row]
            return c
        }
    }
    

    You can create a new project, copy/paste all of the above, and set the Initial View Controller to TheParentVC -- should run as-is.

    If it works as desired, compare the constraints setup to what you're doing and see if you can find the difference(s).