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
Favour composition over inheritance.
Create a UIView which contains the components you need and add them to your view.
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"
...
}
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
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...
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?
Related
I am programmatically creating a view and a imageview while in viewControllerA for an another viewControllerB before I then goto viewControllerB. I have to do this, as I am using an image from the imagePickerController. However by doing this, touchesBegan does not function in viewControllerB
I have set UserInteractionEnabled to true/yes in both the attributes inspector of the xib and programmatically everywhere!
My xib is just a blank canvas, with one View. I have tried different settings with the connections inspector, but still no luck.
Here is my code.
viewControllerA.m
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
CGRect screenRect = [[UIScreen mainScreen] applicationFrame];
view = [[UIView alloc] initWithFrame:screenRect];
image = [info valueForKey:UIImagePickerControllerEditedImage];
SSViewController *psView = [[SSViewController alloc] initWithNibName:#"SSViewController" bundle:nil];
psView.view = [[UIView alloc] initWithFrame:screenRect];
[psView.view setUserInteractionEnabled:YES];
[picker pushViewController:psView animated:YES];
imageView = [[UIImageView alloc] initWithImage:image];
imageView.frame = CGRectMake(20, 20, 280, 280);
[imageView setUserInteractionEnabled:YES];
[psView.imageView setUserInteractionEnabled:YES];
[psView.view addSubview:imageView];
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[button addTarget:psView action:#selector(endPS:) forControlEvents:UIControlEventTouchUpInside];
[button setTitle:#"done" forState:UIControlStateNormal];
button.frame = CGRectMake(140.0, 330.0, 80.0, 25.0);
[psView.view addSubview:button];
}
viewControllerB.h (SSViewController.h)
#interface SSViewController : UIViewController
{
IBOutlet UIImageView *imageView;
IBOutlet UIView *view;
}
#property (nonatomic,strong) IBOutlet UIImageView *imageView;
#property (nonatomic,strong) IBOutlet UIImage *image;
#property (nonatomic, strong) IBOutlet UIView *view;
- (IBAction)endPS:(id)sender;
#end
viewControllerB.m (SSViewController.m)
#implementation SSViewController
#synthesize imageView;
#synthesize image;
#synthesize view;
:
:
- (void)viewDidLoad
{
[view setUserInteractionEnabled:YES];
[imageView setUserInteractionEnabled:YES];
:
:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"touchesBegan");
:
You're doing a number of things wrong:
Don't push a view controller on to the UIImagePickerController. If you've presented it modally, which I hope you have, you need to dismiss it (in viewControllerA) and push your new view controller (viewControllerB) onto your own navigation controller. The fact that you're pushing onto this highly customised navigation controller is most likely why touchesBegan is not being called.
Why are you manually creating the view for viewControllerB? If you're loading it from a XIB file, it will have a perfectly good view ready for you. And don't define the view property in viewControllerB.h, it's already defined in UIViewController.h.
Don't forget to call [super viewDidLoad] in viewControllerB.m. This is a classic mistake that will cause you many headaches.
EDIT:
What I would personally have done is added a method on viewControllerB as such:
viewControllerB.h
#interface SSViewController : UIViewController
{
- (void)setImage:(UIImage *)image;
}
#end
viewControllerB.m
#implementation SSViewController
{
- (void)setImage:(UIImage *)image
{
// In case the image view already exists, remove it first.
[_imageView removeFromSuperview];
_imageView = [[UIImageView alloc] initWithImage:image];
_imageView.frame = CGRectMake(20, 20, 280, 280);
[self.view addSubview:_imageView];
}
}
#end
This way, in viewControllerA.m you can just create viewControllerB.m and call this setImage: method to let viewControllerB do all the work.
I have made a simple subclass of UIView which has a few subviews but its breaking for a reason I can't see.
In my storyboard view controller I have added a UIView and made it the shape I want it, and added my custom class to it. The contents of my subclass is below:
#implementation PersonDetailCell
#synthesize button, requiredMarker, textField;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
-(void)layoutSubviews
{
[self setBackgroundColor:[UIColor colorWithRed:87.0f/255.0f green:153.0f/255.0f blue:191.0f/255.0f alpha:1.0f]];
textField = [[DetailTextField alloc] initWithFrame:CGRectMake(10, 0, self.frame.size.width -20, self.frame.size.height)];
[textField setFont:[UIFont fontWithName:#"Helvetica" size:14.0f]];
[textField setTextColor:[UIColor whiteColor]];
[textField setHidden:YES];
[self addSubview:textField];
button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
[button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[button setHidden:YES];
[self addSubview:button];
requiredMarker = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 4, 22)];
[requiredMarker setBackgroundColor:[UIColor redColor]];
[requiredMarker setHidden:YES];
[self addSubview:requiredMarker];
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
#end
Then in my view controller code I have imported my subclass and hooked up an IBOutlet of it to my view in my storyboard. In my viewDidLoad of my view controller I also try setting background colour of this view but nothing happens.
All of my code runs without crashing, but my issues:
I can't set the background colour from my view controller, (or any other properties for that matter).
When I run the app, the height is more than that I set in my storyboard (width is fine), but I change the height of the view nowhere.
Any ideas? Am i using the wrong approach somehow?
Thanks.
If it is a table cell, then the class should be extending UITableViewCell. Buttons and textfields can be added on the storyboard - then you can use struts and springs (Size Inspector pane) to ensure the view resizes properly. I don't think layoutSubviews should be used for adding new UI elements (maybe for resizing if you need to do it manually).
Here is some working code:
#interface ChildView()
#property (strong, nonatomic) UITextField *textField;
#property (strong, nonatomic) UIButton *button;
#property (strong, nonatomic) UIImageView *requiredMarker;
#end
#implementation ChildView
#synthesize textField= _textField;
#synthesize button = _button;
#synthesize requiredMarker = _requiredMarker;
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
{
[self setBackgroundColor:[UIColor colorWithRed:87.0f/255.0f green:153.0f/255.0f blue:191.0f/255.0f alpha:1.0f]];
self.textField = [[UITextField alloc] init];
[self.textField setFont:[UIFont fontWithName:#"Helvetica" size:14.0f]];
[self.textField setTextColor:[UIColor whiteColor]];
self.textField.text = #"Test text";
[self addSubview:self.textField];
self.button = [[UIButton alloc] init];
self.button.backgroundColor = [UIColor greenColor];
[self addSubview:self.button];
self.requiredMarker = [[UIImageView alloc] init];
[self.requiredMarker setBackgroundColor:[UIColor redColor]];
[self addSubview:self.requiredMarker];
}
return self;
}
-(void)layoutSubviews
{
CGSize hostSize = self.frame.size;
float length = 22;
self.textField.frame = CGRectMake(10, length, hostSize.width, hostSize.height-length);
self.button.frame = CGRectMake(0, 0, hostSize.width, length);
self.requiredMarker.frame = CGRectMake(0, 0, 4, length);
}
#end
I am slowly developing a custom button (while learning how to implement classes etc).
I have a ViewController, which imports a SuperButton.h (UIControl) and then creates an instance of the SuperButton. (This works, as proved by a NSLog.)
But I cannot get the method in SuperButton to display a Label.
I think this might have something to with the '.center' value or the 'addSubview' command?
I would really appreciate your help. Thanks.
Here is my SuperButton.m code:
#import "SuperButton.h"
#implementation SuperButton
#synthesize firstTitle;
#synthesize myLabel;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (void) shoutName{
NSLog(#"My name is %#", firstTitle);
self.backgroundColor = [UIColor blueColor];
CGRect labelFrame = CGRectMake(0.0f, 0.0f, 100.0f, 50.0f);
self.myLabel = [[UILabel alloc] initWithFrame:labelFrame];
self.myLabel.text = #"Come on, don't be shy.";
self.myLabel.font = [UIFont italicSystemFontOfSize:14.0f];
self.myLabel.textColor = [UIColor grayColor];
self.myLabel.center = self.center;
[self addSubview:self.myLabel];
}
Here is the code in my ViewController:
- (void) makeButton{
SuperButton *button1 = [[SuperButton alloc] init];
button1.firstTitle = #"Mr. Ploppy";
[button1 shoutName];
}
(EDIT:) Just in case, here's the SuperButton.h code:
#import <UIKit/UIKit.h>
#interface SuperButton : UIControl
#property (nonatomic, strong) NSString *firstTitle;
#property (nonatomic, strong) UILabel *myLabel;
- (void) shoutName;
#end
I found an answer elsewhere.
I needed to addSubview the 'button'. My working code now looks like this:
- (void) makeButton{
SuperButton *button1 = [[SuperButton alloc] init];
button1.firstTitle = #"Mr. Ploppy";
[button1 shoutName];
[self.view addSubview:button1.myLabel];
[self.view sendSubviewToBack:button1.myLabel];
}
You are initializing your button not with initWithFrame: method, but with simple init. This makes the button of CGRectZero size. Change this line:
SuperButton *button1 = [[SuperButton alloc] init];
to this:
SuperButton *button1 = [[SuperButton alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 100.0f, 50.0f)];
or add a call to setFrame: after initializing your button.
I am new to iPhone programming. And I have an issue. I need to create a custom user control that I will add to my UIScrollView dinamically. The control has an UITextField and an UIButton. See the code below:
#import <UIKit/UIKit.h>
#interface FieldWithValueControl : UIView {
UITextField *txtTagName;
UIButton *addButton;
}
#property (nonatomic, readonly) UITextField *txtTagName;
#property (nonatomic, readonly) UIButton *addButton;
#end
#import "FieldWithValueControl.h"
#define ITEM_SPACING 10
#define ITEM_HEIGHT 20
#define SWITCHBOX_WIDTH 100
#define SCREEN_WIDTH 320
#define ITEM_FONT_SIZE 14
#define TEXTBOX_WIDTH 150
#implementation FieldWithValueControl
#synthesize txtTagName;
#synthesize addButton;
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// Initialization code
txtTagName = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, TEXTBOX_WIDTH, ITEM_HEIGHT)];
txtTagName.borderStyle = UITextBorderStyleRoundedRect;
addButton = [UIButton buttonWithType:UIButtonTypeContactAdd];
[addButton setFrame:CGRectMake(ITEM_SPACING + TEXTBOX_WIDTH, 0, ITEM_HEIGHT, ITEM_HEIGHT)];
[addButton addTarget:self action:#selector(addButtonTouched:)
forControlEvents:UIControlEventTouchUpInside];
[self addSubview:txtTagName];
[self addSubview:addButton];
}
return self;
}
- (void)addButtonTouched:sender
{
UIButton *button = (UIButton*)sender;
NSString *title = [button titleLabel].text;
}
- (void)drawRect:(CGRect)rect {
// Drawing code
}
- (void)dealloc {
[txtTagName release];
[addButton release];
[super dealloc];
}
#end
In my code I create an object of that class and add it to scrollView on form.
FieldWithValueControl *newTagControl = (FieldWithValueControl*)[[FieldWithValueControl alloc] initWithFrame:CGRectMake(ITEM_SPACING, currentOffset + ITEM_SPACING, 0, 0)];
[scrollView addSubview:newTagControl];
The control looks fine, but if I click to the textbox or to the button nothing happens. Keyboard doesn't appear, the button is not clickable etc.
Set width and height values for your control more then 0:
FieldWithValueControl *newTagControl = [[FieldWithValueControl alloc] initWithFrame:
CGRectMake(ITEM_SPACING, currentOffset + ITEM_SPACING, 100, 200)];
try setting the delegate to self.
This is so damn simple im sure! Im missing something and im exhausted from trying to fix it. hopefully someone can help.
The Button in CharacterView.m works but the button nested down in CharacterMale.m does not. I'm not using IB everything is done progmatically.
What would cause one button to work and other not?
/////////////////////////////////////////////////////////////////////////////////
CharacterController.m
/////////////////////////////////////////////////////////////////////////////////
#import "CharacterController.h"
#import "CharacterView.h"
#implementation CharacterController
- (id)init {
NSLog(#"CharacterController init");
self = [ super init ];
if (self != nil) {
}
return self;
}
- (void)loadView {
[ super loadView ];
characterView = [ [ CharacterView alloc ] init];
self.view = characterView;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (void)dealloc {
[super dealloc];
}
#end
/////////////////////////////////////////////////////////////////////////////////
CharacterView.m
/////////////////////////////////////////////////////////////////////////////////
#import "CharacterView.h"
#import "CharacterMale.h"
#implementation CharacterView
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
characterMale = [ [ CharacterMale alloc ] init];
[self addSubview: characterMale];
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(0, 200, 200, 100);
[button setImage:[UIImage imageNamed:#"btnCharSelect.png"] forState:UIControlStateNormal];
[button addTarget:self action:#selector(ApplyImage:) forControlEvents:UIControlEventTouchUpInside];
[ self addSubview: button ];
}
return self;
}
- (void)drawRect:(CGRect)rect {
}
-(void)ApplyImage:(id)sender
{
NSLog(#"CharacterView button works");
}
- (void)dealloc {
[super dealloc];
}
#end
/////////////////////////////////////////////////////////////////////////////////
CharacterMale.m
/////////////////////////////////////////////////////////////////////////////////
#import "CharacterMale.h"
#import "CharacterController.h"
#implementation CharacterMale
- (id)init {
self = [ super init];
if (self != nil) {
UIImage *image = [UIImage imageNamed:#"charMale.png"];
imageView = [[ UIImageView alloc] initWithImage:image];
[image release];
[ self addSubview: imageView ];
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(0, 0, 200, 100);
[button setImage:[UIImage imageNamed:#"btnCharSelect.png"] forState:UIControlStateNormal];
[button addTarget:self action:#selector(ApplyImage:) forControlEvents:UIControlEventTouchUpInside];
[ self addSubview: button ];
}
return self;
}
-(void)ApplyImage:(id)sender
{
NSLog(#"CharacterMal button works");
}
- (void)dealloc {
[super dealloc];
}
#end
FINALLY!!!!!!
I had to init all the views with initWithFrame and pass in valid frame rects. Init should be used with controllers and initWithFrame passing rects for UIViews!!!!
characterView = [ [ CharacterView alloc ] initWithFrame:CGRectMake(0, 0, 480, 320)];
then
characterMale = [ [ CharacterMale alloc ] initWithFrame:frame];
Another thing to consider is that UIButton subviews that have been added a UIImageView don't work at all. You need to create a UIView that you add both the image view and the buttons to.
This is probably because interaction is turned off by default on image views, but I've not checked this.
Does the new view accept user interaction? In other words, is userInteractionEnabled enabled on the view "Characters"?
I had a similar issue when tried to add the button during the initialization of an UIView with a frame of CGRectZero:
#implementation SomeView
...
- (id)initWithTitleImage:(UIImage *) newTitleImage {
// BROKEN: frame with CGRectZero.
self = [super initWithFrame:CGRectZero];
if (self) {
UIButton *someButton = ...;
[self addSubview someButton];
}
}
Once I changed the frame to a proper rectangle, the button worked:
#implementation SomeView
...
- (id)initWithTitleImage:(UIImage *) newTitleImage {
// WORKING: frame with proper CGRect.
self = [super initWithFrame:CGRectMake(0, 0, 480, 320)];
if (self) {
UIButton *someButton = ...;
[self addSubview someButton];
}
}
I have had luck with a selector for a UIButton created in drawRect by using UIControlEventTouchDown instead of the popular UIControlEventTouchUpInside.
For me the issue was caused because, the origin of the button frame was bigger then the parent frame.