我的视图控制器层次结构如下:
UINavigationController
,其根视图控制器是通常的UITableViewController
。表视图显示了一个字母列表。ContainerViewController
。它包含一个嵌入式ContentViewController
,其作用是在屏幕上显示所选字母。内容视图控制器将要显示的字母存储为属性letter: String
,应在其视图按下屏幕之前设置。
class ContentViewController: UIViewController {
var letter = "-"
@IBOutlet private weak var label: UILabel!
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
label.text = letter
}
}
相反,容器视图控制器不应该知道关于字母的任何信息(内容 - 不知道),因为我正在尝试将其构建为尽可能可重用。
class ContainerViewController: UIViewController {
var contentViewController: ContentViewController? {
return childViewControllers.first as? ContentViewController
}
}
我试着在我的表视图控制器中写prepareForSegue()
:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let containerViewController = segue.destinationViewController as? ContainerViewController {
let indexPath = tableView.indexPathForCell(sender as! UITableViewCell)!
let letter = letterForIndexPath(indexPath)
containerViewController.navigationItem.title = "Introducing \(letter)"
// Not executed:
containerViewController.contentViewController?.letter = letter
}
}
但是在调用此方法时尚未创建contentViewController
,并且从未设置letter属性。
值得一提的是,当segue的目标视图控制器直接在内容视图控制器上设置时 - 相应地更新prepareForSegue()
后,这确实有效。
你知道如何实现这个目标吗?
实际上我觉得正确的解决方案是依靠内容视图的程序化实例化,这是我在仔细和彻底的想法之后选择的。
以下是我遵循的步骤:
ContainerViewController
。当用户点击一个单元格时,它仍会执行。ContentViewController
,并在我的班级中向该容器视图添加了一个IB Outlet。ContentViewController
,以便我们可以在适当的时候以编程方式实例化它。ContainerViewController.swift
看起来像(大多数代码安装并删除布局约束):
class ContainerViewController: UIViewController {
var contentViewController: UIViewController? {
willSet {
setContentViewController(newValue)
}
}
@IBOutlet private weak var containerView: UIView!
private var constraints = [NSLayoutConstraint]()
override func viewDidLoad() {
super.viewDidLoad()
setContentViewController(contentViewController)
}
private func setContentViewController(newContentViewController: UIViewController?) {
guard isViewLoaded() else { return }
if let previousContentViewController = contentViewController {
previousContentViewController.willMoveToParentViewController(nil)
containerView.removeConstraints(constraints)
previousContentViewController.view.removeFromSuperview()
previousContentViewController.removeFromParentViewController()
}
if let newContentViewController = newContentViewController {
let newView = newContentViewController.view
addChildViewController(newContentViewController)
containerView.addSubview(newView)
newView.frame = containerView.bounds
constraints.append(newView.leadingAnchor.constraintEqualToAnchor(containerView.leadingAnchor))
constraints.append(newView.topAnchor.constraintEqualToAnchor(containerView.topAnchor))
constraints.append(newView.trailingAnchor.constraintEqualToAnchor(containerView.trailingAnchor))
constraints.append(newView.bottomAnchor.constraintEqualToAnchor(containerView.bottomAnchor))
constraints.forEach { $0.active = true }
newContentViewController.didMoveToParentViewController(self)
}
} }
LetterTableViewController
类中,我实例化并设置了我的内容视图控制器,它被添加到Container的子视图控制器中。这是代码:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let containerViewController = segue.destinationViewController as? ContainerViewController {
let indexPath = tableView.indexPathForCell(sender as! UITableViewCell)!
let letter = letterForIndexPath(indexPath)
containerViewController.navigationItem.title = "Introducing \(letter)"
if let viewController = storyboard?.instantiateViewControllerWithIdentifier("ContentViewController"),
let contentViewController = viewController as? ContentViewController {
contentViewController.letter = letter
containerViewController.contentViewController = contentViewController
}
}
}
这完全适用于完全与内容无关的容器视图控制器。顺便说一句,它曾经是UITabBarController
委托方法中实例化UINavigationController
或appDidFinishLaunching:withOptions:
及其子项的方式。
我可以看到唯一的缺点:在storyboard上明确显示UI流程。
我能想到的唯一方法是添加委托,以便tableViewController实现一个带有一个方法的协议来返回字母;然后你有containerViewController将其childViewController(contentViewController)委托设置为其父级。而且contentViewController最终可以向其代表询问这封信。
在您当前的解决方案中,呈现对象本身负责使用“容器”和“内容”,它不必更改,但是这样的解决方案不仅具有您所描述的问题,而且还会产生问题。 “容器”的目的不是很清楚。
查看UIAlertController
:您没有直接配置其子视图控制器,使用警报控制器时甚至不应该知道它存在。您正在配置“容器”,而不是配置“内容”,该容器知道内容接口,生命周期和行为,并且不会公开它。按照这种方法,您可以对容器和内容实现适当划分的责任,“内容”的最小曝光允许您更新“容器”而无需更新其使用方式。
简而言之,不要试图从一个地方配置所有内容,而是将其配置为只配置“容器”,并让它在需要的时间和地点配置“内容”。例如。在您描述的场景中,“容器”将在初始化子控制器时为“内容”设置数据。我正在使用“容器”和“内容”而不是ContainerViewController
和ContentViewController
,因为解决方案并非严格基于控制器,因为你可以用NSObject
+ UIView
或UIWindow
替换它。