How to add a Container View programmatically

后端 未结 4 613
难免孤独
难免孤独 2020-11-30 17:09

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 co

相关标签:
4条回答
  • 2020-11-30 17:47

    @Rob's answer in 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)
    
    0 讨论(0)
  • 2020-11-30 17:49

    Details

    • Xcode 10.2 (10E125), Swift 5

    Solution

    import UIKit
    
    class WeakObject {
        weak var object: AnyObject?
        init(object: AnyObject) { self.object = object}
    }
    
    class EmbedController {
    
        private weak var rootViewController: UIViewController?
        private var controllers = [WeakObject]()
        init (rootViewController: UIViewController) { self.rootViewController = rootViewController }
    
        func append(viewController: UIViewController) {
            guard let rootViewController = rootViewController else { return }
            controllers.append(WeakObject(object: viewController))
            rootViewController.addChild(viewController)
            rootViewController.view.addSubview(viewController.view)
        }
    
        deinit {
            if rootViewController == nil || controllers.isEmpty { return }
            for controller in controllers {
                if let controller = controller.object {
                    controller.view.removeFromSuperview()
                    controller.removeFromParent()
                }
            }
            controllers.removeAll()
        }
    }
    

    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

    import UIKit
    
    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

    0 讨论(0)
  • 2020-11-30 17:53

    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:

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

    • Instantiate the child view controller by calling instantiateViewController(withIdentifier:) on the storyboard object.
    • Call addChild in your parent view controller.
    • Add the view controller's view to your view hierarchy with addSubview (and also set the frame or constraints as appropriate).
    • Call the didMove(toParent:) method on the child view controller, passing the reference to the parent view controller.

    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.


    For example, in Swift 4.2 it might look like:

    override func viewDidLoad() {
        super.viewDidLoad()
    
        let controller = storyboard!.instantiateViewController(withIdentifier: "Second")
        addChild(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(toParent: 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")
        addChild(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(toParent: 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.


    In the examples above, I’m setting translatesAutosizingMaskIntoConstraints to false defining the constraints myself. You obviously can leave translatesAutosizingMaskIntoConstraints as true and set both the frame and the autosizingMask for the views you add, if you’d prefer.


    See previous revisions of this answer for Swift 3 and Swift 2 renditions.

    0 讨论(0)
  • 2020-11-30 18:10

    Here is my code in swift 5.

    class ViewEmbedder {
    class func embed(
        parent:UIViewController,
        container:UIView,
        child:UIViewController,
        previous:UIViewController?){
    
        if let previous = previous {
            removeFromParent(vc: previous)
        }
        child.willMove(toParent: parent)
        parent.addChild(child)
        container.addSubview(child.view)
        child.didMove(toParent: 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(toParent: nil)
        vc.view.removeFromSuperview()
        vc.removeFromParent()
    }
    
    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.children.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.

    0 讨论(0)
提交回复
热议问题