[英]Pass data to View Controller embedded inside a Container View Controller
我的视图控制器层次结构如下:
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
}
}
我试着在Table View Controller中编写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属性。
值得一提的是,在相应更新prepareForSegue()
之后,直接在内容视图控制器上设置segue的目标视图控制器时,这确实有效。
你知道如何实现这个目标吗?
实际上我觉得正确的解决方案是依靠内容视图的程序化实例化 ,这是我在仔细和彻底的想法之后选择的。
以下是我遵循的步骤:
Table View Controller在storyboard中有一个push segue设置为ContainerViewController
。 当用户点击一个单元格时,它仍会执行。
我将embed segue从Container View中删除到了storyboard中的ContentViewController
,并在我的类中为该Container View添加了一个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 } } }
这完全适用于完全与内容无关的容器视图控制器。 顺便说一句,它曾经是在appDidFinishLaunching:withOptions:
delegate方法中实例化UITabBarController
或UINavigationController
及其子appDidFinishLaunching:withOptions:
方式。
我可以看到唯一的缺点:UI流程更长时间显示在故事板上 。
我能想到的唯一方法是添加委托,以便tableViewController实现一个带有一个方法的协议来返回字母; 然后你有containerViewController将其childViewController(contentViewController)委托设置为其父级。 而且contentViewController最终可以向其代表询问这封信。
在当前解决方案的展示对象本身负责的工作都与“容器”和“内容”,它并没有改变 ,但这样的解决方案不仅具有像你描述的一个问题,也使得“容器”的目的不是很清楚。
查看UIAlertController
:您没有直接配置其子视图控制器,使用警报控制器时甚至不应该知道它存在。 您正在配置“容器”,而不是配置“内容”,该容器知道内容接口,生命周期和行为,并且不会公开它。 按照这种方法,您可以对容器和内容实现适当划分的责任,“内容”的最小曝光允许您更新“容器”而无需更新其使用方式。
简而言之,不要试图从一个地方配置所有内容,而是将其配置为只配置“容器”并让它在需要的时间和地点配置“内容”。 例如,在您描述的场景中,“容器”将在初始化子控制器时为“内容”设置数据。 我使用“容器”和“内容”而不是ContainerViewController
和ContentViewController
因为解决方案并不严格基于控制器,因为你可以用NSObject
+ UIView
或UIWindow
替换它。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.