从子视图更改@IBOutlet

Change @IBOutlet from a subview

我正在尝试从 UIView 启用或禁用工具栏的 @IBOutlet UIButton 项。
当我在 EraseView.Swift 中使用的数组为空时,按钮 应该被禁用
我尝试创建视图控制器的实例,但它给了我错误(展开时发现 nil):
在擦除视图中:

class EraseView: UIView {
    ...
    let editViewController = EditImageViewController()
    //array has item
    editViewController.undoEraseButton.enabled = true //here I get the error
    ...
}

我尝试放置一个全局 Bool,它在 EditImageViewController 中使用它更改了值,但它不起作用:

var enableUndoButton =  false

class EditImageViewController: UIViewController {

   @IBOutlet weak var undoEraseButton: UIBarButtonItem!

    viewDidLoad() {
        undoEraseButton.enabled = enableUndoButton
    }
}

class EraseView: UIView {
    ...        
    //array has item
    enableUndoButton = true //here I get the error
    ...
}

我知道这很简单,但我不能让它起作用。
情况如下:

问题的根源在于以下行:

let editViewController = EditImageViewController()

EditImageViewController() 表示 "ignore what the storyboard has already instantiated for me, but rather instantiate another view controller with no outlets hooked up and use that." 显然,这不是您想要的。

您需要为 EraseView 提供一些方法来通知现有的视图控制器其 "is empty" 状态是否发生了一些变化。而且,理想情况下,您希望以保持这两个 类 松散耦合的方式来执行此操作。 EraseView 应该只通知视图控制器 "is empty" 状态的变化,视图控制器应该启动其他子视图(即按钮)的更新。一个视图真的不应该更新另一个视图的出口。

您可以通过两种方式做到这一点:

  1. 关闭:

    你可以给 EraseView 一个可选的闭包,当它从 "empty" 和 "not empty" 切换时调用它:

    var emptyStateChanged: ((Bool) -> ())?
    

    然后状态改变的时候就可以调用这个了。例如,当您删除视图中的最后一项时,EraseView 可以调用该闭包:

    emptyStateChanged?(true)
    

    最后,为了真正执行任何操作,视图控制器应提供实际的闭包以在状态更改时启用和禁用按钮:

    override func viewDidLoad() {
        super.viewDidLoad()
    
        eraseView.emptyStateChanged = { [unowned self] isEmpty in 
            self.undoEraseButton.enabled = !isEmpty
        }
    }
    

    注意,我使用 unowned 来避免强引用循环。

  2. 委托协议模式:

    所以你可以定义一个协议来做到这一点:

    protocol EraseViewDelegate : class {
        func eraseViewIsEmpty(empty: Bool)
    }
    

    然后给EraseView一个delegate属性:

    weak var delegate: EraseViewDelegate?
    

    请注意,这是 weak 以避免强引用循环。 (这也是我将协议定义为 class 协议的原因,这样我就可以在此处将其设为 weak。)

    当视图的 "is empty" 状态发生变化时,EraseView 将调用此委托。例如,当它变空时,它会相应地通知它的委托:

    delegate?.eraseViewIsEmpty(true)
    

    然后,为了使这一切正常工作,视图控制器应该 (a) 声明它符合协议; (b) 将自己指定为 EraseViewdelegate; (c) 实施 eraseViewIsEmpty 方法,例如:

    class EditImageViewController: UIViewController, EraseViewDelegate {
    
        @IBOutlet weak var undoEraseButton: UIBarButtonItem!
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            eraseView.delegate = self
        }
    
        func eraseViewIsEmpty(empty: Bool) {
            undoEraseButton.enabled = !empty
        }
    }
    

这两种模式都使两个 类 保持松散耦合,但允许 EraseView 将某些事件通知其视图控制器。它还消除了对任何全局的需要。

还有其他方法也可以解决这个问题(例如通知、KVN 等),但希望这能说明基本思想。视图应将任何关键事件通知其视图控制器,而视图控制器应负责其他视图的更新。