NEW: Subscribe to Hacking with Swift+ and accelerate your learning! >>

Accessing the contents of UIContainerView from the parent view

Forums > Swift

I'm working on a program where the main screen has two primary subviews that need to get swapped out, and Interface Builder is not liking letting me work with the one lower in the stack (e.g. when I try to add constraints it tags something higher in the stack). Somewhere along the way someone suggested that this would be a great use for UIViewContainer, and it seems like a natural fit, except for one problem:

I can't interact with it. At. All.

  • I've tried converting the views that I was embedding into the containers from UIView subclasses to UIViewController subclasses
  • I've tried treating the view containers as subviews with the appropriate UIView subclass applied to it directly (both in the parent view controller and in the extra instance that appears on the storyboard where you manage its contents—as well as both combinations of just one or the other)
  • I've tried making the view container an IBOutlet and adding the embedded UIView using addSubview()

Nothing is letting me access the embedded view's properties, methods, or subviews. I just keep getting hits for trying to call things that don't exist.

What's the piece that I'm missing here?

   

You need to do this manually. If you are displaying another View Controller inside UIView you would either keep a reference to the child view controller or use the children property to access these view controllers. Don't forget to call didMove(to:) method after adding the view controller view as subview of container view.

   

OK, I think I get it conceptually, but I'm having trouble figuring out how to do it in practice. If I lay out some code basics here, can someone explain how to get it wired up properly?

Here's the original configuration, before trying to move it out to containers:

class MainViewController: UIViewController {
  @IBOutlet weak var subViewA: SubViewA!
  @IBOutlet weak var subViewB: SubViewB!
}

class SubViewA: UIView {}

class SubViewB: UIView {}

Here's how I think I'm supposed to implement the above?

/* == Storyboard Content==
Added to MainViewController:
* Container Object: subViewA (changed class to SubViewA, connected to corresponding IBOutlet, rebuilt view inside the view that came with the container)
* Container Object: subViewB  (changed class to SubViewB, connected to corresponding IBOutlet, rebuilt view inside the view that came with the container)
== END Storyboard Content== */

class MainViewController: UIViewController {
  @IBOutlet weak var subViewA: SubViewA!
  @IBOutlet weak var subViewB: SubViewB!

  override func viewDidLoad() {
    super.viewDidLoad()

    //...?
  }
}

class SubViewA: UIView {}

class SubViewB: UIView {}

   

This is one of a few things I have never done entirely in Storyboards because it is confusing and error prone.

My approach is usually to add Container View with Interface Builder and create @IBOutlet for it. The rest is done in code:

And then the setup looks like this:

let vc = UIStoryboard.main().instantiateInitialViewController() as! ViewController

addChild(vc)
containerView.addSubview(vc.view)
vc.view.pinEdges(to: containerView)
vc.didMove(toParent: self)

self.childViewController = vc

The main() is my helper method that instantiates the storyboard and then I load the view controller from that.

pinEdges is my AutoLayout helper that pins all side anchors to the container with the NSLayoutContstraint.activate method.

As a last step I am assigning the view controller to local variable. This should either be weak or manually set to nil when appropriate just to be safe.

   

So Xcode fought me on the storyboard thing, first by saying .main() doesn't exist then by...well, my initial view controller is the tab controller for the application, followed by a nav controller for the tab content, then the first tab's content. So getting to a compatible view controller using that method didn't seem to work.

I've tried applying the same principles slightly differently, however, as shown in the following example. Basically, I created a SubViewController class as a thin wrapper for the SubView, then instantitated a controller directly using it. I tried setting the container to the SubViewController class, but Interface Builder rejected that and set it back to SubView.

In the end, however, it's still dealing with the same issue: bailing out whenever the properties of the view are invoked, even though we established the child view and added it...

/* == Storyboard Content==
Added to MainViewController:
* Container Object: subView (changed class to SubView, connected to corresponding IBOutlet, rebuilt view inside the view that came with the container)
== END Storyboard Content== */

class MainViewController: UIViewController {
  @IBOutlet weak var subView: SubView!

  override func viewDidLoad() {
    super.viewDidLoad()

        /// Configure Subviews
        let vc = QuestionViewController()
        vc.setupView()
        questionView = vc.view as? QuestionView

        addChild(vc)
        questionViewContainer.addSubview(vc.view!)
        vc.didMove(toParent: self)

    /// Error appears here
    subView.propertyName = "value"
  }
}

class SubViewController: UIViewController {
    func setupView() {
        view = SubView()
    }
}

class SubView: UIView {
  var propertyName: String?
}

   

Hacking with Swift is sponsored by Fernando Olivares

SPONSORED Ever ask for help and your reviewer immediately notices issues you missed? Fernando Olivares is a 10-year veteran from Big Nerd Ranch and Lambda School who can help hone your app building skills, and he's just launched a new book that does just that – use the code "hacking" to save $5!

Buy the book

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

Not logged in

Log in
 

Link copied to your pasteboard.