【问题标题】:Pass data to View Controller embedded inside a Container View Controller将数据传递给嵌入在容器视图控制器中的视图控制器
【发布时间】:2016-04-12 10:11:51
【问题描述】:

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

  • 入口点是UINavigationController,其根视图控制器是通常的UITableViewController。表格视图显示字母列表。
  • 当用户点击一个单元格时,push 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 在调用此方法时尚未创建,并且从未设置 letter 属性。

值得一提的是,当直接在 Content View Controller 上设置 segue 的目标视图控制器时,这确实有效——在相应地更新 prepareForSegue() 之后。

您知道如何实现这一目标吗?

【问题讨论】:

  • “容器视图控制器不应该对字母一无所知”似乎是一个奇怪的要求。用户看到了一个表格视图,他们点击了一行,出现了另一个视图控制器......当然它应该知道这些数据。听起来您正在寻找真正的问题的解决方案。
  • 嗯...我不同意那个。容器视图控制器的目的是(可以?)改变其子视图控制器的呈现方式,而不是格式化和传递数据本身。以UISplitViewController 为例:它不应该知道所呈现的数据——UIPageViewControllerUINavigationController 类似。
  • 或者我在这里遗漏了一些重要的东西......在这种情况下,我将非常感谢您的反馈:)

标签: ios swift cocoa-touch


【解决方案1】:

其实我觉得正确的解决方案是依靠内容视图的程序化实例化,这是我经过深思熟虑后选择的。

以下是我遵循的步骤:

  • 表格视图控制器在情节提要中将推送 segue 设置为 ContainerViewController。当用户点击一个单元格时,它仍然会执行。

  • 我将 Container View 中的 embed segue 移除到情节提要中的 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 类中,我实例化并设置了我的内容视图控制器,它被添加到容器的子视图控制器中。代码如下:

    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: 委托方法中实例化 UITabBarControllerUINavigationController 及其子代的方式。

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

【讨论】:

    【解决方案2】:

    我能想到的唯一方法是添加委托,以便您的 tableViewController 使用一种方法来实现协议来返回字母;然后你让 containerViewController 将它的 childViewController(contentViewController)委托设置为它的父级。而 contentViewController 终于可以向它的委托人索要这封信了。

    【讨论】:

      【解决方案3】:

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

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

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

      【讨论】:

      • 我明白你的意思,我明白你在UIAlertController 的情况下是什么意思。但在某些情况下,我觉得容器真的应该是不知道内容的,就像 UITabControllerUINavigationController 一样。顺便说一句,当使用 segues 转换到嵌入在 UINavigationController 中的 View Controller 时,如何在 prepareForSegue() 中配置此 View Controller?
      • 您混淆了两种不同的场景:通过 segue 推送新的 VC,以及使用一堆新的“内容”视图控制器推送新的导航控制器。前者是一个正常的用例,您可以快速配置目标视图控制器,后者与您最初的问题类似,并且不是那么简单,因为您自己选择了懒惰的“内容”视图控制器初始化。对于如何配置此类延迟初始化的对象有不同的方法,以及如何选择是构成应用架构的原因。
      • 至于如何使“容器”不知道“内容”,请考虑以下选项: 1)自己初始化和完全配置“内容”视图控制器作为“容器”初始化的一部分和配置 2) 通过协议或公共接口委托内容初始化或内容设置。代表更加多才多艺;带有弱引用的公共接口看似提供了最简单的解决方案,但如果以这种方式使用,通常会产生意大利面条式代码;完全初始化可能是扩展的(或者更糟糕的是,在难以更新时变得更晚)。
      • 好吧,我没有混淆这两种情况,我只是指出了推送导航视图控制器和设置其根视图控制器之间的相似之处,以及问题中描述的情况。在 1) 和 2) 两种情况下,容器都以某种方式知道其子内容,或者通过持有和转发数据,或者通过持有指向符合子特定协议的子委托的指针——在这两种情况下容器不可重复使用的情况。无论如何,我使用了第一个解决方案,并将ContainerViewController 设置为ContentViewController
      猜你喜欢
      • 2018-07-31
      • 2017-07-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-08-14
      • 1970-01-01
      • 1970-01-01
      • 2020-04-01
      相关资源
      最近更新 更多