将数据传递到嵌入在Container View Controller中的View Controller

时间:2016-04-12 10:11:52

标签: ios swift cocoa-touch

我的视图控制器层次结构如下:

view controller hierarchy

  • 入口点为UINavigationController,其根视图控制器通常为UITableViewController。表格视图显示了一个字母列表。
  • 当用户点击某个单元格时,会触发推送segue ,并且该视图会转换为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的目标视图控制器时,这确实有效。

你知道如何实现这个目标吗?

3 个答案:

答案 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代理方法中实例化UINavigationControllerappDidFinishLaunching:withOptions:及其子代的方式。

我唯一能看到的缺点是:storyboard上明确显示了UI流程。

答案 1 :(得分:0)

我能想到的唯一方法是添加委托,以便tableViewController实现一个带有一个方法的协议来返回字母;然后你有containerViewController将其childViewController(contentViewController)委托设置为其父级。而contentViewController最终可以向其代表询问这封信。

答案 2 :(得分:0)

在您当前的解决方案中,呈现对象本身负责使用“容器”和“内容”,它不会必须更改,但这样的解决方案不仅具有像你描述的问题,但也使“容器”的目的不是很清楚。

查看UIAlertController:您没有直接配置其子视图控制器,使用警报控制器时甚至不应该知道它存在。您正在配置“容器”,而不是配置“内容”,该容器知道内容接口,生命周期和行为,并且不会公开它。按照这种方法,您可以对容器和内容实现适当划分的责任,“内容”的最小曝光允许您更新“容器”而无需更新其使用方式。

简而言之,不要尝试从一个地方配置所有内容,而是将其配置为只配置“容器”,并让它在需要的时间和地点配置“内容”。例如。在您描述的场景中,“容器”将在初始化子控制器时为“内容”设置数据。我正在使用“容器”和“内容”而不是ContainerViewControllerContentViewController,因为解决方案并非严格基于控制器,因为您也可以替换它NSObject + {{ 1}}或UIView