在哪里初始化 UIButton 中的目标?特别关心 IBDesignable

Where to initialize target in a UIButton? Particularly caring for IBDesignable

我有一个

@IBDesignable
class Fancy:UIButton

我想

addTarget(self, action:#selector(blah),
   forControlEvents: UIControlEvents.TouchUpInside)

那么应该在 UIButton 的什么地方完成?

addTarget最好的地方在哪里?

1 - 我看到 layoutSubviews 建议 - 是吗?

注意 - 实验表明 layoutSubviews 的一个问题是,当然,只要事情四处移动,它就可以经常调用。多次“添加目标”是个坏主意。

2 - didMoveToSuperview 是另一个建议。

3 - Inits(之一)中的某处?

注意 - 如果您在 Init 中进行实验,就会发现一个有趣的问题。在 Init 期间,IBInspectable 变量尚未实际设置! (因此,例如,我根据 IBInspectable 设置的控制“样式”进行分支;它显然不起作用,因为 @IBInspectable:在 运行 时不起作用!)

4 - 其他地方???

我尝试在 Init 中执行此操作,并且效果很好。但它会破坏可设计对象在编辑器中的工作。

经过折腾,我想到了这个(出于某种原因两者都必须包括在内?)

@IBDesignable
class DotButton:UIButton
    {
    @IBInspectable var mainColor ... etc.
    
    required init?(coder decoder: NSCoder)
        {
        super.init(coder: decoder)
        addTarget(self, action:#selector(blah),
            forControlEvents: UIControlEvents.TouchUpInside)
        }
    override init(frame:CGRect)
        {
        super.init(frame:frame)
        }

我不知道为什么会这样,我也不明白为什么会有两个不同的 init 例程。

在 UIButton 中包含 addTarget 的正确方法是什么?

您不应将产生动作的同一对象添加为目标。
目标及其回调应该是另一个对象,通常是视图控制器。
有 2 个 inits 方法,因为可以通过调用 init 或从 nib/xib 反序列化 (NSCoder) 的过程来实例化按钮。由于您可能已将按钮添加到故事板,因此调用的 init 方法是 init?(_: NSCoder)
[更新]
我同意你在评论中所说的内容,但我认为动作目标模式应该用于与其他对象进行通信,我正在使用条件,因为据我所知我从未见过像你在 Apple 中写的那样的东西代码或其他一些库。如果您想拦截按钮并在按钮内进行一些操作,您可能应该重写 UIControl.
中公开的一些方法 关于可设计的,你是,再一次,正确的。如果您以编程方式创建按钮,则调用 init(frame),如果按钮来自 xib,则调用 init(coder)
方法init(frame)在设计过程中也会被调用。在这一点上,我认为最好的选择是直接调试你的视图。

  • 在 UIButton 子类中放置一些断点
  • Select 故事板中的视图
  • 转到编辑器 -> 调试所选视图

现在你应该明白问题出在哪里了

您的初始化方法不正确,这将起作用:

```swift

override init(frame: CGRect) {
    super.init(frame: frame)
    self.loadNib() 
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    self.loadNib()
}

private func loadNib() {
    let nibView = NSBundle(forClass: self.classForCoder).loadNibNamed("yourView", owner: self, options: nil).first as! UIView
    nibView.frame = self.bounds
    nibView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
    self.button.addTarget(self, action: #selector(action), forControlEvents: .TouchUpInside)
    self.addSubview(nibView)
}

```

实施 awakeFromNib 并在那里实施如何?

https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Protocols/NSNibAwaking_Protocol/#//apple_ref/occ/instm/NSObject/awakeFromNib

您还可以有条件地 运行(或不 运行)在 Interface Builder 的上下文中 运行 编码:

#if !TARGET_INTERFACE_BUILDER
    // this code will run in the app itself
#else
    // this code will execute only in IB
#endif

(参见 http://nshipster.com/ibinspectable-ibdesignable/

tl;博士

    override func endTrackingWithTouch(touch: UITouch?, withEvent event: UIEvent?) {
        super.endTrackingWithTouch(touch, withEvent: event)
        if let touchNotNil = touch {
            if self.pointInside(touchNotNil.locationInView(self), withEvent: event) {
                print("it works")
            }
        }
    }

为什么不用addTarget

addTarget 方法是动作-目标界面的一部分,被认为是“public”。任何与您的按钮相关的内容都可以,例如 remove all of its actions, effectively breaking it. It is preffered to use some of 'protected' means, for instance endTrackingWithTouch which is accessible only to be overriden,而不是直接调用。这样它就不会干扰任何使用动作-目标机制的外部对象。

(我知道ObjC/UIKit中没有严格的'public'或'protected',但概念依然存在)

你的方式

如果您想按照自己的方式进行操作,那么您的示例就很好,只需将 addTarget 调用复制到 init(frame:CGRect)

或者你可以把 addTarget 放在 awakeFromNib 中(不要忘记 super)而不是 init?(coder decoder: NSCoder),但是无论如何你将被迫用编码器实现 init,所以.. .

layoutSubviewsdidMoveToSuperView 都是糟糕的想法。两者都可能发生不止一次,导致再次添加 blah 目标操作。然后blah会被一次点击调用多次

顺便说一句

Apple 方式

通过 Cocoa MVC(由 UIKit classes 实现强制执行),您应该将该操作分配给控制该按钮的对象,无论是否有动画。大多数情况下,该对象将是 Cocoa MVC 'Controller' - UIViewController.

如果您以编程方式创建按钮 UIViewController 应该在覆盖 loadViewviewDidLoad 中将目标分配给自身。当从 nib 加载按钮时,首选方法是在 xib 本身中分配目标操作。

旧的好 MVC

正如 中提到的 here 真正的 MVC 视图不会向自己发送操作。 UIKit 中最接近 real MVC Controller 的是 UIGestureRecognizer.

请注意,使用 UIKit class 集很难提取 真正的 MVC