Search code examples
swiftxcodeconstraints

how to constraints views many times in different situations programmatically in swift?


i have some labels as a buttons which each one filters my table view content. In one of my labels I need to move table view a little bottom.because I have to show another view to user and table view must go below.

i have constraints all my views in view did load method first and i should constraint my tableView in label listener again.

here is my code for first constraint

 override func setupViews() {
    super.setupViews()


    view.addSubview(header)
    view.addSubview(serachOptionView)
    view.addSubview(tableView)
    header.addSubview(mytitle)
    view.addSubview(serachBarView)

    serachBarView.addSubview(serachInCityLabel)
    serachBarView.addSubview(serachInCountryLabel)
    serachBarView.addSubview(serachInTransitLabel)
    serachBarView.addSubview(advanceSearch)
    serachBarView.addSubview(columnLabelLeft)
    serachBarView.addSubview(columnLabelRight)




    //TO - DO header

    header.translatesAutoresizingMaskIntoConstraints=false
    header.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive=true
    header.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive=true
    header.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive=true
    header.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.17).isActive=true
    header.backgroundColor =  UIColor.init(patternImage: UIImage(named: "test.png")!)





    // MARK -- serachBarView


    serachBarView.translatesAutoresizingMaskIntoConstraints=false
    serachBarView.trailingAnchor.constraint(equalTo: header.trailingAnchor, constant: -3).isActive=true
    serachBarView.leadingAnchor.constraint(equalTo: header.leadingAnchor, constant: 3).isActive=true
    serachBarView.bottomAnchor.constraint(equalTo: header.bottomAnchor, constant: -1).isActive=true
    serachBarView.heightAnchor.constraint(equalTo: header.heightAnchor, multiplier: 0.27).isActive=true



    // MARK -- INCity

    serachInCityLabel.translatesAutoresizingMaskIntoConstraints=false
    serachInCityLabel.trailingAnchor.constraint(equalTo: serachBarView.trailingAnchor, constant: -5).isActive=true
    serachInCityLabel.topAnchor.constraint(equalTo: serachBarView.topAnchor, constant: 1).isActive=true
    serachInCityLabel.bottomAnchor.constraint(equalTo: serachBarView.bottomAnchor, constant: -1).isActive=true
    serachInCityLabel.widthAnchor.constraint(equalTo: serachBarView.widthAnchor, multiplier: 0.23).isActive=true


    // MARK -- columnLabelRight

    columnLabelRight.translatesAutoresizingMaskIntoConstraints=false
    columnLabelRight.trailingAnchor.constraint(equalTo: serachInCityLabel.leadingAnchor, constant: 0).isActive=true
    columnLabelRight.topAnchor.constraint(equalTo: serachInCityLabel.topAnchor, constant: 4).isActive=true
    columnLabelRight.bottomAnchor.constraint(equalTo: serachInCityLabel.bottomAnchor, constant: -4).isActive=true
    columnLabelRight.widthAnchor.constraint(equalToConstant: 1.5).isActive=true



    // MARK -- INCountry

    serachInCountryLabel.translatesAutoresizingMaskIntoConstraints=false
    serachInCountryLabel.trailingAnchor.constraint(equalTo: columnLabelRight.leadingAnchor, constant: 0).isActive=true
    serachInCountryLabel.topAnchor.constraint(equalTo: serachBarView.topAnchor, constant: 1).isActive=true
    serachInCountryLabel.bottomAnchor.constraint(equalTo: serachBarView.bottomAnchor, constant: -1).isActive=true
    serachInCountryLabel.widthAnchor.constraint(equalTo: serachBarView.widthAnchor, multiplier: 0.24).isActive=true


    // MARK -- columnLabelRight

    columnLabelLeft.translatesAutoresizingMaskIntoConstraints=false
    columnLabelLeft.trailingAnchor.constraint(equalTo: serachInCountryLabel.leadingAnchor, constant: 0).isActive=true
    columnLabelLeft.topAnchor.constraint(equalTo: serachInCountryLabel.topAnchor, constant: 4).isActive=true
    columnLabelLeft.bottomAnchor.constraint(equalTo: serachInCountryLabel.bottomAnchor, constant: -4).isActive=true
    columnLabelLeft.widthAnchor.constraint(equalToConstant: 1.5).isActive=true

    // MARK -- INTranis


    serachInTransitLabel.translatesAutoresizingMaskIntoConstraints=false
    serachInTransitLabel.trailingAnchor.constraint(equalTo: serachInCountryLabel.leadingAnchor, constant: 0).isActive=true
    serachInTransitLabel.topAnchor.constraint(equalTo: serachBarView.topAnchor, constant: 1).isActive=true
    serachInTransitLabel.bottomAnchor.constraint(equalTo: serachBarView.bottomAnchor, constant: -1).isActive=true
    serachInTransitLabel.widthAnchor.constraint(equalTo: serachBarView.widthAnchor, multiplier: 0.24).isActive=true


    // MARK -- advance search


    advanceSearch.translatesAutoresizingMaskIntoConstraints=false
    advanceSearch.trailingAnchor.constraint(equalTo: serachInTransitLabel.leadingAnchor, constant: 0).isActive=true
    advanceSearch.topAnchor.constraint(equalTo: serachBarView.topAnchor, constant: 1).isActive=true
    advanceSearch.bottomAnchor.constraint(equalTo: serachBarView.bottomAnchor, constant: -1).isActive=true
    advanceSearch.widthAnchor.constraint(equalTo: serachBarView.widthAnchor, multiplier: 0.24).isActive=true


    // MARK -- tableView


    tableView.translatesAutoresizingMaskIntoConstraints=false
    tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive=true

    tableView.topAnchor.constraint(equalTo: serachBarView.bottomAnchor, constant: 0).isActive=true
    tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive=true
    tableView.delegate=self
    tableView.dataSource=self
    tableView.register(specialGoodCell.self, forCellReuseIdentifier: "myCell")


}

and move below my table View here :

@objc func labelAction(){

      // MARK -- tableView


        tableView.translatesAutoresizingMaskIntoConstraints=false
        tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive=true

        tableView.topAnchor.constraint(equalTo: mypopUpView.bottomAnchor, constant: 0).isActive=true
        tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive=true
        tableView.delegate=self
        tableView.dataSource=self
        tableView.register(specialGoodCell.self, forCellReuseIdentifier: "myCell")

}

and the result is no movment and this warnning :

nable 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. 
(
    "<NSLayoutConstraint:0x28276f750 UIView:0x13fd45270.bottom == UIView:0x13fd4b2f0.bottom - 1   (active)>",
    "<NSLayoutConstraint:0x28276f610 V:[UIView:0x13fd45270]-(0)-[UITableView:0x1400a0000]   (active)>",
    "<NSLayoutConstraint:0x28276f2a0 V:[UIView:0x13fd4b2f0]-(40)-[UIView:0x13fd18a40]   (active)>",
    "<NSLayoutConstraint:0x28276f8e0 UIView:0x13fd18a40.height == 0.16*UIView:0x13fe428e0.height   (active)>",
    "<NSLayoutConstraint:0x28276a990 V:[UIView:0x13fd18a40]-(0)-[UITableView:0x1400a0000]   (active)>",
    "<NSLayoutConstraint:0x28276cb40 'UIView-Encapsulated-Layout-Height' UIView:0x13fe428e0.height == 568   (active)>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x28276f2a0 V:[UIView:0x13fd4b2f0]-(40)-[UIView:0x13fd18a40]   (active)>

Solution

  • System doesn't like multiple constraints assigned to an anchor with the same priority, because they conflict and the system 'breaks' one of them to satisfy at least one. Default priority for constraints, if not otherwise defined, is required (1000). You can assign multiple constraints for an anchor and have the desired one active by assigning different priorities to them like defaultLow and defaultHigh and switch them on your behalf.

    First thing is to keep a reference to your constraints in order to change them. Feeling free to update your code to the following:

    class MyViewController: UIViewController {
    
        // ...
    
        // Keep a reference to the constraints you want to change
        // depending on user interaction or desired state 
        // in your view controller class
    
        var tableViewConstraintTopAnchor1: NSLayoutConstraint!
        var tableViewConstraintTopAnchor2: NSLayoutConstraint!
    
        // ...
    
    }
    

    Then in your setupViews function, assign them:

    func setupViews() {
    
        // ...
    
        // adding subviews and other constraints
    
        // ...
    
        // MARK -- tableView
    
    
        tableView.translatesAutoresizingMaskIntoConstraints=false
        tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive=true
        tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive=true
    
        // assign (desired, but conflicting) constraints with different priority
    
        tableViewConstraintTopAnchor1 = tableView.topAnchor.constraint(equalTo: serachBarView.bottomAnchor, constant: 0)
        tableViewConstraintTopAnchor1.priority = .defaultHigh
        tableViewConstraintTopAnchor1.isActive = true
    
        tableViewConstraintTopAnchor2 = tableView.topAnchor.constraint(equalTo: mypopUpView.bottomAnchor, constant: 0)
        tableViewConstraintTopAnchor2.priority = .defaultLow
        tableViewConstraintTopAnchor2.isActive = true
    
        tableView.delegate = self
        tableView.dataSource = self
        tableView.register(specialGoodCell.self, forCellReuseIdentifier: "myCell")
    
    }
    

    In your labelAction function, you simply switch the priority of the constraints and tell the system to update the layout of your view.

    @objc func labelAction(){
    
        // MARK -- tableView
    
        // Switch the priority of the desired constraint to defaultHigh
        // the other ones to defaultLow
    
        tableViewConstraintTopAnchor1.priority = .defaultLow
        tableViewConstraintTopAnchor2.priority = .defaultHigh
    
        view.setNeedsLayout()
        view.layoutIfNeeded()
    
    }
    

    It is also possible to put this in an UIView animation block and have the whole thing animated:

    @objc func labelAction(){
    
        // MARK -- tableView
    
        UIView.animate(withDuration: 0.2) {
    
             self.tableViewConstraintTopAnchor1.priority = .defaultLow
             self.tableViewConstraintTopAnchor2.priority = .defaultHigh
    
             self.view.setNeedsLayout()
             self.view.layoutIfNeeded()
    
        }
    
    }
    

    Assigning both constraints for your tableView in viewDidLoad() (or the function you call within it) only works, if mypopUpView already exists and is in the same display hierarchy - didn't see it in your code so far ;)