【问题标题】:Update label in ViewControllerB from ViewControllerA (ViewControllers are both in containers in the same view)从 ViewController 更新 ViewController 中的标签(视图控制器都在同一个视图的容器中)
【发布时间】:2019-02-26 10:39:57
【问题描述】:

视图控制器 A 和 B 都在容器中,并一起形成一个视图。
在 ViewControllerA 我有一个按钮和一个标签,在 ViewControllerB 我有一个标签。
两个标签都初始化为数字“5”。
通过按下 ViewControllerA 中的按钮,我想为每个标签添加 3 个,
即每个标签应显示“8”。
我认为这就像在 ViewControllerB 中定义一个函数来接受来自 ViewControllerA 的更新总数一样简单,然后在 ViewControllerB 中更新标签的文本属性。
当然,我得到“在展开可选值时意外发现 nil”。
非常感谢您的建议/指导。

import UIKit

class ViewControllerA: UIViewController {

//MARK: Properties
@IBOutlet weak var buttonInViewControllerA: UIButton!
@IBOutlet weak var labelInViewControllerA: UILabel!

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view.
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

//MARK: Actions

@IBAction func buttonActionInViewControllerA(_ sender: UIButton) {
    let a: String = String(Int(labelInViewControllerA.text!)! + 3)
    labelInViewControllerA.text = a
    ViewControllerB().add3(value: a)
}
}

class ViewControllerB: UIViewController {

//MARK: Properties
@IBOutlet weak var labelInViewControllerB: UILabel!

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view.
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

func add3(value: String) {
    self.labelInViewControllerB.text = value
}
}

【问题讨论】:

  • 这是因为,viewControllerB 还没有加载。当 viewControllerB 被加载时,您必须检查标签的更新值。对于修复,您可以添加一个保护语句以防止 add3 函数中的崩溃,例如 ..guard self.isViewLoaded else { return } 在访问该标签之前
  • 但是 ViewControllerB 显示在屏幕上,并显示值为 5 的标签。这不意味着它已经加载了吗? (视图控制器 A 和 B 都在容器中并构成单个视图的一部分)。如果我将以下语句放入 viewDidLoad(在 ViewControllerB 中): print("Value of label in ViewControllerB is", labelInViewControllerB.text) 然后它会打印值“5”,这对我来说表示 ViewControllerB 已经加载并且 UILabel已初始化。
  • 那么就获取viewControllerB的引用,(从viewControllerA,self.parent?.children.last as?viewControllerB)。我假设 viewControllerB 是最后一个孩子
  • PS:谢谢你的快速回复,顺便说一句。
  • 如果我假设这将被添加到 ViewControllerA 中的 IBAction 中,那么整行会是什么样子?

标签: ios swift


【解决方案1】:

问题在于

@IBAction func buttonActionInViewControllerA(_ sender: UIButton) {
    // ...
    ViewControllerB().add3(value: a)

}

您创建了ViewControllerB 的新实例。您需要的是对现有引用(属性)的引用,然后您将其告知更改:

class ViewControllerA: UIViewController {
    var controllerB:ViewControllerB?

    // ...

    @IBAction func buttonActionInViewControllerA(_ sender: UIButton) {
        // ...
        controllerB?.add3(value: a)
    }
}

别忘了在代码中的某处设置controllerB,例如

var vcA = ViewControllerA()
var vcB = ViewControllerB()
vcA.controllerB = vcB
// dispaly vcA and vcB

【讨论】:

  • Andreas - 非常感谢。我现在可以看到我创建了一个新的 ViewControllerB 实例(这就是它为空的原因)!但我不明白底部 4 行的用途,也不明白它们放在哪里(大概在 ViewControllerA 中)。
  • 最后四行创建了 ViewControllers,但这只是示例代码。您可以在某个地方创建它们并将它们放入容器中。
【解决方案2】:

您可能想使用Protocol / Delegate 模式(如果您不熟悉,最好阅读一下)。

基本上,您创建一个 Protocol 来定义您想从“其他地方”访问的一个(或多个)函数。

例如:

// protocol / delegate pattern
protocol ABDelegate: class {
    func myButtonWasTapped()
}

您想从A 中的按钮点击调用myButtonWasTapped(),因此您创建一个“符合”此协议的视图控制器(它具有该功能),并在视图控制器中创建一个ABDelegate 变量你想调用函数。

你的第一个想法可能是:

“好的,我将把它设置为 vcBvcA 的代表,并让我在 vcA 中的按钮点击函数直接调用 vcB 中的增量函数。”

这可行,但是这两个类“耦合得太紧”。也就是太依赖了。

更好的方法是让您的“主”视图控制器充当中介:

// this will be called by the code in ViewControllerA
func myButtonWasTapped() {
    // call func in ViewControllerB
    vcB?.doIncrement()
}

所以它看起来像这样:

棘手的部分是获取对 vcA 和 vcB 的引用。因为容器视图中的嵌入式视图控制器是通过embed segues 加载的,所以您可以在prepare(for segue:...) 中获取它们:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

    // if this is the embedded ViewControllerA
    if let vc = segue.destination as? ViewControllerA {

        // set self as the ABDelegate in ViewControllerA
        vc.theDelegate = self

        // keep a reference in case we want to access it
        self.vcA = vc

    }

    // if this is the embedded ViewControllerB
    if let vc = segue.destination as? ViewControllerB {

        // keep a reference so we can call its doIncrement() func
        self.vcB = vc

    }

}

完整代码变为:

//
//  ContainerTestViewController.swift
//

import UIKit

// protocol / delegate pattern
protocol ABDelegate: class {
    func myButtonWasTapped()
}

// "main" view controller conforms to ABDelegate protocol
class ContainerTestViewController: UIViewController, ABDelegate {

    var vcA: ViewControllerA?
    var vcB: ViewControllerB?

    // this will be called by the code in ViewControllerA
    func myButtonWasTapped() {
        // call func in ViewControllerB
        vcB?.doIncrement()
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

        // if this is the embedded ViewControllerA
        if let vc = segue.destination as? ViewControllerA {

            // set self as the ABDelegate in ViewControllerA
            vc.theDelegate = self

            // keep a reference in case we want to access it
            self.vcA = vc

        }

        // if this is the embedded ViewControllerB
        if let vc = segue.destination as? ViewControllerB {

            // keep a reference so we can call its doIncrement() func
            self.vcB = vc

        }

    }

}

class ViewControllerA: UIViewController {

    var theDelegate: ABDelegate?

    @IBAction func incTapped(_ sender: Any) {

        // call the func in the delegate
        theDelegate?.myButtonWasTapped()

    }

}

class ViewControllerB: UIViewController {

    @IBOutlet var theLabel: UILabel!

    var theValue = 0

    override func viewDidLoad() {
        super.viewDidLoad()
        theLabel.text = String(theValue)
    }

    func doIncrement() -> Void {
        theValue += 3
        theLabel.text = String(theValue)
    }

}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-29
    • 2015-05-05
    • 2012-06-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多