在哪里初始化UIButton中的目标?尤其是照顾IBDesignable

时间:2022-09-10 17:52:44

I have a

我有一个

@IBDesignable
class Fancy:UIButton

I want to

我想

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

So where in UIButton should that be done?

那么在UIButton中应该做什么呢?

Where is the best place for addTarget ?

1 - I have seen layoutSubviews suggested - is that right?

1 -我见过layoutSubviews建议-对吗?

Note - experimentation shows that a problem with layoutSubviews is that, of course, it can be called often, whenever things move around. It would be a bad idea to "addTarget" more than once.

注意——实验表明,layoutSubviews的一个问题是,当事情发生时,它通常可以被调用。不止一次地“添加目标”是个坏主意。

2 - didMoveToSuperview is another suggestion.

2 - didMoveToSuperview是另一个建议。

3 - Somewhere in (one of) the Inits?

3 -在某个地方?

Note - experimentation shows a fascinating problem if you do it inside Init. During Init, IBInspectable variables are not yet actually set! (So for example, I was branching depending on the "style" of control set by an IBInspectable; it plain doesn't work as @IBInspectable: won't work when running!)

注意——如果在Init中进行实验,会显示出一个迷人的问题。在初始化过程中,IBInspectable变量实际上还没有设置!(例如,我根据IBInspectable设置的控件的“样式”进行分支;它显然不能作为@IBInspectable:在运行时不能工作!)

4 - Somewhere else???

4 -其他地方? ? ?

I tried to do it in Init, and it worked well. But it breaks designables from working in the Editor.

我试着在Init中做,它运行得很好。但是它破坏了在编辑器中工作的可设计性。

在哪里初始化UIButton中的目标?尤其是照顾IBDesignable

By thrashing around, I came up with this (for some reason both must be included?)

通过反复讨论,我想到了这个(出于某种原因,这两个都必须包括在内?)

@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)
        }

I don't know why that works, and I don't understand why there would be two different init routines.

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

What's the correct way to include addTarget in a UIButton?

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

4 个解决方案

#1


2  

How about implementing awakeFromNib and doing it there?

如何实现awakeFromNib并在那里执行呢?

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

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

You can also conditionally run (or not run) code when it is being run in the context of Interface Builder:

当在接口构建器的上下文中运行时,您还可以有条件地运行(或不运行)代码:

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

(see http://nshipster.com/ibinspectable-ibdesignable/)

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

#2


3  

You should not add as target the same object that produces the action.
The target and its callback should be another object, usually a view controller.
There are 2 inits methods because the button can be instantiated by calling init or by the process of deserializion (NSCoder) from a nib/xib. Since you probably added the button to a storyboard the init method called is init?(_: NSCoder).
[UPDATE]
I agree about what you say in the comment, but I think that the action-target pattern should be used for communicating with other objects, I'm using conditional, because as far as I know I never seen something like what you wrote in Apple code or some other library. If you want to intercept and make some actions inside the button you should probably override some of the methods exposed in UIControl.
About designable, you are, again, correct. init(frame) is called if you are creating a button programmatically, init(coder) if the button comes from a xib.
The method init(frame) is also called during designable process. At this point I think that the best option is to debug directly your view.

不应该将生成动作的对象添加为目标对象。目标和它的回调应该是另一个对象,通常是一个视图控制器。这里有2种方法,因为按钮可以通过调用init或从nib/xib中得到deserializion (NSCoder)的过程来实例化。既然你可能将按钮添加到故事板中init方法被称为init?(_:NSCoder)。[更新]我同意你在评论中所说的,但我认为动作目标模式应该用于与其他对象通信,我使用的是条件句,因为据我所知,我从未见过类似你在苹果代码或其他库中所写的东西。如果您想拦截并在按钮中执行一些操作,您可能应该覆盖UIControl中公开的一些方法。关于可设计性,您同样是正确的。如果以编程方式创建一个按钮,则调用init(frame),如果按钮来自xib,则调用init(coder)。在可设计过程中也调用方法init(框架)。此时,我认为最好的选择是直接调试视图。

  • Place some breakpoints inside you UIButton subclass
  • 在UIButton子类中放置一些断点
  • Select the view in your storyboard
  • 在故事板中选择视图。
  • Go to the Editor -> Debug selected views
  • 转到编辑器—>调试选定的视图

Now you should be able to understand where the problem is.

现在你应该能够理解问题所在了。

#3


3  

tl;dr

    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")
            }
        }
    }

Why not use addTarget

addTarget method is part of action-target interface which is considered 'public'. Anything with reference to your button can, say, 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, not called directly. This way it will not interfere with any external objects using action-target mechanism.

addTarget方法是action-target接口的一部分,被认为是“public”。任何与你的按钮有关的东西,比如,可以删除它的所有动作,有效地破坏它。使用一些“受保护”的方法,比如endTrackingWithTouch,它只能是overriden,而不是直接调用。这样,它就不会使用动作目标机制干扰任何外部对象。

(I know there is no strict 'public' or 'protected' in ObjC/UIKit, but the concept remains)

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

Your way

If you want to do it exactly your way then your example is all good, just copy addTarget call to init(frame:CGRect).

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

Or you can put addTarget in awakeFromNib (don't forget super) instead of init?(coder decoder: NSCoder), but you will be forced to implement init with coder anyway, so...

或者你可以把addTarget放在awakeFromNib(不要忘记super)而不是init中?(编码器解码器:NSCoder),但是您将*使用编码器实现init,所以……

layoutSubviews and didMoveToSuperView both are terrible ideas. Both may happen more than once resulting in blah target-action added again. Then blah will be called multiple times for a single click.

layoutSubviews和didMoveToSuperView都是糟糕的想法。这两种情况都可能发生多次,导致再次添加blah target-action。然后,每次单击blah将被调用多次。

By the way

The Apple way

By the Cocoa MVC (which is enforced by UIKit classes implmentation) you should assign that action to the object controlling that button, animations or not. Most often that object will be Cocoa MVC 'Controller' - UIViewController.

通过Cocoa MVC(由UIKit类进行强制执行),您应该将该操作分配给控制该按钮或动画的对象。通常那个对象是Cocoa MVC 'Controller' - UIViewController。

If you create button programmatically UIViewController should assign target to itself in overridden loadView or viewDidLoad. When button is loaded from nib the preffered way is to assign target action in xib itself.

如果您以编程方式创建按钮,UIViewController应该在重写的loadView或viewDidLoad中为自己分配目标。当从nib加载按钮时,预设的方法是在xib本身中分配目标操作。

Old Good MVC

As mentioned here in real MVC views do not send actions to themselves. The closest thing to real MVC Controller in UIKit is UIGestureRecognizer.

正如在这里提到的,在实际的MVC视图中,不向它们自己发送操作。在UIKit中最接近实际MVC控制器的是UIGestureRecognizer。

Be warned that it's pretty difficult to pull of real MVC with UIKit class set.

需要注意的是,使用UIKit类集来拉出真正的MVC非常困难。

#4


2  

Your initialize method is not correct, this will work:

你的初始化方法不正确,这将会工作:

```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)
}

```

' ' '

#1


2  

How about implementing awakeFromNib and doing it there?

如何实现awakeFromNib并在那里执行呢?

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

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

You can also conditionally run (or not run) code when it is being run in the context of Interface Builder:

当在接口构建器的上下文中运行时,您还可以有条件地运行(或不运行)代码:

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

(see http://nshipster.com/ibinspectable-ibdesignable/)

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

#2


3  

You should not add as target the same object that produces the action.
The target and its callback should be another object, usually a view controller.
There are 2 inits methods because the button can be instantiated by calling init or by the process of deserializion (NSCoder) from a nib/xib. Since you probably added the button to a storyboard the init method called is init?(_: NSCoder).
[UPDATE]
I agree about what you say in the comment, but I think that the action-target pattern should be used for communicating with other objects, I'm using conditional, because as far as I know I never seen something like what you wrote in Apple code or some other library. If you want to intercept and make some actions inside the button you should probably override some of the methods exposed in UIControl.
About designable, you are, again, correct. init(frame) is called if you are creating a button programmatically, init(coder) if the button comes from a xib.
The method init(frame) is also called during designable process. At this point I think that the best option is to debug directly your view.

不应该将生成动作的对象添加为目标对象。目标和它的回调应该是另一个对象,通常是一个视图控制器。这里有2种方法,因为按钮可以通过调用init或从nib/xib中得到deserializion (NSCoder)的过程来实例化。既然你可能将按钮添加到故事板中init方法被称为init?(_:NSCoder)。[更新]我同意你在评论中所说的,但我认为动作目标模式应该用于与其他对象通信,我使用的是条件句,因为据我所知,我从未见过类似你在苹果代码或其他库中所写的东西。如果您想拦截并在按钮中执行一些操作,您可能应该覆盖UIControl中公开的一些方法。关于可设计性,您同样是正确的。如果以编程方式创建一个按钮,则调用init(frame),如果按钮来自xib,则调用init(coder)。在可设计过程中也调用方法init(框架)。此时,我认为最好的选择是直接调试视图。

  • Place some breakpoints inside you UIButton subclass
  • 在UIButton子类中放置一些断点
  • Select the view in your storyboard
  • 在故事板中选择视图。
  • Go to the Editor -> Debug selected views
  • 转到编辑器—>调试选定的视图

Now you should be able to understand where the problem is.

现在你应该能够理解问题所在了。

#3


3  

tl;dr

    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")
            }
        }
    }

Why not use addTarget

addTarget method is part of action-target interface which is considered 'public'. Anything with reference to your button can, say, 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, not called directly. This way it will not interfere with any external objects using action-target mechanism.

addTarget方法是action-target接口的一部分,被认为是“public”。任何与你的按钮有关的东西,比如,可以删除它的所有动作,有效地破坏它。使用一些“受保护”的方法,比如endTrackingWithTouch,它只能是overriden,而不是直接调用。这样,它就不会使用动作目标机制干扰任何外部对象。

(I know there is no strict 'public' or 'protected' in ObjC/UIKit, but the concept remains)

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

Your way

If you want to do it exactly your way then your example is all good, just copy addTarget call to init(frame:CGRect).

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

Or you can put addTarget in awakeFromNib (don't forget super) instead of init?(coder decoder: NSCoder), but you will be forced to implement init with coder anyway, so...

或者你可以把addTarget放在awakeFromNib(不要忘记super)而不是init中?(编码器解码器:NSCoder),但是您将*使用编码器实现init,所以……

layoutSubviews and didMoveToSuperView both are terrible ideas. Both may happen more than once resulting in blah target-action added again. Then blah will be called multiple times for a single click.

layoutSubviews和didMoveToSuperView都是糟糕的想法。这两种情况都可能发生多次,导致再次添加blah target-action。然后,每次单击blah将被调用多次。

By the way

The Apple way

By the Cocoa MVC (which is enforced by UIKit classes implmentation) you should assign that action to the object controlling that button, animations or not. Most often that object will be Cocoa MVC 'Controller' - UIViewController.

通过Cocoa MVC(由UIKit类进行强制执行),您应该将该操作分配给控制该按钮或动画的对象。通常那个对象是Cocoa MVC 'Controller' - UIViewController。

If you create button programmatically UIViewController should assign target to itself in overridden loadView or viewDidLoad. When button is loaded from nib the preffered way is to assign target action in xib itself.

如果您以编程方式创建按钮,UIViewController应该在重写的loadView或viewDidLoad中为自己分配目标。当从nib加载按钮时,预设的方法是在xib本身中分配目标操作。

Old Good MVC

As mentioned here in real MVC views do not send actions to themselves. The closest thing to real MVC Controller in UIKit is UIGestureRecognizer.

正如在这里提到的,在实际的MVC视图中,不向它们自己发送操作。在UIKit中最接近实际MVC控制器的是UIGestureRecognizer。

Be warned that it's pretty difficult to pull of real MVC with UIKit class set.

需要注意的是,使用UIKit类集来拉出真正的MVC非常困难。

#4


2  

Your initialize method is not correct, this will work:

你的初始化方法不正确,这将会工作:

```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)
}

```

' ' '