如何在没有情节提要的情况下在容器视图中打开视图控制器
How to open view controllers in a container view without storyboard
我想在不使用故事板的情况下通过单击按钮在现有视图控制器上打开一个视图控制器。我该怎么做呢?这就是我的意思:
假设我们有三个视图控制器,我可以在它们之间滚动:
"zeroVC"、"oneVC" 和 "twoVC"
当我按下 "twoVC" 上的按钮时,我现在想在以下之间滚动:
"zeroVC"、"oneVC" 和 "threeVC"
我尝试通过堆栈溢出查看所有内容,但它们都使用故事板。
假设我们有四个视图控制器:RedViewController
、GreenViewController
、BlueViewController
,以及一个包含所有视图控制器的 ContainerViewController
.
虽然您提到了一个包含三个子项的滚动视图控制器,但我们会将其设置为两个屏幕以保持简单。
以下方法是可扩展的,因此您可以轻松地将其用于任意数量的视图控制器。
我们的 RedViewController
有 7 行:
class RedViewController: UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .red
self.view = view
}
}
在我们继续 GreenViewController
和 BlueViewController
之前,我们将定义 protocol SwapViewControllerDelegate
:
protocol SwapViewControllerDelegate: AnyObject {
func swap()
}
GreenViewController
和 BlueViewController
将有一个符合此协议的 delegate
,它将处理交换。
我们会让ContainerViewController
遵守这个协议。
请注意 SwapViewControllerDelegate
在其继承列表中有 AnyObject
使其成为一个 class-only 协议——因此我们可以使委托弱化,以避免内存保留循环。
以下为GreenViewController
:
class GreenViewController: UIViewController {
weak var delegate: SwapViewControllerDelegate?
override func loadView() {
let view = UIView()
view.backgroundColor = .green
self.view = view
}
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton()
button.setTitle("Swap Me!", for: .normal)
button.setTitleColor(.black, for: .normal)
button.titleLabel?.font = .boldSystemFont(ofSize: 50)
button.addTarget(
self,
action: #selector(swapButtonWasTouched),
for: .touchUpInside)
view.addSubview(button)
// Put button at the center of the view
button.translatesAutoresizingMaskIntoConstraints = false
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
@objc private func swapButtonWasTouched(_ sender: UIButton) {
delegate?.swap()
}
}
它有 weak var delegate: SwapViewControllerDelegate?
,它会在 viewDidLoad
中添加的按钮被触摸时处理交换,触发 swapButtonWasTouched
方法。
BlueViewController
同样实现:
class BlueViewController: UIViewController {
weak var delegate: SwapViewControllerDelegate?
override func loadView() {
let view = UIView()
view.backgroundColor = .blue
self.view = view
}
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton()
button.setTitle("Swap Me!", for: .normal)
button.setTitleColor(.white, for: .normal)
button.titleLabel?.font = .boldSystemFont(ofSize: 50)
button.addTarget(
self,
action: #selector(swapButtonWasTouched),
for: .touchUpInside)
view.addSubview(button)
// Put button at the center of the view
button.translatesAutoresizingMaskIntoConstraints = false
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
@objc private func swapButtonWasTouched(_ sender: UIButton) {
delegate?.swap()
}
}
唯一的区别是 view
的 backgroundColor
和 button
的 titleColor
。
最后,我们来看看ContainerViewController
。
ContainerViewController
有四个属性:
class ContainerViewController: UIViewController {
let redVC = RedViewController()
let greenVC = GreenViewController()
let blueVC = BlueViewController()
private lazy var scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.bounces = false
scrollView.isPagingEnabled = true
return scrollView
}()
...
}
scrollView
是将包含子视图控制器的视图,redVC
、greenVC
和 blueVC
。
我们将使用自动布局,所以不要忘记将 translatesAutoresizingMaskIntoConstraints
标记为 false
.
现在,设置 scrollView
:
的自动布局约束
class ContainerViewController: UIViewController {
...
private func setupScrollView() {
view.addSubview(scrollView)
let views = ["scrollView": scrollView]
[
NSLayoutConstraint.constraints(
withVisualFormat: "H:|[scrollView]|",
metrics: nil,
views: views),
NSLayoutConstraint.constraints(
withVisualFormat: "V:|[scrollView]|",
metrics: nil,
views: views),
]
.forEach { NSLayoutConstraint.activate([=15=]) }
}
...
}
我使用了 VFL,但您可以像我们为上面的按钮所做的那样手动设置自动布局约束。
使用自动布局,我们不必自己设置 scrollView 的 contentSize
。
有关将自动布局与 UIScrollView
一起使用的更多信息,请参阅 Technical Note TN2154: UIScrollView And Autolayout。
现在最重要的setupChildViewControllers()
:
class ContainerViewController: UIViewController {
...
private func setupChildViewControllers() {
[redVC, greenVC, blueVC].forEach { addChild([=16=]) }
let views = [
"redVC": redVC.view!,
"greenVC": greenVC.view!,
"blueVC": blueVC.view!,
]
views.values.forEach {
scrollView.addSubview([=16=])
[=16=].translatesAutoresizingMaskIntoConstraints = false
[=16=].widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
[=16=].heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
}
[
NSLayoutConstraint.constraints(
withVisualFormat: "H:|[redVC][greenVC]|",
options: .alignAllTop,
metrics: nil,
views: views),
NSLayoutConstraint.constraints(
withVisualFormat: "H:|[redVC][blueVC]|",
options: .alignAllTop,
metrics: nil,
views: views),
NSLayoutConstraint.constraints(
withVisualFormat: "V:|[redVC(==greenVC,==blueVC)]|",
metrics: nil,
views: views),
]
.forEach { NSLayoutConstraint.activate([=16=]) }
[redVC, greenVC, blueVC].forEach { [=16=].didMove(toParent: self) }
greenVC.view.isHidden = true
greenVC.delegate = self
blueVC.delegate = self
}
...
}
我们首先将每个 [redVC, greenVC, blueVC]
添加为 ContainerViewController
的子视图控制器。
然后将 view
的子视图控制器添加到 scrollView
。
将子视图控制器的 widthAnchor
和 heightAnchor
设置为 view.widthAnchor
和 view.heightAnchor
,以使它们全屏显示。
此外,这在屏幕旋转时也有效。
使用views
字典,我们使用VFL来设置自动布局约束。
我们将 greenVC.view
放在 redVC.view
的右边:H:|[redVC][greenVC]|
,blueVC.view
的右边也是如此:H:|[redVC][blueVC]|
。
要固定 greenVC.view
和 blueVC.view
的垂直位置,请将 .alignAllTop
选项添加到约束中。
然后为redVC.view
应用垂直布局,并设置greenVC.view
和blueVC.view
的高度:"V:|[redVC(==greenVC,==blueVC)]|
。
设置垂直位置,因为我们在设置水平约束时使用 .alignAllTop
。
添加 then 作为子视图控制器后,我们应该在子视图控制器上调用 didMove(toParent:)
方法。
(如果您想知道 didMove(toParent:)
和 addChild(_:)
方法做了什么,显然它们做的很少;请参阅 What does addChildViewController actually do? and 。)
最后隐藏greenVC.view
,将greenVC.delegate
和blueVC.delegate
设为self
。
那么当然我们需要ContainerViewController
来符合SwapViewControllerDelegate
:
extension ContainerViewController: SwapViewControllerDelegate {
func swap() {
greenVC.view.isHidden.toggle()
blueVC.view.isHidden.toggle()
}
}
就是这样!
整个项目上传here.
我推荐阅读 Implementing a Container View Controller,Apple 对其进行了详细记录。 (里面写的是Objective-C,其实直接翻译成Swift)
我想在不使用故事板的情况下通过单击按钮在现有视图控制器上打开一个视图控制器。我该怎么做呢?这就是我的意思:
假设我们有三个视图控制器,我可以在它们之间滚动:
"zeroVC"、"oneVC" 和 "twoVC"
当我按下 "twoVC" 上的按钮时,我现在想在以下之间滚动:
"zeroVC"、"oneVC" 和 "threeVC"
我尝试通过堆栈溢出查看所有内容,但它们都使用故事板。
假设我们有四个视图控制器:RedViewController
、GreenViewController
、BlueViewController
,以及一个包含所有视图控制器的 ContainerViewController
.
虽然您提到了一个包含三个子项的滚动视图控制器,但我们会将其设置为两个屏幕以保持简单。 以下方法是可扩展的,因此您可以轻松地将其用于任意数量的视图控制器。
我们的 RedViewController
有 7 行:
class RedViewController: UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .red
self.view = view
}
}
在我们继续 GreenViewController
和 BlueViewController
之前,我们将定义 protocol SwapViewControllerDelegate
:
protocol SwapViewControllerDelegate: AnyObject {
func swap()
}
GreenViewController
和 BlueViewController
将有一个符合此协议的 delegate
,它将处理交换。
我们会让ContainerViewController
遵守这个协议。
请注意 SwapViewControllerDelegate
在其继承列表中有 AnyObject
使其成为一个 class-only 协议——因此我们可以使委托弱化,以避免内存保留循环。
以下为GreenViewController
:
class GreenViewController: UIViewController {
weak var delegate: SwapViewControllerDelegate?
override func loadView() {
let view = UIView()
view.backgroundColor = .green
self.view = view
}
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton()
button.setTitle("Swap Me!", for: .normal)
button.setTitleColor(.black, for: .normal)
button.titleLabel?.font = .boldSystemFont(ofSize: 50)
button.addTarget(
self,
action: #selector(swapButtonWasTouched),
for: .touchUpInside)
view.addSubview(button)
// Put button at the center of the view
button.translatesAutoresizingMaskIntoConstraints = false
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
@objc private func swapButtonWasTouched(_ sender: UIButton) {
delegate?.swap()
}
}
它有 weak var delegate: SwapViewControllerDelegate?
,它会在 viewDidLoad
中添加的按钮被触摸时处理交换,触发 swapButtonWasTouched
方法。
BlueViewController
同样实现:
class BlueViewController: UIViewController {
weak var delegate: SwapViewControllerDelegate?
override func loadView() {
let view = UIView()
view.backgroundColor = .blue
self.view = view
}
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton()
button.setTitle("Swap Me!", for: .normal)
button.setTitleColor(.white, for: .normal)
button.titleLabel?.font = .boldSystemFont(ofSize: 50)
button.addTarget(
self,
action: #selector(swapButtonWasTouched),
for: .touchUpInside)
view.addSubview(button)
// Put button at the center of the view
button.translatesAutoresizingMaskIntoConstraints = false
button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
@objc private func swapButtonWasTouched(_ sender: UIButton) {
delegate?.swap()
}
}
唯一的区别是 view
的 backgroundColor
和 button
的 titleColor
。
最后,我们来看看ContainerViewController
。
ContainerViewController
有四个属性:
class ContainerViewController: UIViewController {
let redVC = RedViewController()
let greenVC = GreenViewController()
let blueVC = BlueViewController()
private lazy var scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.bounces = false
scrollView.isPagingEnabled = true
return scrollView
}()
...
}
scrollView
是将包含子视图控制器的视图,redVC
、greenVC
和 blueVC
。
我们将使用自动布局,所以不要忘记将 translatesAutoresizingMaskIntoConstraints
标记为 false
.
现在,设置 scrollView
:
class ContainerViewController: UIViewController {
...
private func setupScrollView() {
view.addSubview(scrollView)
let views = ["scrollView": scrollView]
[
NSLayoutConstraint.constraints(
withVisualFormat: "H:|[scrollView]|",
metrics: nil,
views: views),
NSLayoutConstraint.constraints(
withVisualFormat: "V:|[scrollView]|",
metrics: nil,
views: views),
]
.forEach { NSLayoutConstraint.activate([=15=]) }
}
...
}
我使用了 VFL,但您可以像我们为上面的按钮所做的那样手动设置自动布局约束。
使用自动布局,我们不必自己设置 scrollView 的 contentSize
。
有关将自动布局与 UIScrollView
一起使用的更多信息,请参阅 Technical Note TN2154: UIScrollView And Autolayout。
现在最重要的setupChildViewControllers()
:
class ContainerViewController: UIViewController {
...
private func setupChildViewControllers() {
[redVC, greenVC, blueVC].forEach { addChild([=16=]) }
let views = [
"redVC": redVC.view!,
"greenVC": greenVC.view!,
"blueVC": blueVC.view!,
]
views.values.forEach {
scrollView.addSubview([=16=])
[=16=].translatesAutoresizingMaskIntoConstraints = false
[=16=].widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
[=16=].heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
}
[
NSLayoutConstraint.constraints(
withVisualFormat: "H:|[redVC][greenVC]|",
options: .alignAllTop,
metrics: nil,
views: views),
NSLayoutConstraint.constraints(
withVisualFormat: "H:|[redVC][blueVC]|",
options: .alignAllTop,
metrics: nil,
views: views),
NSLayoutConstraint.constraints(
withVisualFormat: "V:|[redVC(==greenVC,==blueVC)]|",
metrics: nil,
views: views),
]
.forEach { NSLayoutConstraint.activate([=16=]) }
[redVC, greenVC, blueVC].forEach { [=16=].didMove(toParent: self) }
greenVC.view.isHidden = true
greenVC.delegate = self
blueVC.delegate = self
}
...
}
我们首先将每个 [redVC, greenVC, blueVC]
添加为 ContainerViewController
的子视图控制器。
然后将 view
的子视图控制器添加到 scrollView
。
将子视图控制器的 widthAnchor
和 heightAnchor
设置为 view.widthAnchor
和 view.heightAnchor
,以使它们全屏显示。
此外,这在屏幕旋转时也有效。
使用views
字典,我们使用VFL来设置自动布局约束。
我们将 greenVC.view
放在 redVC.view
的右边:H:|[redVC][greenVC]|
,blueVC.view
的右边也是如此:H:|[redVC][blueVC]|
。
要固定 greenVC.view
和 blueVC.view
的垂直位置,请将 .alignAllTop
选项添加到约束中。
然后为redVC.view
应用垂直布局,并设置greenVC.view
和blueVC.view
的高度:"V:|[redVC(==greenVC,==blueVC)]|
。
设置垂直位置,因为我们在设置水平约束时使用 .alignAllTop
。
添加 then 作为子视图控制器后,我们应该在子视图控制器上调用 didMove(toParent:)
方法。
(如果您想知道 didMove(toParent:)
和 addChild(_:)
方法做了什么,显然它们做的很少;请参阅 What does addChildViewController actually do? and
最后隐藏greenVC.view
,将greenVC.delegate
和blueVC.delegate
设为self
。
那么当然我们需要ContainerViewController
来符合SwapViewControllerDelegate
:
extension ContainerViewController: SwapViewControllerDelegate {
func swap() {
greenVC.view.isHidden.toggle()
blueVC.view.isHidden.toggle()
}
}
就是这样! 整个项目上传here.
我推荐阅读 Implementing a Container View Controller,Apple 对其进行了详细记录。 (里面写的是Objective-C,其实直接翻译成Swift)