Reuse UIButton style - ios

I've got a couple of UIButtons with all the same style, but sometimes they have different colors and sometimes they have different widths. However, I hate to make a seperate class for a new button that just needs a slightly different color, so I thought that there must be a way to style a button and take, for instance, the color and height as a property. After that I image it would work just as importing a UITableView's datasource/delegate.
So a more concrete question: is there a way to make a class for a UIButton that takes parameters such as width and height, and is it possible to declare this kind of class to a UIButton?

That's no problem at all. Create a category or subclass of UIButton. Define a custom init method like - (instancetype)initWithColor:(UIColor *)color size:(CGSize)size. In the init method call self = [super initWithFrame:CGRectZero] and then set those properties on self.

About the frequently used style, you can make a method which return a styled button.
- (UIButton *)styledButtonWithFrame:(CGRect)frame
{
UIButton *btn = [[UIButton alloc] initWithFrame:frame];
[btn setBackgroundColor:[UIColor redColor]];
[btn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
return btn;
}
and you can create an instance of styledButton
UIButton *styledBtn = [self styledButtonWithFrame:CGRectMake(0, 0, 10, 10)];
The second question, you can use category.
#interface UIView (BKExpansion)
#property (nonatomic, assign) CGFloat frameX;
#property (nonatomic, assign) CGFloat frameHeight;
#end
#implementation UIView (BKExpansion)
- (CGFloat)frameX
{
return self.frame.origin.x;
}
- (void)setFrameX:(CGFloat)frameX
{
CGRect frame_ = self.frame;
frame_.origin.x = frameX;
self.frame = frame_;
}
- (CGFloat)frameWidth
{
return self.frame.size.width;
}
- (void)setFrameWidth:(CGFloat)frameWidth
{
CGRect frame_ = self.frame;
frame_.size.width = frameWidth;
self.frame = frame_;
}
#end
You can add frameY, frameBottom, and so on.
If you want to change frameX or frameWidth, just use
[styledBtn setFrameY:30];

Related

UIButton in a custom UIView is not being triggered when tapped upon

I've placed a UIButton inside a custom UIView, CustomButtonView. The UIButton is not responding to tap/click events, and its appropriate selector method is not being triggered. Here's my code below; what have I done wrong?
As well, the custom view has a background color of red. When I add the custom view to my view controller's view, the red rectangle displays. However, when I add my UIButton to my custom view (with the background color of red), only the UIButton displays, not the red rectangle. Why is this happening?
//
// CustomButtonView.h
//
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
#interface CustomButtonView : UIView
#property (strong, nonatomic) UIImageView *buttonImageView;
#property (strong, nonatomic) UIButton *button;
#end
NS_ASSUME_NONNULL_END
//
// CustomButtonView.m
//
#import "CustomButtonView.h"
#implementation CustomButtonView
- (instancetype)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor redColor];
_button = [UIButton buttonWithType:UIButtonTypeSystem];
_button.backgroundColor = [UIColor greenColor];
[_button setTitle:#"john doe" forState:UIControlStateNormal];
[_button setTitle:#"john doe" forState:UIControlStateHighlighted];
_button.translatesAutoresizingMaskIntoConstraints = NO;
_button.titleLabel.font = [UIFont boldSystemFontOfSize:14.0];
_button.titleLabel.textColor = [UIColor whiteColor];
self.userInteractionEnabled = NO;
self.button.userInteractionEnabled = YES;
//[self addSubview:_button];
//[_button.topAnchor constraintEqualToAnchor:self.topAnchor].active = YES;
//[_button.leadingAnchor constraintEqualToAnchor:self.leadingAnchor].active = YES;
}
return self;
}
#end
//
// ViewController.m
// TestApp
//
//
#import "ViewController.h"
#import "CustomButtonView.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor yellowColor];
_customButtonView = [[CustomButtonView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
_customButtonView.backgroundColor = [UIColor redColor];
_customButtonView.translatesAutoresizingMaskIntoConstraints = NO;
[_customButtonView.button addTarget:self action:#selector(customButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:_customButtonView];
}
- (void)customButtonTapped:(id)sender{
NSLog(#"Button tapped!");
}
#end
Here are some screenshots, below. Please click on the links.
When I display the UIButton inside the custom-view, only the button displays. The red rectangle of the custom-view doesn't display.
When I simply add the custom-view to my view-controller (ie with no UIButton added to the custom view), the red rectangle of the custom-view displays.
When you disable user interaction for a specific view then this user interaction is not forwarded to its subviews. Or in other words; for user interaction to work you need to make sure that all superviews in chain (superview of superview of superview) do have user interaction enabled.
In your case you have self.userInteractionEnabled = NO; which means that any subview of self (including your button) will not accept any user interaction.
Next to this issues it is still possible that other issues are present. For instance: A subview will accept user interaction only within physical area of all of its superviews. That means that if a button with frame { 0, 0, 100, 100 } is placed on a superview of size { 100, 50 } then only the top part of the button will be clickable.
As for the size changes this is a completely different story...
You decided to disable translation of autoresizing mask on your custom view by writing _customButtonView.translatesAutoresizingMaskIntoConstraints = NO;. That means that you will not use frame in conjunction with other constraints. It means that whatever frame you set (in your case CGRectMake(0, 0, 100, 100)) this frame will be overridden by anything else that may effect the frame of your custom view.
In case where your button code is commented out there is no such thing that "may effect the frame of your custom view" and the view stays as size of { 100, 100 }. But as soon as you added a button you added additional constraints which most likely lead to resizing your button to sizeToFit and with it its superview which is your custom view.
To avoid this you need to either remove disabling of translation of autoresizing mask into constraint on your custom view. Or you need to define custom view size by adding constraints to it so that they are set to { 100, 100 }. You can simply constrain heightAnchor and widthAnchor to 100.

Adding action to custom uiview

I have been trying to do this simple thing : adding an action to a simple custom view. I have looked over the internet and found two "easy" solution :
UITapGestureRecognizer
UIButton
I want to do this programmatically and I just need to handle the tap.
Here is my code so far, I've tried both solutions separately and together and it doesn't work !
.m
#import "AreaView.h"
#implementation AreaView
#define GREY 27.0/255.0
#define PINK_R 252.0/255.0
#define PINK_G 47.0/255.0
#define PINK_B 99.0/255.0
- (id) initWithFrame:(CGRect)frame imageName:(NSString *)imageName areaName:(NSString *)areaName minimumSpending:(int)minimumSpending andCapacity:(int)capacity
{
self = [self initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor colorWithRed:GREY green:GREY blue:GREY alpha:1];
self.userInteractionEnabled=YES;
//Init variables
_areaName=areaName;
_capacity=capacity;
_minimumSpending=minimumSpending;
//Image view
_logoImageView = [[UIImageView alloc]initWithFrame:CGRectMake(5, 4, 66, 50)];
//_logoImageView.image = [UIImage imageNamed:imageName];
_logoImageView.backgroundColor = [UIColor grayColor];
//Label
_areaNameLabel = [[UILabel alloc]initWithFrame:CGRectMake(0, 54, 76, 18)];
_areaNameLabel.textAlignment = NSTextAlignmentCenter;
_areaNameLabel.textColor = [UIColor whiteColor];
_areaNameLabel.font = [UIFont systemFontOfSize:12.0];
_areaNameLabel.text = areaName;
//button
_button = [[UIButton alloc]initWithFrame:self.bounds];
_button.userInteractionEnabled=YES;
_button.backgroundColor=[UIColor yellowColor];
[_button addTarget:self action:#selector(handleTap:) forControlEvents:UIControlEventTouchUpInside];
//tap gesture racognizer
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapped:)];
[tapRecognizer setNumberOfTapsRequired:1];
[tapRecognizer setDelegate:self];
[self addGestureRecognizer:tapRecognizer];
[self addSubview:_logoImageView];
[self addSubview:_areaNameLabel];
[self addSubview:_button];
}
return self;
}
-(void)handleTap:(UIButton *)button
{
NSLog(#"tapped!");
}
-(void)tapped:(UITapGestureRecognizer *)recognizer
{
NSLog(#"tapped!");
}
#end
.h
#import <UIKit/UIKit.h>
#interface AreaView : UIView <UIGestureRecognizerDelegate>
#property (nonatomic) UIImageView *logoImageView;
#property (nonatomic) UILabel *areaNameLabel;
#property (nonatomic) NSString *areaName;
#property (nonatomic) int minimumSpending;
#property (nonatomic) int capacity;
- (id) initWithFrame:(CGRect)frame imageName:(NSString *)imageName areaName:(NSString *)areaName minimumSpending:(int)minimumSpending andCapacity:(int)capacity;
#property (nonatomic, strong) UIButton *button;
#end
Thanks for your help!
EDIT
The problem is that both handleTap and tapped are never fired even if I comment the button solution or the tap gesture one to test them separately. For the button implementation, I can see it on my interface but clicking on it does nothing.
My UIView is then added programmatically several times (for several views) in a UIScrollview.
EDIT 2
The problem is more complicate than that. The custom view is inside a scrollview which is inside another different custom view whose main function is to rewrite hittest, so that touches on this view are held by the scrollview. (Here is the purpose of all that).
It seems that as long as hittest is involved, it doesn't work.
Implement this delegate method, which will help you.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
id touchedView = gestureRecognizer.view;
if ([touchedView isKindOfClass:[UIButton class]])
{
return NO; //It won't invoke gesture method, But it'll fire button method.
}
return YES;
}
I'm not sure why your UIButton and UITapGestureRecognizer selectors aren't firing. However another option to handle taps on a view is to simply override the touchesEnded:withEvent method:
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
//handle a tap
}
That way, you won't have to create a UIButton or a UITapGestureRecognizer object at all.

Properly cast a UILabel

I've created a custom sub-class GTLabel of UILabel to have the right font, size, color, etc...
When I create a GTLabel programmatically, everything works fine. When I create a UILabel in the IB, I just have to change its class into GTLabel and it works.
Now when I have a UIButton, with a titleLabel, I would like to transform this UILabel into a GTLabel.
I've created a class method in GTLabel :
+ (GTLabel*)labelFromLabel:(UILabel*)label
{
...
return myGTLabel;
}
I don't really see how I'm suppose to proceed in this method.
Am I suppose to do as bellow?
GTLabel *myGTLabel = [[GTLabel alloc] init];
// Get all the properties of the original label
myGTLabel.text = label.text;
myGTLabel.frame = label.frame;
// Do the modifications
myGTLabel.font = [UIFont fontWithName:#"Gotham-Light"
size:label.font.pointSize];
The idea would be to do something like
myButton.titleLabel = [GTLabel labelFromLabel:myButton.titleLabel];
Thanks for your help !
You could implement your custom UIButton ( eg. GTButton ) and redefine titleLabel property, setting the same propertylabel used for your GTLabel
something like:
#import "GTButton.h"
#implementation GTButton
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
-(UILabel *)titleLabel
{
UILabel* parentLabel = [super titleLabel];
parentLabel.font = [UIFont fontWithName:#"Gotham-Light"
size:parentLabel.font.pointSize];
// ... set all your attributes
return parentLabel;
}
#end
titleLabel method of UIButton is read-only.
Although this property is read-only, its own properties are read/write. Use these properties primarily to configure the text of the button.
For example:
UIButton *button = [UIButton buttonWithType: UIButtonTypeSystem];
button.titleLabel.font = [UIFont systemFontOfSize: 12];
button.titleLabel.lineBreakMode = UILineBreakModeTailTruncation;
Create custom class for UIButton as you create for label and set property in it.

Pass UIButton event from subview to superview

I have a UIView like a menubarView with buttons.
I added this menubarView to another view (parentView).
I need to call a method on the parentView on TouchUpInside of menubar button.
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame = CGRectMake(x, y, width, height);
[button addTarget:self action:#selector(myLocalMethod:) forControlEvents:UIControlEventTouchDown];
[button setTitle:#"Call My Method" forState:UIControlStateNormal];
[self addSubview:button];
Solution:
In menuBarView I added this code:
- (void)myLocalMethod:(UIButton *)button
{
ParentView *parent = (ParentView *)self.superview;
[parent myMethod:button];
}
Try this in myMethod
[((<SuperViewName>)self.superview) <methodInSuperView>]
Replace the placeholders SuperViewName and methodInSuperView with your names.
Explanation: I am casting the superview to the appropriate superview class and the invoking the required superview method. Make sure the methodInSuperView is public (Declare it in interface).
One option is to provide callback blocks on the subview:
#property (nonatomic, copy) void(^onDidTapMyButton1)();
Then implement an action (or a target and selector) on the subview that calls this callback:
- (IBAction)didTapMyButton1 {
if (self.onDidTapMyButton1) {
self.onDidTapMyButton1();
}
}
In your parent view you can then do:
mySubView.onDidTapMyButton1 = ^{
// perform some action
};
You can also do this with a single callback for all buttons:
#property (nonatomic, copy) void(^onDidTapButton)(UIButton *tappedButton);
- (IBAction)didTapButton:(UIButton *)sender {
if (self.onDidTapButton) {
self.onDidTapButton(sender);
}
}

UIView is never rendered if added as subview in viewDidLoad

I'm trying to add a subview to a view controller programatically, but it seems like if I add the subview in the viewDidLoad of the view controller whatever I have added as subview in the view itself is not rendered in the view controller.
But if I move the [self.view addSubview:myUIView] in the init method of the view controller, everything is rendered.
Also, if I call the same methods in the viewDidAppear I get all the elements rendered, but it's right after the view controller was displayed, and I can see when the elements are rendered in the view controller.
This would be my view controller:
//interface
#class RLJSignInView;
#protocol RLJSignInViewControllerDelegate;
#interface RLJSignInViewController : UIViewController <UITextFieldDelegate>
#property (nonatomic, readwrite) RLJSignInView *signInView;
#property (nonatomic, assign) id<RLJSignInViewControllerDelegate> delegate;
#end
//implementation
#import "RLJSignInViewController.h"
#import "RLJSignInView.h"
#import "RLJSignInViewControllerDelegate.h"
#import "UIColor+RLJUIColorAdditions.h"
#implementation RLJSignInViewController
- (id)init
{
self = [super init];
if (self) {
_signInView = [[RLJSignInView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:self.signInView];
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"%#", self.signInView);
[self.view setBackgroundColor:[UIColor rgbWithRed:250 green:250 blue:250]];
}
#end
And this would be my view:
//interface
#interface RLJSignInView : UIView
#property (strong, nonatomic, readwrite) UITextField *username;
#property (strong, nonatomic, readwrite) UITextField *password;
#property (strong, nonatomic, readwrite) UIButton *signIn;
#property (strong, nonatomic, readwrite) UIButton *signUp;
#end
//implementation
#import "RLJSignInView.h"
#import "UITextField+RLJUITextFieldAdditions.h"
#import "UIButton+RLJUIButtonAdditions.h"
#implementation RLJSignInView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
CGRect usernameInputFrame = CGRectMake(10.0, 40.0, self.bounds.size.width - 20, 40);
CGRect passwordInputFrame = CGRectMake(10.0, 79.0, self.bounds.size.width - 20, 40);
CGRect signInButtonFrame = CGRectMake(10.0, 160, self.bounds.size.width - 20, 40);
CGRect signUpButtonFrame = CGRectMake(10.0, 220, self.bounds.size.width - 20, 40);
UIView *usernameInputLeftView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 10.0, 40)];
UIView *passwordInputLeftView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 10.0, 40)];
self.username = [[UITextField alloc] initWithFrame:usernameInputFrame
textAlignment:NSTextAlignmentLeft
textColor:[UIColor blackColor]
clearButton:UITextFieldViewModeWhileEditing
leftView:usernameInputLeftView
placeholder:#"Username"
backgroundColor:[UIColor whiteColor]
strokeWidth:2.0
strokeColor:[UIColor lightGrayColor]
keyboardType:UIKeyboardTypeEmailAddress
byRoundingCorners:UIRectCornerTopLeft | UIRectCornerTopRight
cornerRadii:CGSizeMake(4.0, 4.0)
secure:NO];
self.password = [[UITextField alloc] initWithFrame:passwordInputFrame
textAlignment:NSTextAlignmentLeft
textColor:[UIColor blackColor]
clearButton:UITextFieldViewModeWhileEditing
leftView:passwordInputLeftView
placeholder:#"Password"
backgroundColor:[UIColor whiteColor]
strokeWidth:2.0
strokeColor:[UIColor lightGrayColor]
keyboardType:UIKeyboardTypeDefault
byRoundingCorners:UIRectCornerBottomLeft | UIRectCornerBottomRight
cornerRadii:CGSizeMake(4.0, 4.0)
secure:YES];
self.signIn = [[UIButton alloc] initWithFrame:signInButtonFrame
title:#"Sign In"
colorNormal:[UIColor whiteColor]
colorHighlighted:[UIColor whiteColor]
colorDisabled:[UIColor whiteColor]
backgroundNormal:[UIColor colorWithRed:82 / 255.0 green:156 / 255.0 blue:201 / 255.0 alpha:1.0]
cornerRadius:4.0];
self.signUp = [[UIButton alloc] initWithFrame:signUpButtonFrame
title:#"Sign Up"
colorNormal:[UIColor blackColor]
colorHighlighted:[UIColor blackColor]
colorDisabled:[UIColor blackColor]
backgroundNormal:[UIColor whiteColor]
cornerRadius:4.0];
[self addSubview:self.username];
[self addSubview:self.password];
[self addSubview:self.signIn];
[self addSubview:self.signUp];
}
return self;
}
#end
I'm not sure what I could do about it, but I know for sure that I would not like the view to be added to the subview on initialisation.
Or maybe I'm doing the rendering of the subviews of the view wrong. I would appreciate some input on this matter if anyone encountered the same thing or if noticed that I messed up something.
Don't call self.view from inside your init method. This will trigger viewDidLoad being called before your init method even returns, which is incorrect behavior.
Instead, follow this pattern:
create your objects (properties / instance variables) in init, or by using lazy instantiation
add the subviews in viewDidLoad
set the frames in viewWillLayoutSubviews
This pattern will avoid bugs like the one you posted here, and set you up for success handling rotation and resizing events in the future.
The key might be you are using self.view.bounds too early. It might work when you provide an initial size frame (like IB would do).
init is too early. The view controller is initializing, the view is not loaded yet, which means there's no view and the bounds will return CGRectZero.
viewDidLoad is also too early to rely on the view's bounds. The view is just loaded, it does not have a superview yet and might be resized later.
viewWillAppear is the first moment you can rely on the view's bounds. Since it will be resized according to the autoresizingmasks or autolayout constraints.
You can add the subview in viewDidLoad but you you should provide an initial frame (and you set the correct autoresizingmasks or constraints) it should all work fine.

Resources