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

冷暖自知 提交于 2019-12-10 15:18:58

问题


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?

Where is the best place for addTarget ?

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

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.

2 - didMoveToSuperview is another suggestion.

3 - Somewhere in (one of) the Inits?

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!)

4 - Somewhere else???

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

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.

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


回答1:


How about implementing awakeFromNib and doing it there?

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/)




回答2:


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.

  • Place some breakpoints inside you UIButton subclass
  • 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:


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.

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

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).

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...

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.

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.

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.

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.

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




回答4:


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

```



来源:https://stackoverflow.com/questions/37242334/where-to-initialize-target-in-a-uibutton-particularly-caring-for-ibdesignable

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!