如何以编程方式添加Container View

时间:2021-01-02 16:47:42

A Container View can be easily added into a storyboard through Interface Editor. When added, a Container View is of a placeholder view, an embed segue, and a (child) view controller.

可以通过界面编辑器轻松地将容器视图添加到故事板中。添加时,Container View具有占位符视图,嵌入segue和(子)视图控制器。

However, I am not able to find a way to add a Container View programmatically. Actually, I am not even able to find a class named UIContainerView or so.

但是,我无法找到以编程方式添加Container View的方法。实际上,我甚至找不到一个名为UIContainerView的类。

A name for the class of Container View is surely a good start. A complete guide including the segue will be much appreciated.

Container View类的名称肯定是一个好的开始。我们非常感谢包括segue在内的完整指南。

I am aware of View Controller Programming Guide, but I do not regard it as the same as the way Interface Builder does for Container Viewer. For example, when the constraints are properly set, the (child) view will adapts to the size changes in Container View.

我知道View Controller Programming Guide,但我不认为它与Interface Builder对Container Viewer的方式相同。例如,正确设置约束后,(子)视图将适应容器视图中的大小更改。

4 个解决方案

#1


121  

A storyboard "container view" is just a standard UIView object. There is no special "container view" type. In fact, if you look at the view hierarchy, you can see that the "container view" is a standard UIView:

故事板“容器视图”只是一个标准的UIView对象。没有特殊的“容器视图”类型。实际上,如果查看视图层次结构,可以看到“容器视图”是标准的UIView:

如何以编程方式添加Container View

To achieve this programmatically, you employ "view controller containment":

要以编程方式实现此目的,请使用“视图控制器包含”:

  • Instantiate the child view controller by calling instantiateViewController(withIdentifier:) on the storyboard object.
  • 通过在storyboard对象上调用instantiateViewController(withIdentifier :)来实例化子视图控制器。
  • Call addChildViewController in your parent view controller.
  • 在父视图控制器中调用addChildViewController。
  • Add the view controller's view to your view hierarchy with addSubview (and also set the frame or constraints as appropriate).
  • 使用addSubview将视图控制器的视图添加到视图层次结构中(并根据需要设置框架或约束)。
  • Call the didMove(toParentViewController:) method on the child view controller, passing the reference to the parent view controller.
  • 在子视图控制器上调用didMove(toParentViewController :)方法,将引用传递给父视图控制器。

See Implementing a Container View Controller in the View Controller Programming Guide and the "Implementing a Container View Controller" section of the UIViewController Class Reference.

请参阅“View Controller编程指南”中的实现容器视图控制器和UIViewController类参考的“实现容器视图控制器”部分。


For example, in Swift 3 it might look like:

例如,在Swift 3中它可能看起来像:

override func viewDidLoad() {
    super.viewDidLoad()

    let controller = storyboard!.instantiateViewController(withIdentifier: "Second")
    addChildViewController(controller)
    controller.view.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(controller.view)

    NSLayoutConstraint.activate([
        controller.view.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
        controller.view.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10),
        controller.view.topAnchor.constraint(equalTo: view.topAnchor, constant: 10),
        controller.view.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10)
    ])

    controller.didMove(toParentViewController: self)
}

Note, the above doesn't actually add a "container view" to the hierarchy. If you want to do that, you'd do something like:

注意,上面实际上并没有向层次结构添加“容器视图”。如果你想这样做,你会做类似的事情:

override func viewDidLoad() {
    super.viewDidLoad()

    // add container

    let containerView = UIView()
    containerView.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(containerView)
    NSLayoutConstraint.activate([
        containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
        containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10),
        containerView.topAnchor.constraint(equalTo: view.topAnchor, constant: 10),
        containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10),
    ])

    // add child view controller view to container

    let controller = storyboard!.instantiateViewController(withIdentifier: "Second")
    addChildViewController(controller)
    controller.view.translatesAutoresizingMaskIntoConstraints = false
    containerView.addSubview(controller.view)

    NSLayoutConstraint.activate([
        controller.view.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
        controller.view.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
        controller.view.topAnchor.constraint(equalTo: containerView.topAnchor),
        controller.view.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
    ])

    controller.didMove(toParentViewController: self)
}

This latter pattern is extremely useful if ever transitioning between different child view controllers and you just want to make sure one child's view is in the same location and the previous child's view (i.e. all the unique constraints for the placement are dictated by the container view, rather than needing to rebuild these constraints each time). But if just performing simple view containment, the need for this separate container view is less compelling.

如果在不同的子视图控制器之间进行转换,并且您只是想确保一个子视图位于同一位置和前一个子视图(即放置的所有唯一约束都由容器视图决定,则后一种模式非常有用,而不是每次都需要重建这些约束。但是,如果只是执行简单的视图包含,那么对这个单独的容器视图的需求就不那么引人注目了。


For Swift 2 renditions, see previous revision of this answer.

对于Swift 2再现,请参阅此答案的先前修订版。

#2


21  

@Rob's answer in Swift 3:

@ Rob在Swift 3中的回答:

    // add container

    let containerView = UIView()
    containerView.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(containerView)
    NSLayoutConstraint.activate([
        containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
        containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10),
        containerView.topAnchor.constraint(equalTo: view.topAnchor, constant: 10),
        containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10),
        ])

    // add child view controller view to container

    let controller = storyboard!.instantiateViewController(withIdentifier: "Second")
    addChildViewController(controller)
    controller.view.translatesAutoresizingMaskIntoConstraints = false
    containerView.addSubview(controller.view)

    NSLayoutConstraint.activate([
        controller.view.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
        controller.view.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
        controller.view.topAnchor.constraint(equalTo: containerView.topAnchor),
        controller.view.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
        ])

    controller.didMove(toParentViewController: self)

#3


9  

Details

xCode: 9.1, Swift 4

xCode:9.1,Swift 4

Solution

class EmbedController {

    public private(set) weak var rootViewController: UIViewController?

    public private(set) var controllers = [UIViewController]()

    init (rootViewController: UIViewController) {
        self.rootViewController = rootViewController
    }

    func append(viewController: UIViewController) {
        if let rootViewController = self.rootViewController {
            controllers.append(viewController)
            rootViewController.addChildViewController(viewController)
            rootViewController.view.addSubview(viewController.view)
        }
    }

    deinit {
        if self.rootViewController != nil {
            for controller in controllers {
                controller.view.removeFromSuperview()
                controller.removeFromParentViewController()
            }
            controllers.removeAll()
            self.rootViewController = nil
        }
    }
}

Usage

class SampleViewController: UIViewController {
    private var embedController: EmbedController?

    override func viewDidLoad() {
        super.viewDidLoad()
        embedController = EmbedController(rootViewController: self)

        let newViewController = ViewControllerWithButton()
        newViewController.view.frame = CGRect(origin: CGPoint(x: 50, y: 150), size: CGSize(width: 200, height: 80))
        newViewController.view.backgroundColor = .lightGray
        embedController?.append(viewController: newViewController)
    }
}

Full sample

ViewController

视图控制器

import UIKit

class ViewController: UIViewController {

    private var embedController: EmbedController?
    private var button: UIButton?
    private let addEmbedButtonTitle = "Add embed"

    override func viewDidLoad() {
        super.viewDidLoad()

        button = UIButton(frame: CGRect(x: 50, y: 50, width: 150, height: 20))
        button?.setTitle(addEmbedButtonTitle, for: .normal)
        button?.setTitleColor(.black, for: .normal)
        button?.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
        view.addSubview(button!)

        print("viewDidLoad")
        printChildViewControllesInfo()
    }

    func addChildViewControllers() {

        var newViewController = ViewControllerWithButton()
        newViewController.view.frame = CGRect(origin: CGPoint(x: 50, y: 150), size: CGSize(width: 200, height: 80))
        newViewController.view.backgroundColor = .lightGray
        embedController?.append(viewController: newViewController)

        newViewController = ViewControllerWithButton()
        newViewController.view.frame = CGRect(origin: CGPoint(x: 50, y: 250), size: CGSize(width: 200, height: 80))
        newViewController.view.backgroundColor = .blue
        embedController?.append(viewController: newViewController)

        print("\nChildViewControllers added")
        printChildViewControllesInfo()
    }

    @objc func buttonTapped() {

        if embedController == nil {
            embedController = EmbedController(rootViewController: self)
            button?.setTitle("Remove embed", for: .normal)
            addChildViewControllers()
        } else {
            embedController = nil
            print("\nChildViewControllers removed")
            printChildViewControllesInfo()
            button?.setTitle(addEmbedButtonTitle, for: .normal)
        }
    }

    func printChildViewControllesInfo() {
        print("view.subviews.count: \(view.subviews.count)")
        print("childViewControllers.count: \(childViewControllers.count)")
    }
}

ViewControllerWithButton

ViewControllerWithButton

class ViewControllerWithButton:UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    private func addButon() {
        let buttonWidth: CGFloat = 150
        let buttonHeight: CGFloat = 20
        let frame = CGRect(x: (view.frame.width-buttonWidth)/2, y: (view.frame.height-buttonHeight)/2, width: buttonWidth, height: buttonHeight)
        let button = UIButton(frame: frame)
        button.setTitle("Button", for: .normal)
        button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
        view.addSubview(button)
    }

    override func viewWillLayoutSubviews() {
        addButon()
    }

    @objc func buttonTapped() {
        print("Button tapped in \(self)")
    }
}

Results

如何以编程方式添加Container View 如何以编程方式添加Container View 如何以编程方式添加Container View

#4


6  

Here is my code in swift 3, also work in swift 4.

这是我在swift 3中的代码,也适用于swift 4。

class ViewEmbedder {
    class func embed(
        parent:UIViewController,
        container:UIView,
        child:UIViewController,
        previous:UIViewController?){

        if let previous = previous {
            removeFromParent(vc: previous)
        }
        child.willMove(toParentViewController: parent)
        parent.addChildViewController(child)
        container.addSubview(child.view)
        child.didMove(toParentViewController: parent)
        let w = container.frame.size.width;
        let h = container.frame.size.height;
        child.view.frame = CGRect(x: 0, y: 0, width: w, height: h)
    }

    class func removeFromParent(vc:UIViewController){
        vc.willMove(toParentViewController: nil)
        vc.view.removeFromSuperview()
        vc.removeFromParentViewController()
    }

    class func embed(withIdentifier id:String, parent:UIViewController, container:UIView, completion:((UIViewController)->Void)? = nil){
        let vc = parent.storyboard!.instantiateViewController(withIdentifier: id)
        embed(
            parent: parent,
            container: container,
            child: vc,
            previous: parent.childViewControllers.first
        )
        completion?(vc)
    }
}

Usage

@IBOutlet weak var container:UIView!

ViewEmbedder.embed(
    withIdentifier: "MyVC", // Storyboard ID
    parent: self,
    container: self.container){ vc in
    // do things when embed complete
}

Use the other embed function with non-storyboard view controller.

将其他嵌入函数与非故事板视图控制器一起使用。

#1


121  

A storyboard "container view" is just a standard UIView object. There is no special "container view" type. In fact, if you look at the view hierarchy, you can see that the "container view" is a standard UIView:

故事板“容器视图”只是一个标准的UIView对象。没有特殊的“容器视图”类型。实际上,如果查看视图层次结构,可以看到“容器视图”是标准的UIView:

如何以编程方式添加Container View

To achieve this programmatically, you employ "view controller containment":

要以编程方式实现此目的,请使用“视图控制器包含”:

  • Instantiate the child view controller by calling instantiateViewController(withIdentifier:) on the storyboard object.
  • 通过在storyboard对象上调用instantiateViewController(withIdentifier :)来实例化子视图控制器。
  • Call addChildViewController in your parent view controller.
  • 在父视图控制器中调用addChildViewController。
  • Add the view controller's view to your view hierarchy with addSubview (and also set the frame or constraints as appropriate).
  • 使用addSubview将视图控制器的视图添加到视图层次结构中(并根据需要设置框架或约束)。
  • Call the didMove(toParentViewController:) method on the child view controller, passing the reference to the parent view controller.
  • 在子视图控制器上调用didMove(toParentViewController :)方法,将引用传递给父视图控制器。

See Implementing a Container View Controller in the View Controller Programming Guide and the "Implementing a Container View Controller" section of the UIViewController Class Reference.

请参阅“View Controller编程指南”中的实现容器视图控制器和UIViewController类参考的“实现容器视图控制器”部分。


For example, in Swift 3 it might look like:

例如,在Swift 3中它可能看起来像:

override func viewDidLoad() {
    super.viewDidLoad()

    let controller = storyboard!.instantiateViewController(withIdentifier: "Second")
    addChildViewController(controller)
    controller.view.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(controller.view)

    NSLayoutConstraint.activate([
        controller.view.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
        controller.view.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10),
        controller.view.topAnchor.constraint(equalTo: view.topAnchor, constant: 10),
        controller.view.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10)
    ])

    controller.didMove(toParentViewController: self)
}

Note, the above doesn't actually add a "container view" to the hierarchy. If you want to do that, you'd do something like:

注意,上面实际上并没有向层次结构添加“容器视图”。如果你想这样做,你会做类似的事情:

override func viewDidLoad() {
    super.viewDidLoad()

    // add container

    let containerView = UIView()
    containerView.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(containerView)
    NSLayoutConstraint.activate([
        containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
        containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10),
        containerView.topAnchor.constraint(equalTo: view.topAnchor, constant: 10),
        containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10),
    ])

    // add child view controller view to container

    let controller = storyboard!.instantiateViewController(withIdentifier: "Second")
    addChildViewController(controller)
    controller.view.translatesAutoresizingMaskIntoConstraints = false
    containerView.addSubview(controller.view)

    NSLayoutConstraint.activate([
        controller.view.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
        controller.view.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
        controller.view.topAnchor.constraint(equalTo: containerView.topAnchor),
        controller.view.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
    ])

    controller.didMove(toParentViewController: self)
}

This latter pattern is extremely useful if ever transitioning between different child view controllers and you just want to make sure one child's view is in the same location and the previous child's view (i.e. all the unique constraints for the placement are dictated by the container view, rather than needing to rebuild these constraints each time). But if just performing simple view containment, the need for this separate container view is less compelling.

如果在不同的子视图控制器之间进行转换,并且您只是想确保一个子视图位于同一位置和前一个子视图(即放置的所有唯一约束都由容器视图决定,则后一种模式非常有用,而不是每次都需要重建这些约束。但是,如果只是执行简单的视图包含,那么对这个单独的容器视图的需求就不那么引人注目了。


For Swift 2 renditions, see previous revision of this answer.

对于Swift 2再现,请参阅此答案的先前修订版。

#2


21  

@Rob's answer in Swift 3:

@ Rob在Swift 3中的回答:

    // add container

    let containerView = UIView()
    containerView.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(containerView)
    NSLayoutConstraint.activate([
        containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
        containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10),
        containerView.topAnchor.constraint(equalTo: view.topAnchor, constant: 10),
        containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10),
        ])

    // add child view controller view to container

    let controller = storyboard!.instantiateViewController(withIdentifier: "Second")
    addChildViewController(controller)
    controller.view.translatesAutoresizingMaskIntoConstraints = false
    containerView.addSubview(controller.view)

    NSLayoutConstraint.activate([
        controller.view.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
        controller.view.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
        controller.view.topAnchor.constraint(equalTo: containerView.topAnchor),
        controller.view.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
        ])

    controller.didMove(toParentViewController: self)

#3


9  

Details

xCode: 9.1, Swift 4

xCode:9.1,Swift 4

Solution

class EmbedController {

    public private(set) weak var rootViewController: UIViewController?

    public private(set) var controllers = [UIViewController]()

    init (rootViewController: UIViewController) {
        self.rootViewController = rootViewController
    }

    func append(viewController: UIViewController) {
        if let rootViewController = self.rootViewController {
            controllers.append(viewController)
            rootViewController.addChildViewController(viewController)
            rootViewController.view.addSubview(viewController.view)
        }
    }

    deinit {
        if self.rootViewController != nil {
            for controller in controllers {
                controller.view.removeFromSuperview()
                controller.removeFromParentViewController()
            }
            controllers.removeAll()
            self.rootViewController = nil
        }
    }
}

Usage

class SampleViewController: UIViewController {
    private var embedController: EmbedController?

    override func viewDidLoad() {
        super.viewDidLoad()
        embedController = EmbedController(rootViewController: self)

        let newViewController = ViewControllerWithButton()
        newViewController.view.frame = CGRect(origin: CGPoint(x: 50, y: 150), size: CGSize(width: 200, height: 80))
        newViewController.view.backgroundColor = .lightGray
        embedController?.append(viewController: newViewController)
    }
}

Full sample

ViewController

视图控制器

import UIKit

class ViewController: UIViewController {

    private var embedController: EmbedController?
    private var button: UIButton?
    private let addEmbedButtonTitle = "Add embed"

    override func viewDidLoad() {
        super.viewDidLoad()

        button = UIButton(frame: CGRect(x: 50, y: 50, width: 150, height: 20))
        button?.setTitle(addEmbedButtonTitle, for: .normal)
        button?.setTitleColor(.black, for: .normal)
        button?.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
        view.addSubview(button!)

        print("viewDidLoad")
        printChildViewControllesInfo()
    }

    func addChildViewControllers() {

        var newViewController = ViewControllerWithButton()
        newViewController.view.frame = CGRect(origin: CGPoint(x: 50, y: 150), size: CGSize(width: 200, height: 80))
        newViewController.view.backgroundColor = .lightGray
        embedController?.append(viewController: newViewController)

        newViewController = ViewControllerWithButton()
        newViewController.view.frame = CGRect(origin: CGPoint(x: 50, y: 250), size: CGSize(width: 200, height: 80))
        newViewController.view.backgroundColor = .blue
        embedController?.append(viewController: newViewController)

        print("\nChildViewControllers added")
        printChildViewControllesInfo()
    }

    @objc func buttonTapped() {

        if embedController == nil {
            embedController = EmbedController(rootViewController: self)
            button?.setTitle("Remove embed", for: .normal)
            addChildViewControllers()
        } else {
            embedController = nil
            print("\nChildViewControllers removed")
            printChildViewControllesInfo()
            button?.setTitle(addEmbedButtonTitle, for: .normal)
        }
    }

    func printChildViewControllesInfo() {
        print("view.subviews.count: \(view.subviews.count)")
        print("childViewControllers.count: \(childViewControllers.count)")
    }
}

ViewControllerWithButton

ViewControllerWithButton

class ViewControllerWithButton:UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    private func addButon() {
        let buttonWidth: CGFloat = 150
        let buttonHeight: CGFloat = 20
        let frame = CGRect(x: (view.frame.width-buttonWidth)/2, y: (view.frame.height-buttonHeight)/2, width: buttonWidth, height: buttonHeight)
        let button = UIButton(frame: frame)
        button.setTitle("Button", for: .normal)
        button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
        view.addSubview(button)
    }

    override func viewWillLayoutSubviews() {
        addButon()
    }

    @objc func buttonTapped() {
        print("Button tapped in \(self)")
    }
}

Results

如何以编程方式添加Container View 如何以编程方式添加Container View 如何以编程方式添加Container View

#4


6  

Here is my code in swift 3, also work in swift 4.

这是我在swift 3中的代码,也适用于swift 4。

class ViewEmbedder {
    class func embed(
        parent:UIViewController,
        container:UIView,
        child:UIViewController,
        previous:UIViewController?){

        if let previous = previous {
            removeFromParent(vc: previous)
        }
        child.willMove(toParentViewController: parent)
        parent.addChildViewController(child)
        container.addSubview(child.view)
        child.didMove(toParentViewController: parent)
        let w = container.frame.size.width;
        let h = container.frame.size.height;
        child.view.frame = CGRect(x: 0, y: 0, width: w, height: h)
    }

    class func removeFromParent(vc:UIViewController){
        vc.willMove(toParentViewController: nil)
        vc.view.removeFromSuperview()
        vc.removeFromParentViewController()
    }

    class func embed(withIdentifier id:String, parent:UIViewController, container:UIView, completion:((UIViewController)->Void)? = nil){
        let vc = parent.storyboard!.instantiateViewController(withIdentifier: id)
        embed(
            parent: parent,
            container: container,
            child: vc,
            previous: parent.childViewControllers.first
        )
        completion?(vc)
    }
}

Usage

@IBOutlet weak var container:UIView!

ViewEmbedder.embed(
    withIdentifier: "MyVC", // Storyboard ID
    parent: self,
    container: self.container){ vc in
    // do things when embed complete
}

Use the other embed function with non-storyboard view controller.

将其他嵌入函数与非故事板视图控制器一起使用。