Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

enter image description here

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?

like image 818
Fattie Avatar asked May 15 '16 18:05

Fattie


1 Answers

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.

like image 75
gp-v Avatar answered Sep 21 '22 06:09

gp-v