1

In my table view's cell I've stack view which contains views. I've trailing constraints for stack view. I calculate the value of this trailing constraints based on how many views I've to show inside stack view.

Is there any way we can recalculate this trailing constraint value and display view without reloading table view in viewWillTransition after device rotation?

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)

    self.tableView.reloadData()

    // self.view.layoutIfNeeded() // This doesn't recalculate constraints
}

Storyboard

layoutIfNeeded

Davis
  • 105
  • 1
  • 12

2 Answers2

1

You should implement the method

func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator)

Which gets called when device transition to new view (landscape or portrait). In this you can change your constraint and call

view.setNeedsLayout() To mark that layout needs to be recalculated in next Runloop.

Aks
  • 1,567
  • 13
  • 23
1

There are a few ways you could do this -- re-calculating size on rotation (or size change) is probably not the best way.

One approach, taking advantage of UIStackView to handle all of the layout, would be to add "blank" views to your stack view. If you set only 1 or 2 "real" views to be showing, you can then also show "blank" views to fill-out the 3 "columns."

So, assuming you might show a row with Zero real views, leave your prototype as-is, but then:

  • add an IBOutlet reference to the stack view
  • in awakeFromNib(), create and add 3 clear UIViews to the stack view
  • keep track of the real views and the blank views in arrays

Now, when you set which views to show, hide the view(s) not to show and show enough blank views to keep 3 views visible.

Examples:

  • [1, 2] hide view3 and show blank1
  • [2] hide view1 and view3 and show blank1 and blank2

So, you will always have 3 subviews in your stack, and no calculations are necessary... auto-layout will keep them arranged.

Here is a sample implementation:

class ThreeColCell: UITableViewCell {

    @IBOutlet var mainStackView: UIStackView!

    @IBOutlet var view1: UIView!
    @IBOutlet var view2: UIView!
    @IBOutlet var view3: UIView!

    var arrayOfRealViews: [UIView] = [UIView]()
    var arrayOfBlankViews: [UIView] = [UIView]()

    var myData: [Int] = [Int]() {
        didSet {
            // hide all the views in the stack
            mainStackView.arrangedSubviews.forEach {
                $0.isHidden = true
            }

            // show the specified "real" views
            myData.forEach { i in
                arrayOfRealViews[i - 1].isHidden = false
            }

            // if fewer than 3 "real" views, show "blank" view(s)
            for i in 0..<(3 - myData.count) {
                arrayOfBlankViews[i].isHidden = false
            }
        }
    }

    override func awakeFromNib() {
        super.awakeFromNib()
        commonInit()
    }

    func commonInit() -> Void {
        // ordered array of views 1 to 3
        arrayOfRealViews = [view1, view2, view3]

        // add 3 "blank" views to the stack view
        //  and to array of blank views
        for _ in 0..<3 {
            let v = UIView()
            v.translatesAutoresizingMaskIntoConstraints = false
            v.backgroundColor = .clear
            mainStackView.addArrangedSubview(v)
            arrayOfBlankViews.append(v)
        }
    }

}

class ThreeColTableViewController: UITableViewController {

    var theData = [
        [1, 2],
        [1, 2, 3],
        [1],
        [1, 2, 3],
        [2],
        [2, 3],
        [1, 2, 3],
        [3],
        [2, 3],
        [1, 2, 3],
    ]


    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return theData.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "ThreeColCell", for: indexPath) as! ThreeColCell
        cell.myData = theData[indexPath.row]
        return cell
    }

}

Resulting in Portrait:

and Landscape:


Edit:

Here is another example, using an array of structs with .isDisabled1 (and 2 and 3) properties:

struct Profile {

    var isDisabled1 = false
    var isDisabled2 = false
    var isDisabled3 = false

}

class ThreeColCell: UITableViewCell {

    @IBOutlet var mainStackView: UIStackView!

    @IBOutlet var view1: UIView!
    @IBOutlet var view2: UIView!
    @IBOutlet var view3: UIView!

    var arrayOfRealViews: [UIView] = [UIView]()
    var arrayOfBlankViews: [UIView] = [UIView]()

    var myProfile: Profile = Profile() {
        didSet {
            // hide all the views in the stack
            mainStackView.arrangedSubviews.forEach {
                $0.isHidden = true
            }

            // I don't know how you have your button/label views set up, but here
            // you would set button titles and label texts based on myProfile properties

            // create a [1, 2, 3] array based on the .isDisabled# properties of the Profile object
            var a = [Int]()

            if !myProfile.isDisabled1 {
                a.append(1)
            }
            if !myProfile.isDisabled2 {
                a.append(2)
            }
            if !myProfile.isDisabled3 {
                a.append(3)
            }

            // you now have an array "a" that will be
            //  [1, 2, 3]    or
            //  [1, 2]       or
            //  [2]          or
            //  [2, 3]       etc

            // show the specified "real" views (arrays are Zero-based)
            a.forEach { i in
                arrayOfRealViews[i - 1].isHidden = false
            }

            // pad stackview to 3 using "blank" view(s)
            // if 1 real view, show 2 blank views
            // if 2 real views, show 1 blank view
            // if 3 real views, don't show any blank views
            for i in 0..<(3 - a.count) {
                arrayOfBlankViews[i].isHidden = false
            }

        }
    }

    override func awakeFromNib() {
        super.awakeFromNib()

        // ordered array of views 1 to 3
        arrayOfRealViews = [view1, view2, view3]

        // add 3 "blank" views to the stack view
        //  and to array of blank views
        for _ in 0..<3 {
            let v = UIView()
            v.translatesAutoresizingMaskIntoConstraints = false
            v.backgroundColor = .clear
            mainStackView.addArrangedSubview(v)
            arrayOfBlankViews.append(v)
        }
    }

}

class ThreeColTableViewController: UITableViewController {

    var profilesArray: [Profile] = [Profile]()

    override func viewDidLoad() {
        super.viewDidLoad()

        // create a few Profiles
        // all 3 views are "enabled" by default

        var p = Profile()

        profilesArray.append(p)

        // Profile with views 2 and 3 disabled
        p = Profile()
        p.isDisabled2 = true
        p.isDisabled3 = true

        profilesArray.append(p)

        // Profile with view 3 disabled
        p = Profile()
        p.isDisabled3 = true

        profilesArray.append(p)

        // Profile with view 1 disabled
        p = Profile()
        p.isDisabled1 = true

        profilesArray.append(p)

        // Profile with views 1 and 2 disabled
        p = Profile()
        p.isDisabled1 = true
        p.isDisabled2 = true

        profilesArray.append(p)

    }

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return profilesArray.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "ThreeColCell", for: indexPath) as! ThreeColCell

        cell.myProfile = profilesArray[indexPath.row]

        return cell
    }

}
DonMag
  • 69,424
  • 5
  • 50
  • 86
  • Looks like a solution but it's so complex that my head is spinning. My data is not array of int but an array of structure and it has many fields. – Davis Apr 02 '19 at 15:10
  • *"array of structure and it has many fields"* --- not really any difference. Presumably, something in your data structure indicates whether or not the view should be visible, so use that value to show/hide the views and "blank" views. – DonMag Apr 02 '19 at 15:56
  • @Davis - I edited my answer to include another example... This time, using an array of "Profile" objects instead of a simple array of Ints. – DonMag Apr 02 '19 at 19:22