6

I tried to subclass UIButton to include an activity indicator, but when i use initWithFrame:(since i'm subclassing uibutton i'm not using buttonWithType:) the button doesn't display. Also how would i set the button type in this case?:

my view controller:

    ActivityIndicatorButton *button = [[ActivityIndicatorButton alloc] initWithFrame:CGRectMake(10, 10, 300, 44)];
    [button addTarget:self action:@selector(buttonPressed) forControlEvents:UIControlEventTouchUpInside];
    [button setTitle:@"Older Posts..." forState: UIControlStateNormal];
    [cell addSubview:button];
    [button release];

my activityindicatorbutton class:

#import <Foundation/Foundation.h>


@interface ActivityIndicatorButton : UIButton {

    UIActivityIndicatorView *_activityView;
}

-(void)startAnimating;
-(void)stopAnimating;
@end

@implementation ActivityIndicatorButton

- (id)initWithFrame:(CGRect)frame {
    if (self=[super initWithFrame:frame]) {
        _activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
        _activityView.frame = CGRectOffset(_activityView.frame, 60.0f, 10.0f);

        [self addSubview: _activityView];
    }
    return self;
}

-(void) dealloc{
    [super dealloc];
    [_activityView release];
    _activityView = nil;
}

-(void)startAnimating {
    [_activityView startAnimating];
}

-(void)stopAnimating {
    [_activityView stopAnimating];
}
@end
prostock
  • 9,327
  • 19
  • 70
  • 118

5 Answers5

10

Favour composition over inheritance.

Create a UIView which contains the components you need and add them to your view.

bandejapaisa
  • 26,576
  • 13
  • 94
  • 112
5

I ran into a similar situation, and agree with Jeff that you don't really need to subclass UIButton. I solved this by subclassing UIControl, and then overriding layoutSubviews to do all of the configuration of the views I wanted on my "button". It's a much more simple implementation that subclassing UIButton since there does seem to be some hidden mojo going on under the hood. My implementation looked like this:

- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
    self.opaque = YES;

    self.imageView = [[UIImageView alloc] initWithFrame:CGRectZero];
    [self addSubview:self.imageView];

    self.textLabel = [[UILabel alloc] initWithFrame:CGRectZero];
    [self addSubview:self.textLabel];
    }

return self;
}

And layoutSubviews looked like this:

- (void)layoutSubviews {
[super layoutSubviews];

// Get the size of the button
CGRect bounds = self.bounds;

// Configure the subviews of the "button"
...
}
jmstone617
  • 5,707
  • 2
  • 24
  • 26
  • Could you also add some example how to layout the textLabel and imageView? – Tim Büthe Jun 05 '12 at 12:40
  • in initWithFrame you can alloc/init the textLabel and imageView, and give them both frames of CGRectZero. Add them as subviews of the UIControl here. In layoutSubviews, set the frames of the label and image view. – jmstone617 Jun 06 '12 at 01:01
2

I have created a custom class, preferring composition over inheritance and it works perfect. My custom class has a button and it knows it's MCContact object. Also it draws a proper button and calculates frames automatically using MCContact object, that is passed.

Header file sample:

#import <UIKit/UIKit.h>

@protocol MCContactViewDelegate;

@interface MCContactView : UIView
{

}

@property (nonatomic, strong) MCContact *mcContact;
@property (nonatomic, weak) id <MCContactViewDelegate> delegate;

- (id)initWithContact:(MCContact*)mcContact delegate:(id <MCContactViewDelegate>)delegate;

@end

@protocol MCContactViewDelegate <NSObject>

- (void)contactViewButtonClicked:(MCContactView*)contactView;

@end

Implementation file:

#import "MCContactView.h"

@interface MCContactView()
{
    UIButton *_button;
}

@end

@implementation MCContactView

- (id)initWithContact:(MCContact*)mcContact delegate:(id <MCContactViewDelegate>)delegate
{
    self = [super initWithFrame:CGRectZero];

    if (self) {

        GetTheme();

        _mcContact = mcContact;
        _delegate = delegate;
        _button = [UIButton buttonWithType:UIButtonTypeCustom];

        UIImage *normalBackgroundImage = [[UIImage imageNamed:@"tokenNormal.png"] stretchableImageWithLeftCapWidth:12.5 topCapHeight:12.5];
        [_button setBackgroundImage:normalBackgroundImage forState:UIControlStateNormal];

        UIImage *highlightedBackgroundImage = [[UIImage imageNamed:@"tokenHighlighted.png"] stretchableImageWithLeftCapWidth:12.5 topCapHeight:12.5];
        [_button setBackgroundImage:highlightedBackgroundImage forState:UIControlStateHighlighted];

        _button.titleLabel.font = [theme contactButtonFont];
        [_button setTitleColor:[theme contactButtonTextColor] forState:UIControlStateNormal];

        [_button setTitleEdgeInsets:UIEdgeInsetsMake(4, 6, 4, 6)];

        NSString *tokenString = ([allTrim(mcContact.name) length]>0) ? mcContact.name : mcContact.eMail;
        [_button setTitle:tokenString forState:UIControlStateNormal];

        [_button addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];

        CGSize size = [tokenString sizeWithFont:[theme contactButtonFont]];
        size.width += 20;
        if (size.width > 200) {
            size.width = 200;
        }
        size.height = normalBackgroundImage.size.height;
        [_button setFrame:CGRectMake(0, 0, size.width, size.height)];

        self.frame = _button.frame;
        [self addSubview:_button];
    }

    return self;
}


- (void)buttonClicked:(id)sender
{
    [self.delegate contactViewButtonClicked:self];
}

/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
    // Drawing code
}
*/

@end 
Denis Kutlubaev
  • 15,320
  • 6
  • 84
  • 70
1

You have a pretty obvious problem that concerns your dealloc method: [super dealloc]; must be called AT THE END of your implementation, or else the line after that will try to access a memory space (the ivar space) that has been already deallocated, so it's going to crash.

For the other problem, I'm not sure it's a good idea to put an activity monitor as the subview of a button in general...

Psycho
  • 1,863
  • 1
  • 16
  • 17
  • Not only will in crash, but every example of these that i've come across are very hard to track down. The debugger didn't break at the source of the problem. – bandejapaisa Jan 21 '12 at 00:21
-2

You don’t really want to subclass UIButton. It’s a class cluster, so individual instances will be something like UIRoundRectButton or some other private Apple class. What are you trying to do that requires a subclass?

Jeff Kelley
  • 19,021
  • 6
  • 70
  • 80
  • Hi Jeff, I'm trying to put an activity indicator view into the button. – prostock Feb 18 '11 at 19:50
  • No problem. Just add the activity indicator view as a subview of the button. – Jeff Kelley Feb 18 '11 at 20:10
  • 68
    UIButton is not a class cluster at all. A class cluster is represented by a public abstract class, that means no instance variables, with a bunch of private concrete subclasses that provide the implementation of the abstract methods of the abstract class. UIButton on the other hand is a concrete class, none of its methods is abstract, and it has instance variables to store the value you pass through its arguments. The only problematic part is that +buttonWithType can instantiate subclasses instead of UIButton directly, thus it can be seen as a factory method, not a class-cluster... – Psycho Jun 29 '11 at 05:09
  • Psycho is correct. How do I know? Because I subclassed UIButton to implement my own buttons (basically uses runtime attributes in IB to define what the button looks like). – Feloneous Cat Dec 12 '12 at 19:48
  • 4
    To add up on @Psycho's comment, from the `buttonWithType:` doc: _This method is a convenience constructor for creating button objects with specific configurations. It you subclass UIButton, this method does not return an instance of your subclass. If you want to create an instance of a specific subclass, you must alloc/init the button directly._ – samvermette Mar 10 '13 at 06:43