如何将数据从父视图控制器多次传递到 Swift 中的容器 VC 子容器?

How to Pass Data Multiple Times from Parent View Controller to Container VC Child in Swift?

我在 ViewControllerA(父)上有一个按钮,我想更新 ViewControllerB(子)中的一个变量。 ViewControllerB 是 ViewControllerA 内部的容器视图。

这是 ViewControllerB 中的变量,我想通过按下父 ViewControllerA 按钮多次更新:

@IBOutlet weak var childViewHeight: NSLayoutConstraint!

因为子视图 ViewControllerB 通过嵌入转场连接,看来我只能通过 prepareForSegue 方法将数据从 ViewControllerA 传递到 ViewControllerB 一次。 performSegue 方法导致程序崩溃并出现 SIGABRT 错误。

我知道尝试从单独的 class 或视图控制器更新 IBOutlet 通常被认为是不好的做法,但我需要一种方法来按下 ViewControllerA 上的按钮来更改高度同时约束 ViewControllerA 和 ViewControllerB。

如果这在我目前的方法中是不可能的,请给我另一个关于如何重新设计我的应用程序以使其成为可能的建议。

更新 - 这是导致崩溃的代码:

@IBAction func button(_ sender: AnyObject) {
    performSegue(withIdentifier: "seg", sender: self)
}

更新 - 这是我在调试控制台中输入 "bt" 时的结果:

* thread #1: tid = 0x1fcdd, 0x000000010d9b1f06 libsystem_kernel.dylib`__pthread_kill + 10, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
frame #0: 0x000000010d9b1f06 libsystem_kernel.dylib`__pthread_kill + 10
frame #1: 0x000000010dad24ec libsystem_pthread.dylib`pthread_kill + 90
frame #2: 0x000000010d7040b3 libsystem_c.dylib`abort + 129
frame #3: 0x000000010d9d043a libc++abi.dylib`abort_message + 266
frame #4: 0x000000010d9f4a9f libc++abi.dylib`default_terminate_handler() + 267
frame #5: 0x000000010c7b559f libobjc.A.dylib`_objc_terminate() + 103
frame #6: 0x000000010d9f1c09 libc++abi.dylib`std::__terminate(void (*)()) + 8
frame #7: 0x000000010d9f1894 libc++abi.dylib`__cxa_rethrow + 99
frame #8: 0x000000010c7b54b7 libobjc.A.dylib`objc_exception_rethrow + 40
frame #9: 0x000000010a2eebf1 CoreFoundation`CFRunLoopRunSpecific + 433
frame #10: 0x000000010f6d7a48 GraphicsServices`GSEventRunModal + 161
frame #11: 0x000000010ad27e8b UIKit`UIApplicationMain + 159
* frame #12: 0x000000010a1c60cf ContainerVC2`main + 111 at AppDelegate.swift:12
frame #13: 0x000000010d6586bd libdyld.dylib`start + 1

更新 - 这是 "bt" 控制台输出,其中设置了异常断点:

* thread #1: tid = 0x219bd, 0x000000010afca2ee libobjc.A.dylib`objc_exception_throw, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x000000010afca2ee libobjc.A.dylib`objc_exception_throw
frame #1: 0x0000000108b7dec2 CoreFoundation`+[NSException raise:format:arguments:] + 98
frame #2: 0x0000000109079455 Foundation`-[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 195
frame #3: 0x0000000109f65309 UIKit`__67-[UIStoryboardEmbedSegueTemplate newDefaultPerformHandlerForSegue:]_block_invoke + 438
frame #4: 0x0000000109ce05e4 UIKit`-[UIStoryboardSegueTemplate _performWithDestinationViewController:sender:] + 453
frame #5: 0x0000000109ce03ee UIKit`-[UIStoryboardSegueTemplate _perform:] + 82
frame #6: 0x00000001096dc45b UIKit`-[UIViewController performSegueWithIdentifier:sender:] + 99
* frame #7: 0x00000001089d99b3 ContainerVC2`ViewController1.button(sender=0x00007fff57224658, self=0x00007fcddb707cb0) -> () + 131 at ViewController.swift:9
frame #8: 0x00000001089d9a26 ContainerVC2`@objc ViewController1.button(AnyObject) -> () + 54 at ViewController.swift:0
frame #9: 0x000000010953eb6f UIKit`-[UIApplication sendAction:to:from:forEvent:] + 83
frame #10: 0x00000001096bf927 UIKit`-[UIControl sendAction:to:forEvent:] + 67
frame #11: 0x00000001096bfc08 UIKit`-[UIControl _sendActionsForEvents:withEvent:] + 388
frame #12: 0x00000001096be6aa UIKit`-[UIControl touchesBegan:withEvent:] + 414
frame #13: 0x00000001095aabbd UIKit`-[UIWindow _sendTouchesForEvent:] + 1188
frame #14: 0x00000001095ac8d6 UIKit`-[UIWindow sendEvent:] + 3984
frame #15: 0x000000010955a1e1 UIKit`-[UIApplication sendEvent:] + 281
frame #16: 0x0000000109d1502f UIKit`__dispatchPreprocessedEventFromEventQueue + 3314
frame #17: 0x0000000109d0dc4e UIKit`__handleEventQueue + 4879
frame #18: 0x0000000108b1fcb1 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
frame #19: 0x0000000108b04c6c CoreFoundation`__CFRunLoopDoSources0 + 556
frame #20: 0x0000000108b04156 CoreFoundation`__CFRunLoopRun + 918
frame #21: 0x0000000108b03b5d CoreFoundation`CFRunLoopRunSpecific + 285
frame #22: 0x000000010deeca48 GraphicsServices`GSEventRunModal + 161
frame #23: 0x000000010953ce8b UIKit`UIApplicationMain + 159
frame #24: 0x00000001089db0cf ContainerVC2`main + 111 at AppDelegate.swift:12
frame #25: 0x000000010be6d6bd libdyld.dylib`start + 1

这是我点击 "continue program execution" 按钮一次后的 "bt" 输出:

* thread #1: tid = 0x219bd, 0x000000010c206607 libc++abi.dylib`__cxa_throw, queue = 'com.apple.main-thread', stop reason = breakpoint 1.2
frame #0: 0x000000010c206607 libc++abi.dylib`__cxa_throw
frame #1: 0x000000010afca443 libobjc.A.dylib`objc_exception_throw + 341
frame #2: 0x0000000108b7dec2 CoreFoundation`+[NSException raise:format:arguments:] + 98
frame #3: 0x0000000109079455 Foundation`-[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 195
frame #4: 0x0000000109f65309 UIKit`__67-[UIStoryboardEmbedSegueTemplate newDefaultPerformHandlerForSegue:]_block_invoke + 438
frame #5: 0x0000000109ce05e4 UIKit`-[UIStoryboardSegueTemplate _performWithDestinationViewController:sender:] + 453
frame #6: 0x0000000109ce03ee UIKit`-[UIStoryboardSegueTemplate _perform:] + 82
frame #7: 0x00000001096dc45b UIKit`-[UIViewController performSegueWithIdentifier:sender:] + 99
* frame #8: 0x00000001089d99b3 ContainerVC2`ViewController1.button(sender=0x00007fff57224658, self=0x00007fcddb707cb0) -> () + 131 at ViewController.swift:9
frame #9: 0x00000001089d9a26 ContainerVC2`@objc ViewController1.button(AnyObject) -> () + 54 at ViewController.swift:0
frame #10: 0x000000010953eb6f UIKit`-[UIApplication sendAction:to:from:forEvent:] + 83
frame #11: 0x00000001096bf927 UIKit`-[UIControl sendAction:to:forEvent:] + 67
frame #12: 0x00000001096bfc08 UIKit`-[UIControl _sendActionsForEvents:withEvent:] + 388
frame #13: 0x00000001096be6aa UIKit`-[UIControl touchesBegan:withEvent:] + 414
frame #14: 0x00000001095aabbd UIKit`-[UIWindow _sendTouchesForEvent:] + 1188
frame #15: 0x00000001095ac8d6 UIKit`-[UIWindow sendEvent:] + 3984
frame #16: 0x000000010955a1e1 UIKit`-[UIApplication sendEvent:] + 281
frame #17: 0x0000000109d1502f UIKit`__dispatchPreprocessedEventFromEventQueue + 3314
frame #18: 0x0000000109d0dc4e UIKit`__handleEventQueue + 4879
frame #19: 0x0000000108b1fcb1 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
frame #20: 0x0000000108b04c6c CoreFoundation`__CFRunLoopDoSources0 + 556
frame #21: 0x0000000108b04156 CoreFoundation`__CFRunLoopRun + 918
frame #22: 0x0000000108b03b5d CoreFoundation`CFRunLoopRunSpecific + 285
frame #23: 0x000000010deeca48 GraphicsServices`GSEventRunModal + 161
frame #24: 0x000000010953ce8b UIKit`UIApplicationMain + 159
frame #25: 0x00000001089db0cf ContainerVC2`main + 111 at AppDelegate.swift:12
frame #26: 0x000000010be6d6bd libdyld.dylib`start + 1

更新 - prepareforsegue 代码:

override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) {
    if segue.identifier == "seg" {
        var vcB: ViewControllerB?
        vcB = segue.desinationViewController as? ViewControllerB
}

调用prepare for segue的时候,ViewControllerB的outlets还没有设置。它们都是零,因此尝试访问一个会导致崩溃。

对于你正在尝试做的事情,最肮脏的方法是通过视图控制器 A 的 childViewControllers 数组访问它,从内部视图控制器 A 更改 layoutContraint 的常量。你将不得不投下第一个数组中的元素(假设你只有一个 child)到视图控制器 B class.

的 object

以上是一种非常肮脏的方法,但在您了解更多信息之前,它会完成工作。

@IBAction buttonAction(sender: UIButton) {
    if let viewControllerB = childViewControllers[0] as? ViewControllerB {
        viewControllerB.childViewHeight.constant = 25.0
    }
}

同样,这是一种非常肮脏的方法,但是在您学习正确方法的同时它会完成工作。


有人问我为什么以上是个坏主意...

假设您没有编写有问题的应用程序,并且您的任务是使用约束常量修复错误。修复错误需要做的第一件事是找到所有改变该常量的地方。

在理想情况下,该常量只会在一个地方发生突变(我的 ViewController class 中有一个名为 updateUI 的方法可以处理所有 IBOutlet 突变。 ) 如果是这种情况,那么修复错误将只是理解常量发生变化的一个函数的问题。

下一个最好的情况是只有一个 class 改变约束的常量。然后,修复错误所需要做的就是了解 class 的工作原理。理解单个函数的更大任务,但可行。

然而,使用上面的代码意味着有多个 classes 调整约束的常量,如果有两个 classes 这样做,谁说没有更多?在这种情况下,修复错误需要了解整个应用程序。


更好的方法...

据推测,您的屏幕上有多个视图控制器,它们通过您的模型代码以某种方式相互连接。该模型代表您的真相来源。当模型更改并相应地更新它们自己和它们的视图时,你的两个视图控制器应该得到通知。如果您没有表示影响这两个视图的抽象的模型,那么您应该创建一个。

您无需调用 performSegue 进行嵌入转场。当从情节提要中加载包含视图控制器时,会自动触发嵌入转场。

您可以在包含视图控制器中使用 prepareForSegue 来获取对包含视图控制器的引用(它将是 segue 中的 destinationViewController)。获得引用后,您可以将其存储在 属性 中并使用它与其进行交互。我建议您在视图控制器上调用一个函数来更新其约束,而不是直接更新约束:

class ViewControllerA: UIViewController {

    var viewControllerB: ViewControllerB?

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        if segue.identifier == "seg" {
            self.viewControllerB = segue.destinationViewController as? ViewControllerB
        }
     }

     @IBAction func button(_ sender: AnyObject) {
         self.viewControllerB?.doSomethingWithHeight(newHeight)
     }
}



class ViewControllerB: UIViewController {

    @IBOutlet weak var childViewHeight: NSLayoutConstraint!

    func doSomethingWithHeight(newHeight: CGFloat) {
        self.childViewHeight.constant = newHeight
    }
}