I am showing a view Controller with a self sizing tableview inside a Container view. See green color view in image 1.
If the device orientation changed to Landscape mode, the UI has no issues and shows exactly as I want. See below image.
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.
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
}
}
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.
Rotated:
Scrolling, when needed:
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).