9

On an iPhone project I'm using Xcode storyboard to embed a few containing views in a main scroll view. I've heard containing view is also an "embed segue". Now I don't necessarily have to embed other child controllers, I could have just created custom views and have the original child controllers' logic in those custom subviews. (I think I'm just going to do that after posting here, because it seems easier.) But I've already done the code and want to know how easy or hard it is to stay with it.

Because scroll view content is greater than the main screen bounds, it's harder to layout the container view in storyboard. I can think of three ways to solve it. I can either

  1. Drag the scroll view up and down and put my container views there.
  2. Just drag some view in the scroll view, and then resize the frame in the main controller's viewDidLoad. (And if I'm using auto layout then I would add auto layout there) But just seems to defy the advantage of having storyboard and embed segue in the first place. But it seems easier than #3 if I have to interact with child view controllers.
  3. Forget storyboard and just write a Containing controller logic (as described in WWDC 2012 video Implementing UIViewController Containment) but this appears to be complicated.

Is there a way to create embed segue in Xcode, but NOT putting it in but to do something like a "manual segue" as with other view transitions? I wouldn't be able to see the layout in storyboard but at least it'll be easier than #3 and I don't have to drag up and down like #2 which seems silly.

huggie
  • 17,587
  • 27
  • 82
  • 139
  • Creating container views programmatically really isn't that complicated, it is only like two or three extra lines of code (from creating a view programmatically). I have done this many times (I don't use storyboards). That is the route I would personally take if it made sense in my given situation. – Firo Sep 21 '13 at 04:23
  • Whether you add views or view controllers (via the embed segue) depends on what you're doing in those views. It's hard to advise you without knowing what you want to do in there. – rdelmar Sep 21 '13 at 06:02
  • @Firo Do you mean the same thing as I'm saying? I'm talking about adding child sub view controllers. It's an hour of WWDC video just on the topic, can't be THAT easy. =/ – huggie Sep 21 '13 at 06:26
  • @rdelmar: Suppose I just want to add child view controllers, that's what my question was aiming for. – huggie Sep 21 '13 at 06:27
  • I guess I can still do it the way I always have in storyboard, I end up with this solution: http://stackoverflow.com/questions/16889123/how-to-add-objects-to-a-uiscrollview-that-extend-beyond-uiview-from-storyboard/16889377#16889377 – huggie Sep 21 '13 at 07:36
  • @huggie if you're wondering why you don't have a lot of upvotes for this question, it's because it's too specific (about scroll views). If it focused on the essential question about adding a child view controller programmatically, it would be more useful and probably get more upvotes. – David James Jun 15 '15 at 11:20

2 Answers2

31

I understand that the WWDC has an hour of video on it. But if you have watched any of their other videos it should become quite clear that time does not directly relate to complexity. This is how you use a container (or a child sub view controller) programmatically:

[self addChildViewController:child];        // 1
[self.view addSubview:child.view];          // 2
[child didMoveToParentViewController:self]; // 3

Pretty simple and only two extra lines of code compared to adding a subview. As you said, there are storyboard solutions but depending on your complexity, doing this through code may be easier. It really comes down to your preference though.

If you intend to animate adding the view, you should make the last call to didMoveToParentViewController in the completion block (i.e. after the animation has been completed).

Firo
  • 15,448
  • 3
  • 54
  • 74
  • 1
    Re: didMoveToParentViewController, from the docs: "If you are implementing your own container view controller, it must call the didMoveToParentViewController: method of the child view controller after the transition to the new controller is complete or, if there is no transition, immediately after calling the addChildViewController: method." – David James Jun 15 '15 at 11:15
  • And don't forget to call `-willMoveToParentViewController:` before calling `-addChildViewController:`. – erikprice Mar 07 '16 at 18:39
  • @erikprice `addChildViewController:` automatically calls `willMoveToParentViewController:` for you. The reason you need to manually call `didMoveToParentViewController` is because you are responsible for when it will be added. – Firo Mar 07 '16 at 19:18
  • 1
    @Firo Oh, I misread the docs. We have to call `-willMoveToParentViewController:` on the child being _removed_ from the custom container controller. But as you said, that method is called on the added child for us when we call `-addChildViewController:`. Thanks @Firo. – erikprice Mar 07 '16 at 19:37
1

Here are the helper functions I use to programmatically embed a child view controller in a view.

struct MyChildViewController {
  static func embed(
    viewControllerId: String,
    storyboardName: String,
    containerViewController: UIViewController,
    containerView: UIView) -> UIViewController? {

    guard let viewController = initViewController(viewControllerId, storyboardName: storyboardName)
      else { return nil }

    containerViewController.addChildViewController(viewController)
    containerView.addSubview(viewController.view)

    viewController.view.translatesAutoresizingMaskIntoConstraints = false

    MyConstraints.fillParent(
      viewController.view, parentView: containerView, margin: 0, vertically: true)

    MyConstraints.fillParent(
      viewController.view, parentView: containerView, margin: 0, vertically: false)

    viewController.didMoveToParentViewController(containerViewController)

    return viewController
  }

  static func initViewController(viewControllerId: String, storyboardName: String) -> UIViewController? {
    let storyboard = UIStoryboard(name: storyboardName, bundle: NSBundle.mainBundle())
    return storyboard.instantiateViewControllerWithIdentifier(viewControllerId)
  }
}

struct MyConstraints {
  static func fillParent(view: UIView, parentView: UIView, margin: CGFloat = 0,
    vertically: Bool) -> [NSLayoutConstraint] {

    var marginFormat = ""

    if margin != 0 {
      marginFormat = "-\(margin)-"
    }

    var format = "|\(marginFormat)[view]\(marginFormat)|"

    if vertically {
      format = "V:" + format
    }

    let constraints = NSLayoutConstraint.constraintsWithVisualFormat(format,
      options: [], metrics: nil,
      views: ["view": view])

    parentView.addConstraints(constraints)

    return constraints
  }
}

Usage:

let childWiewController = MyChildViewController.embed("MyViewControllerId", storyboardName: "MyStoryboardName", containerViewController: containerViewController, containerView: containerView)

Where:

  • "MyViewControllerId" - the storyboard ID of the child view controller that will be embedded.
  • "MyStoryboardName" - the name of the storyboard file with embedded view controller.
  • containerView - the view in your container view controller that will have the child view controller embedded.
Evgenii
  • 36,389
  • 27
  • 134
  • 170
  • Why do you have to do the fillParent stuff all by yourself? Why does iOS only do this for you if you use the UIContainerView through the storyboard? – Arjan Nov 18 '15 at 17:00
  • I think I figured that out -> childViewController.view.frame = containerView.frame does the trick for me – Arjan Nov 18 '15 at 17:07
  • 1
    @Arjan in my example I am embedding the child view controller programmatically, not from the storyboard. I create fillParent in order to create layout constraints. This will keep the child size same as parent if parent size changes (on orientation change, for example). – Evgenii Nov 19 '15 at 07:26