28

I am subclassing the UIButton, what i want is to set the button type to Round Rect.

Button.h

@interface Button : UIButton {}
    - (void)initialize;
@end

Button.m

@implementation Button

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self initialize];
    }
    return self;
}


-(id)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    if(self){
        [self initialize];
    }
    return self;
}

- (void)initialize
{
    self.titleLabel.font = [UIFont systemFontOfSize:20];
    self.titleLabel.textColor = [UIColor redColor];
    self.titleLabel.textAlignment = UITextAlignmentCenter;
   //[UIButton buttonWithType:UIButtonTypeRoundedRect];
}

@end

Here i tried [UIButton buttonWithType:UIButtonTypeRoundedRect] but it doesn't work. Can anyone suggest how to make it work?

I know in many previous post it has been said that Subclassing UIButton is not recommended, but the fact that in Developer's Docs there is no mention about NOT subclassing it.

Haris Hussain
  • 2,531
  • 3
  • 25
  • 38
  • 2
    What I you *really* trying to do? I know, you said, you want to set the button's type, but I don't believe you. You have an idea for your UI and you try to reach it with subclassing UIButton and setting its type..., but you won't. So tell us, what you are going for, and I'm sure there'll be a solution different from your approach. – Kai Huppmann Apr 23 '12 at 09:35
  • but you _won't_.. :s well, nothing so special, just creating the subclass so that i could manipulate the buttons throughout the app..! – Haris Hussain Apr 23 '12 at 09:57
  • 1
    @HarisHussain Why do you need a subclass to manipulate buttons? The UIButton interface seems sufficient for that. – Caleb Apr 23 '12 at 09:59
  • possible duplicate of [Subclass UIButton to add a property](http://stackoverflow.com/questions/5500327/subclass-uibutton-to-add-a-property). Also [create unbutton subclass](http://stackoverflow.com/q/5045672/643383) and [Subclassing UIButton but can't access my properties](http://stackoverflow.com/q/2920045/643383). – Caleb Apr 23 '12 at 09:59
  • So that the buttons remain consistant throughout the app. So that when i have to change font size of all the buttons i do not have to change it everywhere i have used one. etc. – Haris Hussain Apr 23 '12 at 10:10
  • So how about a constant like `#define BUTTON_FONT [UIFont fontWithName:aFontName size:aFontSize]`? – Kai Huppmann Apr 23 '12 at 10:18

8 Answers8

16

UIButton subclass in Swift

*The following code works in Swift 3 and above.

You cannot set the buttonType of a UIButton subclass by design. It is automatically set to custom which means you get a plain appearance and behavior as a starting point.

If you want to reuse code that sets the button's appearance, you can do this without subclassing. One approach is by providing factory methods that create a UIButton and set visual properties.

Example factory method to avoid subclassing

extension UIButton {
    static func createStandardButton() -> UIButton {
        let button = UIButton(type: UIButtonType.system)
        button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 16)
        button.setTitleColor(UIColor.black, for: .normal)
        button.setTitleColor(UIColor.gray, for: .highlighted)
        return button
    }
}

let button = UIButton.createStandardButton()

I avoid subclassing UIButton when other customization techniques suffice. The factory method example is one option. There are other options including the UIAppearance API, etc.

Sometimes there are customization needs that require subclassing. For example, I've created UIButton subclasses to take fine control over how the button animates in response to touch events or to have the button call a predefined delegate or closure when tapped (as opposed to assigning a #selector to each instance).

The following is a basic UIButton subclass example as a starting point.

Example UIButton Subclass

internal class CustomButton: UIButton {

    init() {
        // The frame can be set outside of the initializer. Default to zero.
        super.init(frame: CGRect.zero)
        initialize()
    }

    required init?(coder aDecoder: NSCoder) {
        // Called when instantiating from storyboard or nib
        super.init(coder: aDecoder)
        initialize()
    }

    func initialize() {
        print("Execute common initialization code")
    }
}

let button = CustomButton()
print(button.buttonType == .custom)  // true

 

A note about UIButtonType

Since the question was asked in 2012, UIButtonType.roundedRect has been deprecated. The header file comments say to use UIButtonType.system instead.

The following is from UIButton.h converted to Swift in Xcode

public enum UIButtonType : Int {

    case custom // no button type

    @available(iOS 7.0, *)
    case system // standard system button

    case detailDisclosure
    case infoLight
    case infoDark
    case contactAdd

    public static var roundedRect: UIButtonType { get } // Deprecated, use UIButtonTypeSystem instead
}
Mobile Dan
  • 6,444
  • 1
  • 44
  • 44
  • 2
    Not that I'm a fan of storyboards, but the factory method doesn't really work with storyboards. Other than that, it's a really nice strategy. – solidcell Feb 13 '19 at 10:21
13

You may find the discussion at CocoaBuilder's thread How to subclass UIButton? helpful, particularly Jack Nutting's suggestion to ignore the buttonType:

Note that this way the buttonType isn't explicitly set to anything, which probably means that it's UIButtonTypeCustom. The Docs don't seem to actually specify that, but since that's the 0 value in the enum, that's likely what happens (and that seems to be the observable behavior as well)

Caleb
  • 124,013
  • 19
  • 183
  • 272
12

not quite what you're looking for, but remember that your subclass still has the buttonWithType method, and it works fine.

buttonWithType calls your subclasses initWithFrame, and sets the type appropriately.

SubclassButton *myButton=[SubclassButton buttonWithType:UIButtonTypeRoundedRect];
Confused Vorlon
  • 9,659
  • 3
  • 46
  • 49
4

Creating a convenience init worked for me:

class CustomButton: UIButton {
    
    convenience init() {
        self.init(type: .system)
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        /*Do customization here*/
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        
        /*Do customization here*/
    }
}
1

With iOS7, this is more relevant than before, as sometimes you need to use UIButtonTypeSystem, and you can't simply override init and do [MyButton alloc] init], because then it's a UIButtonTypeCustom, which doesn't reflect tintColor nor have nice highlight fade effects.

To subclass, make a static constructor like so:

+ (instancetype)myButtonWithTitle:(NSString *)title imageNamed:(NSString *)imageName target:(id)target action:(SEL)action {
    MyButton *button = [self buttonWithType:UIButtonTypeSystem];
    ... my custom setup for title, image, target, etc ...
    return button;
}
Chris
  • 39,719
  • 45
  • 189
  • 235
1

This is not a good solution, so we need to 'thank' the Apple for use this crutch. XD

//if self.buttonType == .system && self.state.contains(.highlighted) {
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
        self.titleLabel?.alpha = 1.0
    }
//}

Add in touchesBegan(...) for example.

Sergey Sergeyev
  • 776
  • 8
  • 12
0

You can definitely subclass UIButton. I have successfully created a DateButton which looks like a UITextField but when the user touches it it displays a DatePicker in a Popover. Has a property for storing the Date, etc. Let me know if the above never solved your problem and I'll post details more.

Duncan Groenewald
  • 8,496
  • 6
  • 41
  • 76
-2

What about this?

- (id)initWithFrame:(CGRect)frame 
{
    self = [UIButton buttonWithType:UIButtonTypeRoundedRect]; 
    if (self) {
        self.frame = frame;
    }

    return self;
}
Chakalaka
  • 2,807
  • 19
  • 26
  • 7
    That gives you an instance of UIButton, not an instance of the subclass. So, for example, any additional ivars that the subclass adds won't be there. – Caleb Apr 23 '12 at 09:40
  • [UIRoundedRectButton initialize]: unrecognized selector sent to instance 0x8a406a0 – Haris Hussain Apr 23 '12 at 09:42
  • it work on initWithFrame but how to work on InitWithCoder? otherwise please retain it or it leaks~~ – adali Apr 23 '12 at 09:50
  • This doesn't work either.. i have tried so many iterations :P – Haris Hussain Apr 23 '12 at 09:50
  • 1
    @HarisHussain `+initialize` is a class method, not an instance method. See [Objective-C: init vs. initialize](http://stackoverflow.com/a/6191515/643383) for an explanation. Using an instance method with the same name might work, but it also might be part of your problem. – Caleb Apr 23 '12 at 09:51