我的视图控制器层次结构如下:
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
尚未在调用此方法时创建,并且字母属性永远不会设置。
值得一提的是,在相应更新prepareForSegue()
之后,直接在内容视图控制器上设置segue的目标视图控制器时,这确实有效。
你知道如何实现这个目标吗?
答案 0 :(得分:1)
实际上我认为正确的解决方案是依靠内容视图的程序化实例化,这是我在仔细而彻底的想法之后选择的。
以下是我遵循的步骤:
表视图控制器在故事板中将推送segue设置为ContainerViewController
。当用户点击一个单元格时,它仍会执行。
我将embed segue从容器视图中删除到了故事板中的ContentViewController
,然后我在班上的容器视图中添加了一个IB Outlet。
我将故事板ID设置为内容视图控制器,例如...... ContentViewController
,以便我们可以在适当的时候以编程方式对其进行实例化。
我实现了一个自定义容器视图控制器,如Apple的View Controller Programming Guide中所述。现在我的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流程。
答案 1 :(得分:0)
我能想到的唯一方法是添加委托,以便tableViewController实现一个带有一个方法的协议来返回字母;然后你有containerViewController将其childViewController(contentViewController)委托设置为其父级。而contentViewController最终可以向其代表询问这封信。
答案 2 :(得分:0)
在您当前的解决方案中,呈现对象本身负责使用“容器”和“内容”,它不会必须更改,但这样的解决方案不仅具有像你描述的问题,但也使“容器”的目的不是很清楚。
查看UIAlertController
:您没有直接配置其子视图控制器,使用警报控制器时甚至不应该知道它存在。您正在配置“容器”,而不是配置“内容”,该容器知道内容接口,生命周期和行为,并且不会公开它。按照这种方法,您可以对容器和内容实现适当划分的责任,“内容”的最小曝光允许您更新“容器”而无需更新其使用方式。
简而言之,不要尝试从一个地方配置所有内容,而是将其配置为只配置“容器”,并让它在需要的时间和地点配置“内容”。例如。在您描述的场景中,“容器”将在初始化子控制器时为“内容”设置数据。我正在使用“容器”和“内容”而不是ContainerViewController
和ContentViewController
,因为解决方案并非严格基于控制器,因为您也可以替换它NSObject
+ {{ 1}}或UIView
。