ios - 如何在没有故事板的容器视图中打开视图控制器
问题描述
我想通过按钮单击在现有视图控制器上打开视图控制器,而不使用情节提要。我该怎么做呢?这就是我的意思:
假设我们有三个可以滚动的视图控制器:
“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
and之前BlueViewController
,我们将定义protocol SwapViewControllerDelegate
:
protocol SwapViewControllerDelegate: AnyObject {
func swap()
}
GreenViewController
并且BlueViewController
将有一个delegate
符合此协议的协议,它将处理交换。我们将ContainerViewController
遵守此协议。
请注意,在其继承列表中SwapViewControllerDelegate
有AnyObject
,使其成为仅类协议——因此我们可以使委托变弱,以避免内存保留循环。
以下是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
'sbackgroundColor
和 the button
's 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($0) }
}
...
}
我使用了 VFL,但您可以手动设置自动布局约束,就像我们对上面的按钮所做的那样。使用自动布局,我们不必contentSize
自己设置滚动视图。有关将自动布局与 结合使用的更多信息UIScrollView
,请参阅技术说明 TN2154:UIScrollView 和自动布局。
现在最重要的setupChildViewControllers()
:
class ContainerViewController: UIViewController {
...
private func setupChildViewControllers() {
[redVC, greenVC, blueVC].forEach { addChild($0) }
let views = [
"redVC": redVC.view!,
"greenVC": greenVC.view!,
"blueVC": blueVC.view!,
]
views.values.forEach {
scrollView.addSubview($0)
$0.translatesAutoresizingMaskIntoConstraints = false
$0.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
$0.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($0) }
[redVC, greenVC, blueVC].forEach { $0.didMove(toParent: self) }
greenVC.view.isHidden = true
greenVC.delegate = self
blueVC.delegate = self
}
...
}
我们首先将每个添加[redVC, greenVC, blueVC]
为ContainerViewController
. 然后将view
子视图控制器的 's 添加到scrollView
. 将子视图控制器的 and 设置为widthAnchor
and ,以使它们全屏显示。此外,这也将在屏幕旋转时起作用。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
didMove(toParent:)
在我们添加 then 作为子视图控制器之后,我们应该调用子视图控制器上的方法。(如果您想知道什么didMove(toParent:)
和addChild(_:)
方法做什么,显然它们做的很少;请参阅addChildViewController 实际上做了什么?和didMoveToParentViewController 和 willMoveToParentViewController。)
最后,隐藏greenVC.view
,设置greenVC.delegate
和blueVC.delegate
到self
。那么当然,我们需要ContainerViewController
符合SwapViewControllerDelegate
:
extension ContainerViewController: SwapViewControllerDelegate {
func swap() {
greenVC.view.isHidden.toggle()
blueVC.view.isHidden.toggle()
}
}
而已!整个项目在这里上传。
我推荐阅读Implementing a Container View Controller,Apple 有详细的文档。(它是用 Objective-C 编写的,但实际上翻译成 Swift 很简单)
推荐阅读
- jquery - 为什么 removeClass() 有效但 hasClass() 无效?
- sql - 显示结果中匹配的参数
- linux - 在shell脚本中擦除字符串的一部分
- python - 解决 Kaggle 的泰坦尼克号机器学习
- python - python中基于颜色的图例,matplotlib
- javascript - 我必须在 Promise 构造函数中捕获错误吗?
- javascript - 如何使用 JavaScript 将使用单个分隔符的字符串解析为层次结构?
- java - 如何通过生成随机电子邮件地址。SQL Server 中的存储过程或函数
- php - 如何在php中显示数据库中的多个复选框值
- prolog - Prolog:未捕获的异常:错误(existence_error(procedure,s/3),top_level/0)