简体   繁体   中英

Constraint Crash after Dismissing View Controller

I'm a bit new to Xcode and been trying to do things programatically. I have View Controller A, B, C, and D. I have a back button on C, and D. When going from D to C using self.dismiss it works fine, however when I go from C to BI am getting a crash that looks like it's a constraint issue and I have no idea why.

Again, the crash occurs when going from C to B. The error says no common ancestor for DropDownButton, but there is no DropDownButton on ViewController B, it exists on C the one I am trying to dismiss.

I would like to know more about how the view controllers dismissing and Auto Layout works, could someone point me in the right direction please?

"oneonone.DropDownButton:0x7fcfe9d30660'🇺🇸+1 ⌄'.bottom"> because they have no common ancestor.  Does the constraint or its anchors reference items in different view hierarchies?  That's illegal. userInfo: (null)
2018-11-09 19:56:22.828322-0600 oneonone[62728:4835265] *** Terminating app due to uncaught exception 'NSGenericException', reason: 'Unable to activate constraint with anchors <NSLayoutYAxisAnchor

UPDATE TO QUESTIONS:

Here is View Controller C, included is the var, adding it to subview, and how I dismiss this view controller

    lazy var countryCodes: DropDownButton = {
        let button = DropDownButton(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
        let us = flag(country: "US")
        let br = flag(country: "BR")
        let lightGray = UIColor(red: 240/255, green: 240/255, blue: 240/255, alpha: 1)
        button.backgroundColor = lightGray
        button.setTitle(us + "+1 \u{2304}", for: .normal)
        button.titleLabel?.font = UIFont.systemFont(ofSize: 20)
        button.setTitleColor(UIColor.darkGray, for: .normal)
        button.uiView.dropDownOptions = [us + "+1", br + "+55", "+33", "+17", "+19"]
        return button
    }()

override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = .white

        [countryCodes].forEach{ view.addSubview($0) }

        setupLayout()

    }

func setupLayout(){
        countryCodes.translatesAutoresizingMaskIntoConstraints = false
        countryCodes.topAnchor.constraint(equalTo: instructionLabel.bottomAnchor, constant: 30).isActive = true
        countryCodes.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: -77.5).isActive = true
        countryCodes.widthAnchor.constraint(equalToConstant: 85).isActive = true // guarantees this width for stack
        countryCodes.heightAnchor.constraint(equalToConstant: 40).isActive = true
}

@objc func buttonPressed(){
    self.dismiss(animated: true, completion: nil)
}

Here is the code in view controller B that (creates or presents?) View Controller C

@objc func phoneAuthButtonPressed(){
        let vc = phoneAuthViewController()
        self.present(vc, animated: true, completion: nil)
    }

UPDATE 2: ADDING THE CUSTOM CLASS

Here is the button code that I used as a custom class following a tutorial, I believe the problem lies in here

protocol dropDownProtocol {
    func dropDownPressed(string: String)
}

class DropDownButton: UIButton, dropDownProtocol {

    var uiView = DropDownView()

    var height = NSLayoutConstraint()

    var isOpen = false

    func dropDownPressed(string: String) {
        self.setTitle(string + " \u{2304}", for: .normal)
        self.titleLabel?.font = UIFont.systemFont(ofSize: 18)
        self.dismissDropDown()
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.backgroundColor = UIColor.gray
        uiView = DropDownView.init(frame: CGRect.init(x: 0, y: 0, width: 0, height: 0))
        uiView.delegate = self
        uiView.layer.zPosition = 1 // show in front of other labels
        uiView.translatesAutoresizingMaskIntoConstraints = false
    }

    override func didMoveToSuperview() {
        self.superview?.addSubview(uiView)
        self.superview?.bringSubviewToFront(uiView)
        uiView.topAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
        uiView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
        uiView.widthAnchor.constraint(equalTo: self.widthAnchor).isActive = true
        height = uiView.heightAnchor.constraint(equalToConstant: 0)
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { // animates drop down list
        NSLayoutConstraint.deactivate([self.height])

        if self.uiView.tableView.contentSize.height > 150 {
            self.height.constant = 150
        } else {
            self.height.constant = self.uiView.tableView.contentSize.height
        }

        if isOpen == false {
            isOpen = true
            NSLayoutConstraint.activate([self.height])
            UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseInOut, animations: {
                self.uiView.layoutIfNeeded()
                self.uiView.center.y += self.uiView.frame.height / 2
            }, completion: nil)
        } else {
            dismissDropDown()
        }
    }

    func dismissDropDown(){
        isOpen = false
        self.height.constant = 0
        NSLayoutConstraint.activate([self.height])
        UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseInOut, animations: {
            self.uiView.center.y -= self.uiView.frame.height / 2
            self.uiView.layoutIfNeeded()
        }, completion: nil)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}


class DropDownView: UIView, UITableViewDelegate, UITableViewDataSource {

    var dropDownOptions = [String]()

    var tableView = UITableView()

    var delegate : dropDownProtocol!

    let lightGray = UIColor(red: 240/255, green: 240/255, blue: 240/255, alpha: 1)

    override init(frame: CGRect) {
        super.init(frame: frame)
        tableView.backgroundColor = lightGray
        tableView.delegate = self
        tableView.dataSource = self
        tableView.translatesAutoresizingMaskIntoConstraints = false
        self.addSubview(tableView) // can not come after constraints
        tableView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
        tableView.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
        tableView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
        tableView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

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

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

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell()
        cell.textLabel?.text = dropDownOptions[indexPath.row]
        cell.textLabel?.font = UIFont.systemFont(ofSize: 14)
        cell.textLabel?.textColor = UIColor.darkGray
        cell.backgroundColor = lightGray
        return cell
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        self.delegate.dropDownPressed(string: dropDownOptions[indexPath.row])
        self.tableView.deselectRow(at: indexPath, animated: true)
    }

}

As I can see, the error says that one of countryCodes buttons is located in the different view than instructionLabel . They should have the same parent if you want them to be constrained by each other.

Hi I think the problem occurs in the didMoveToSuperView() function, because it is called also when the view is removed from it's superview. so when you try to setup the anchor to something that does no more exist it crashes.

try something like this :

  if let superview = self.superview {
    superview.addSubview(uiView)
    superview.bringSubviewToFront(uiView)
    uiView.topAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
    uiView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
    uiView.widthAnchor.constraint(equalTo: self.widthAnchor).isActive = true
    height = uiView.heightAnchor.constraint(equalToConstant: 0)
}

Replace your didMoveToSuperview function with this and it will work. This function also gets called when the view its removed from the superview and the superview will be nil and that's causing the crash.

    override func didMoveToSuperview() {
    if let superview = self.superview {
        self.superview?.addSubview(dropView)
        self.superview?.bringSubviewToFront(dropView)
        dropView.topAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
        dropView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
        dropView.widthAnchor.constraint(equalTo: self.widthAnchor).isActive = true
        height = dropView.heightAnchor.constraint(equalToConstant: 0)
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM