2

Ok I just ran into something bizarre. I've got my app controller dependency injecting a view (header) into a view controller. That view controller presents another view controller modally and dependency injects it's own header for the presenting view controller to use. But when its presented the header from the first controller disappears.

The property is still set but it's been removed from the view hierarchy.

I've reproduced this issue in fresh singleview project:

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        self.view.backgroundColor = .white

        let button = UIButton(frame: CGRect(x: 0, y: 20, width: 100, height: 50))
        button.setTitle("Click Me!", for: .normal)
        button.addTarget(self, action: #selector(self.segue), for: .touchUpInside)
        button.backgroundColor = .black
        button.setTitleColor(.lightGray, for: .normal)

        self.view.addSubview(button)
    }

    func segue() {
        let view = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
        view.backgroundColor = .lightGray

        let firstVC = FirstViewController()
        firstVC.sharedView = view

        present(firstVC, animated: false)
    }
}

class FirstViewController: UIViewController {
    var sharedView: UIView!

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

        self.view.addSubview(self.sharedView)

        let button = UIButton(frame: CGRect(x: 0, y: 200, width: 100, height: 50))
        button.setTitle("Click Me!", for: .normal)
        button.addTarget(self, action: #selector(self.segue), for: .touchUpInside)
        button.backgroundColor = .black
        button.setTitleColor(.lightGray, for: .normal)

        self.view.addSubview(button)
    }

    func segue() {
        let secondVC = SecondViewController()
        secondVC.sharedView = self.sharedView

        present(secondVC, animated: true)
    }
}

class SecondViewController: UIViewController {
    var sharedView: UIView!

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

        self.view.addSubview(self.sharedView)

        let button = UIButton(frame: CGRect(x: 0, y: 200, width: 100, height: 50))
        button.setTitle("Click Me!", for: .normal)
        button.addTarget(self, action: #selector(self.segue), for: .touchUpInside)
        button.backgroundColor = .black
        button.setTitleColor(.lightGray, for: .normal)

        self.view.addSubview(button)
    }

    func segue() {
        self.dismiss(animated: true)
    }
}

Can someone explain what's going on here? Why does the sharedView disappear from the FirstViewController?

Aquila Sagitta
  • 438
  • 3
  • 9
  • 2
    At the doc of `addSubview()`: `Views can have only one superview. If view already has a superview and that view is not the receiver, this method removes the previous superview before making the receiver its new superview.` I think that's the issue. You may want this: https://stackoverflow.com/questions/4425939/can-uiview-be-copied – Larme Jun 21 '17 at 21:32
  • Ahh that makes sense. Any suggestions to make this work properly? – Aquila Sagitta Jun 21 '17 at 21:33
  • I edited my previous comment with a possible solution. But I'd use a method that create that headerView with all your customization and call it. – Larme Jun 21 '17 at 21:35
  • Awesome. Looks like I just need to make a factory for my header. Thanks! – Aquila Sagitta Jun 21 '17 at 21:37
  • 1
    @Larme, you beat me to it. You should post your comment as an answer. It is, in fact, the correct answer. – Duncan C Jun 21 '17 at 23:35
  • Views aren't meant to be shared. A factory that creates new copies of your unique view does sound like a good way to go. – Duncan C Jun 21 '17 at 23:36

1 Answers1

1

At the doc of -addSubview(_:):

Views can have only one superview. If view already has a superview and that view is not the receiver, this method removes the previous superview before making the receiver its new superview.

That should explain your issue.

I'd suggest instead that you create a method that generate a headerView (a new one each time) according to your customization style.

If you really want to "copy" the view, you can check that answer. Since UIView is not NSCopying Compliant, their trick is to "archive/encode" it since it's NSCoding compliant, copy that archive, and "unarchive/decode" the copy of it.

Larme
  • 24,190
  • 6
  • 51
  • 81