Need help with Custom UIButton Class

Discussion in 'iOS Programming' started by Hawkeye75, May 20, 2010.

  1. Hawkeye75 macrumors member

    Joined:
    Mar 29, 2010
    #1
    Hi folks, I'm still new to Cocoa Touch so bear with me. It's probably a concept I am missing but here goes.

    I want to create a custom UIButton class so it can behave different from a regular button. I want the class to inherit most of it methods from it's superclass and just draw the button from interface builder. The problem is I'm looking to simply change the background color back and and forth every time the button is pressed. However I can't seem to find any appropriate methods that can do this when reviewing the docs.

    Any Ideas?

    Thanks
     
  2. ritsard macrumors regular

    ritsard

    Joined:
    Jun 18, 2009
    Location:
    SF Bay Area, CA
    #2
    if you just want the button color to change, you probably can do this on a the action associated with the button. Since UIButton inherits from UIView, you should be able to access the backgroundColor property.
     
  3. Hawkeye75 thread starter macrumors member

    Joined:
    Mar 29, 2010
    #3
    I realize I can tie the button to an IBAction method in the UIViewController. However I had hoped to make it more as behavioural change within the Button Class. That way every time I created the button it would be automatic instead of adding the code each time.
     
  4. Hawkeye75 thread starter macrumors member

    Joined:
    Mar 29, 2010
    #4
    Ok I think I can make it work with the inherited method from UIControl
    addTarget:action:forControlEvents:

    But I'm just having a hell of a time trying to initialize the button itself.
     
  5. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
  6. Hawkeye75 thread starter macrumors member

    Joined:
    Mar 29, 2010
    #6
    Well doing some research online it doesn't seem like people recommend subclassing UIButton.

    To save me the hassle I just decided to create the code in the UIViewController after all. I've created a custom button and just set the background image. However now I have an entirely different problem. The button property currentBackgroundImage doesn't seem to contain the image name as I expected because my if statement does not work.

    Code:
    - (IBAction)buttonPressed {
    	
    	if(testButton.currentBackgroundImage == @"whiteButton.png") {
    		
    		UIImage *buttonImageNormal = [UIImage imageNamed:@"blueButton.png"];
    		UIImage *stretchableButtonImageNormal = [buttonImageNormal stretchableImageWithLeftCapWidth:12 topCapHeight:0];
    		[testButton setBackgroundImage:stretchableButtonImageNormal forState:UIControlStateNormal];
    	}
    	else {
    		UIImage *buttonImageNormal = [UIImage imageNamed:@"whiteButton.png"];
    		UIImage *stretchableButtonImageNormal = [buttonImageNormal stretchableImageWithLeftCapWidth:12 topCapHeight:0];
    		[testButton setBackgroundImage:stretchableButtonImageNormal forState:UIControlStateNormal];
    	}
        
    }
    I've already set the button's background image to "whiteButton.png" in the ViewDidLoad method.
     
  7. PhoneyDeveloper macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #7
    If you're going to create a custom button you can subclass UIButton with no problems. Create the button in code with initWithFrame.

    In your case, call addTarget:action:forControlEvents: for the events you're interested in and set the target to the button itself. The button subclass can then manage its own state.

    If you want a rounded rect button then the only way to subclass it that I know is to create the button in a nib. Create a nib with a rounded rect button and then set the class to your subclass. This should work.

    You can't compare strings with ==. You must use isEqualToString: But keeping the state of a button by setting a string is bogus. Use a BOOL or an enum.
     
  8. Hawkeye75 thread starter macrumors member

    Joined:
    Mar 29, 2010
    #8
    The only time I've ever used initWithFrame is within a method I know that get's triggered by the system. i.e. viewDidLoad. Can you use an example of how you would use initWithFrame from within a UIButton subclass?

    Although I would normally agree with you, it's at least a known property of the button that I didn't have to implement myself.
     
  9. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #9
    Except currentBackgroundImage returns a UIImage type object and not an NSString.
     
  10. PhoneyDeveloper macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #10
    From my button subclass:

    Code:
    - (id)initWithFrame:(CGRect)frame 
    {
        if ((self = [super initWithFrame:frame])) 
    	{
                 // Initialization code
                 [self setUpButton];
        }
        return self;
    }
    
    You create your button with

    Code:
    MyButton* button = [[MyButton alloc] initWithFrame:frame];
    
    The problem with UIButton is that you normally create buttons using the [UIButton buttonWithType:] factory method. But it won't create a button of your kind. At any rate you need a way to create a button of your kind. initWithFrame is that way. Obviously setUpButton adds the setup to your button that you need.
     
  11. Hawkeye75 thread starter macrumors member

    Joined:
    Mar 29, 2010
    #11
    Doh...my apologies.
     
  12. Hawkeye75 thread starter macrumors member

    Joined:
    Mar 29, 2010
    #12
    Thanks, I'll give that a try tonight.
     
  13. admanimal macrumors 68040

    Joined:
    Apr 22, 2005
    #13
    Personally I would use the tag property of the button (actually I believe it is inherited from UIView). It's just an int value that you have control of and can use it to check the identity of your views. Just make sure you assign some non-zero value to it before you try to check its value later.
     
  14. chown33 macrumors 604

    Joined:
    Aug 9, 2009
    #14
    This is the wrong way to check for string equality. Use either the isEqual: method or the isEqualToString: method.

    The posted code is testing whether the returned property has the same id as the literal string @"whiteButton.png". In other words, it's checking if they are the same NSString pointer.
     
  15. Hawkeye75 thread starter macrumors member

    Joined:
    Mar 29, 2010
    #15
    I know, I completely forgot about this. I currently work with 2 other programming languages and I took a little break from cocoa. I think sometimes my head is going to explode.

    I'm going to try and subclass UIButton again tonight and if that doesn't work I'll just go with the way I know and assign property to track it's current background.
     
  16. Hawkeye75 thread starter macrumors member

    Joined:
    Mar 29, 2010
    #16
    Phew...had to dig deep for this one but I finally got it. I found out initWithFrame never gets called when you subclass the button in IB. Instead you use initWithCoder and it works like a charm. Here is the code I used for those interested.

    CustomButton.h

    Code:
    #import <UIKit/UIKit.h>
    
    @interface CustomButton : UIButton {
    	NSInteger toggle;
    }
    
    @property (nonatomic, assign) NSInteger toggle;
    
    - (void)buttonPressed: (id)sender;
    
    @end
    
    CustomButton.m

    Code:
    #import "CustomButton.h"
    
    
    @implementation CustomButton
    
    @synthesize toggle;
    
    - (id) initWithCoder:(NSCoder *)coder
    {
    	if (self = [super initWithCoder:coder]) {
    		
    		self.toggle = 0;
    		
    		UIImage *buttonImageNormal = [UIImage imageNamed:@"whiteButton.png"];
    		UIImage *stretchableButtonImageNormal = [buttonImageNormal stretchableImageWithLeftCapWidth:12 topCapHeight:0];
    		[self setBackgroundImage:stretchableButtonImageNormal forState:UIControlStateNormal];
    		
    		UIImage *buttonImagePressed = [UIImage imageNamed:@"blueButton.png"];
    		UIImage *stretchableButtonImagePressed = [buttonImagePressed stretchableImageWithLeftCapWidth:12 topCapHeight:0];
    		[self setBackgroundImage:stretchableButtonImagePressed forState:UIControlStateHighlighted];
    		
    		[self addTarget: self action: @selector(buttonPressed:) forControlEvents: UIControlEventTouchUpInside];
    		
    	}
    	return self;
    }
    
    - (void)buttonPressed: (id)sender
    {
    	if (toggle == 0) {
    		UIImage *buttonImageNormal = [UIImage imageNamed:@"blueButton.png"];
    		UIImage *stretchableButtonImageNormal = [buttonImageNormal stretchableImageWithLeftCapWidth:12 topCapHeight:0];
    		[self setBackgroundImage:stretchableButtonImageNormal forState:UIControlStateNormal];
    		self.toggle = 1;
    	}
    	else {
    		UIImage *buttonImageNormal = [UIImage imageNamed:@"whiteButton.png"];
    		UIImage *stretchableButtonImageNormal = [buttonImageNormal stretchableImageWithLeftCapWidth:12 topCapHeight:0];
    		[self setBackgroundImage:stretchableButtonImageNormal forState:UIControlStateNormal];
    		self.toggle = 0;
    	}
    }
    
    
    - (void)dealloc {
        [super dealloc];
    }
    
    
    @end
    
     
  17. PhoneyDeveloper macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #17
    Glad you got it figured out. Not complicated at all in the end.

    Minor point. If your toggle is a BOOL then you can swap it with

    Code:
    self.toggle = ! self.toggle;
    As an int you can swap it like this

    Code:
    self.toggle = 1 - self.toggle;
    You can always toggle an int between two values by subtracting the current value from the sum of the two possible values.
     
  18. Hawkeye75 thread starter macrumors member

    Joined:
    Mar 29, 2010
    #18
    At the time I just wanted proof of concept but that is absolutely a better way to go. I went back and changed my code to use a boolean value instead. Thanks for the tip!
     

Share This Page