【问题标题】:Can I pass in private variables to a UIViewController that will be instantiate via a Storyboard?我可以将私有变量传递给将通过 Storyboard 实例化的 UIViewController 吗?
【发布时间】:2019-12-11 21:22:01
【问题描述】:

我有一个“根”UIViewController,它本身实例化了另外两个 UIViewController。但是,我需要将一些东西传递给其中的每一个,这必须在初始化时完成。

但是

所有 3 个 UIViewController 都通过 Storyboard 实例化。

这可能吗?或者我应该看看其他一些设计模式来传递这些数据?

具体来说,我试图避免紧密耦合,但是“根”正在实例化的 2 个 UIViewController 彼此不了解,但在两者中,都要求他们知道一个变量。

我可以将此变量设为“根”上的静态变量,但这会强制耦合。

我可以制作一个全局“常量”快速文件并将变量放入其中,但这看起来很脏而且很笨拙。

理想情况下,我想在实例化时将此变量传递给这些 UIViewController 中的每一个,但由于它们都是从 Storyboard 加载的,所以我无法让它工作。

我看过关于类似事情的博客和堆栈帖子,例如

https://blog.scottlogic.com/2014/11/20/swift-initialisation.html

但我还没有看到一个决定性的方法。

我是不是走错了路?

显然下面的“示例”代码不起作用,但它可以向您展示我正在尝试做的事情,当然条件是所有这三个 UIViewController 都有一个条目Interface Builder 并使用 Storyboard 中的那些实例化...

class root : UIViewController
{
    private let a: CGFloat = 1.0
    private let b: CGFloat = 10.0

    private let vc1: firstViewController
    private let vc2: secondViewController

    override init()
    {
        self.vc1 = firstViewController(a: a, b: b)
        self.vc2 = secondViewController(a: a, b: b)

        super.init
    }
}


class firstViewController : UIViewController
{
    private let a: CGFloat
    private let b: CGFloat

    init(a: CGFloat, b: CGFloat)
    {
        self.a = a
        self.b = b
        super.init(aDecoder: Decoder)
    }
}

这也归结为如何“链接” UIViewController 实例化......我还没有找到关于它的博客或教程或解释。

感谢任何帮助/建议。

【问题讨论】:

  • 你可以使用storyBoards来做到这一点,所以你可以从代码中创建viewController或者在prepareforSegue或者在展示之前传入,但是因为所有的var都是私有的,所以你不能
  • 与手头的问题无关,但按照惯例,类名应始终以大写字母开头,例如RootViewController 和 FirstViewController 而不是 root 和 firstViewController。

标签: ios swift uiviewcontroller arguments init


【解决方案1】:

典型的解决方案是实现prepare(for:sender:),它让您有机会在目标视图控制器中设置属性。如果你想让它们保持松耦合,你可以使用协议:

protocol DataDestination {
    var a: CGFloat? { get set }
    var b: CGFloat? { get set }
}

class RootViewController: UIViewController {
    private let a: CGFloat = 1.0
    private let b: CGFloat = 10.0

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let destination = segue.destination as? DataDestination {
            destination.a = a
            destination.b = b
        }
    }
}

class FirstViewController: UIViewController, DataDestination {
    var a: CGFloat?
    var b: CGFloat?

    override func viewDidLoad() {
        super.viewDidLoad()
        print(a,b)           // by the time we hit this, those will be set
    }
}

显然,您应该给您的协议起一个更有意义的名称,但我们可能想了解更多有关该应用的信息,以便提出更好的建议。

【讨论】:

  • 嗨,罗伯。非常感谢您花时间回复并编写一些伪代码。我有个问题; 'FirstViewController' 中的变量是否必须是可选的?它们可以是私有的,让 CGFloats 和 not 可选吗?考虑到“RootViewController”实例化了另外两个 ViewController,即使它们有自己的故事板条目,也没有从 root 到呈现任一 VC 的“过渡”......所以“prepareForSegue”实际上会被调用吗?
  • 不,他们不能是private let。首先,如果它们是从外部源设置的,那么private 没有意义。根据定义,您需要公开接口来设置它们。而且因为从情节提要中实例化,您无法在 init 期间初始化它们。所以它们要么必须是可选的,要么你需要将它们初始化为某个值,例如0 在init 期间,然后让RootViewController 将它们更新为以后的任何内容。
  • 再次感谢您的帮助。我将重构以尝试一下。我已将其标记为预期的答案。感谢您的帮助。
  • Re prepareForSegue,显然只有在进行 segue 时才有效(无论是演示文稿还是嵌入 segue)。所以,问题是你在代码中手动实例化这些目标视图控制器之后要做什么。但是假设你不能轻易使用segue(例如这些是UIPageViewController中的页面),那么你可以在storyboard.instantiateViewController(withIdentifier:)之后设置a和b。
【解决方案2】:

除了Rob's answer,您还可以通过导航到 Interface Builder -> Identity Inspector 将 Storyboard ID 分配给第一个和第二个视图控制器。然后像这样实例化和设置它们:

let sb = UIStoryboard(name: "Main", bundle: nil)
if let firstVC = sb.instantiateViewController(withIdentifier: "firstVCIdentifier") as? FirstViewController {
    firstVC.a = self.a // ('a' must not be private in firstVC)
}

并展示它们:

self.present(firstVC, animated: true, completion: nil)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-01-07
    • 2017-05-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多