Ryan10
Ryan10

Reputation: 126

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)>

Upvotes: 2

Views: 478

Answers (2)

Rainer Schwarz
Rainer Schwarz

Reputation: 380

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 ;)

Upvotes: 1

jon wilson
jon wilson

Reputation: 54

for example ... in mutilple constraints you have made, you have a leading, a trailing, then also a width. you can't have a leading say of 5 then a trailing of -5 but then also specify a width because you have already specified the width with the leading and trailing constraints if that makes sense? if not tell me and ill explain in more detail. basically if your screen was 300 pixels wide. you have a label and constrain it to leading of 50, a trailing of -50.. then your label width is automatically going to be 200 .. you couldn't add a width anchor of anything because its won't be able to satisfy all your constraints.

Upvotes: 0

Related Questions