如何以编程方式添加容器视图

可以通过接口编辑器轻松地将容器视图添加到故事板中。添加容器视图后,容器视图具有占位符视图、嵌入式 segue 和(子)视图控制器。

但是,我无法找到以编程方式添加容器视图的方法。实际上,我甚至找不到一个名为 UIContainerView左右的类。

容器视图类的名称无疑是一个良好的开端。一个完整的指南,包括接下来将不胜感激。

我知道《查看控制器编程指南》 ,但是我不认为它和《容器查看器》的 Interface Builder 是一样的。例如,当正确设置约束时,(子)视图将适应容器视图中的大小更改。

91383 次浏览

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

container view

为了以编程方式实现这一点,您可以使用“视图控制器包含”:

  • 通过在故事板对象上调用 instantiateViewController(withIdentifier:)来实例化子视图控制器。
  • 在父视图控制器中调用 addChild
  • 使用 addSubview将视图控制器的 view添加到视图层次结构中(还可以适当地设置 frame或约束)。
  • 调用子视图控制器上的 didMove(toParent:)方法,将引用传递给父视图控制器。

参见 视图控制器编程指南中的 实现容器视图控制器< a href = “ https://developer.apple.com/library/ios/document/UIKit/Reference/UIViewController _ Class”rel = “ norefrer”> UIViewController Class Reference 。中的“实现容器视图控制器”部分


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

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)
}

注意,上面实际上并没有向层次结构添加“容器视图”。如果你想这么做,你可以这样做:

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)
}

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


在上面的示例中,我将 translatesAutosizingMaskIntoConstraints设置为 false,自己定义约束。您显然可以将 translatesAutosizingMaskIntoConstraints保留为 true,并为您添加的视图设置 frameautosizingMask,如果您愿意的话。


有关 Swift 3Swift 2的翻译,请参阅以前对这个答案的修订。

@ 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)

细节

  • Xcode 10.2(10E125) ,Swift 5

解决方案

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()
}
}

用法

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)
}
}

完整样本

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)")
}
}

结果

enter image description here enter image description here enter image description here

这是我在 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)
}


}

用法

@IBOutlet weak var container:UIView!


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

使用非故事板视图控制器的另一个嵌入函数。