I've created a class that inherits from UIButton. If I add rounded-rect buttons to my XIB and set their class property to UIButton, they appear as rounded-rect buttons (but then none of my custom code is ever called, so the buttons behave like ordinary buttons which isn't what I want). If I set these buttons' class property in IB to my custom class UIButtonCustom, they still appear to be rounded rect buttons in the IB designer, but when I run the app the buttons are of the custom type (meaning they appear mostly blank with the button text, since I'm not setting any background image for the button).
Is there any way to get these buttons to look like rounded rect buttons when the app actually runs?
The rounded button that you see on xib is sub class of UIButton i.e. UIRoundedRectButton.
And I don't think you can subclass it as it's not documented (a private API).
As others here have noted, you cannot subclass a UIRoundedRectButton. But you can get your buttons to look like rounded rect buttons by setting some CALayer properties in your init method.
#import "MyCustomButton.h"
#import <QuartzCore/QuartzCore.h>
#implementation MyCustomButton
- (id)initWithFrame:(CGRect)frame
//button created in code
{
self = [super initWithFrame:frame];
if (self) {
[self initialise];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder;
//button created in Interface Builder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self initialise];
}
return self;
}
- (void)initialise;
{
self.layer.cornerRadius = 10.0f;
self.layer.borderWidth = 1.0f;
self.layer.borderColor = [[UIColor lightGrayColor] CGColor];
}
#end
If you have autolayout disabled you can continue to use 'Rounded Rect' button style in IB to give you the correct appearance in layout, although the style will be ignored when your custom button loads. If autolayout is enabled, you will have to change your IB style to 'custom', or you will find the button frame does not behave the way you expect.
Related
I am new to iOS development and I recently got stucked with a problem where I am not able to set text on both sides of a button. Also I wish to add multi-line text in a button (as shown below). I have gone through lot of answers but none of them satisfied my requirement.
Any suggestion or help would greatly be appreciated, thanks!
You should definitely go with a custom UIButton class. The reason for this is that UIButton has other properties that you will want, like being recognized by iOS as a button, conform to Accessibility as a button, show up in storyboard as a button class, let your co-workers see that it actually is a button, have same/similar interface as a button, and so on.
Creating a custom button isn't that hard. Basically, just sub-class UIButton and implement awakeFromNib to set up the internals and layoutSubviews to position and size everything.
Here is an outline of how to do it...
1. Create a UIButton sub-class (.h)
Add the custom interface to the header file. It could look something like this.
#interface MyButton : UIButton
- (void)setServiceText:(NSString *)serviceText;
- (void)setPriceText:(NSString *)priceText;
- (void)setTimeText:(NSString *)timeText;
#end
2. Add controls to hold the internals of your button (.m)
The three labels and a view to use as the red sidebar. From here on you add to the code file.
#interface MyButton ()
#property (strong, nonatomic) UILabel *serviceLabel;
#property (strong, nonatomic) UILabel *priceLabel;
#property (strong, nonatomic) UILabel *timeLabel;
#property (strong, nonatomic) UIView *redSideBarView;
#end
3. Add code corresponding to the interface (.m)
Since UILabel redraws itself when we set the text property, we do not need to do more to make it appear on the button.
- (void)setServiceText:(NSString *)serviceText {
_serviceLabel.text = serviceText;
}
- (void)setPriceText:(NSString *)priceText {
_priceLabel.text = priceText;
}
- (void)setTimeText:(NSString *)timeText {
_timeLabel.text = timeText;
}
4. Implement awakeFromNib (.m)
This method will be called when Storyboard instantiate your button, so here is a good place to create your labels and do other stuff that only needs to be done once.
- (void)awakeFromNib {
[super awakeFromNib];
_sideBarView = [[UIView alloc] initWithFrame:CGRectZero];
_sideBarView.backgroundColor = [UIColor redColor];
[self addSubview:_sideBarView];
_serviceLabel = [[UILabel alloc] initWithFrame:CGRectZero];
_serviceLabel.font = [UIFont systemFontOfSize:18.0];
[self addSubview:_serviceLabel];
_priceLabel = [[UILabel alloc] initWithFrame:CGRectZero];
_priceLabel.textColor = [UIColor greenColor];
_priceLabel.font = [UIFont systemFontOfSize:16.0];
_priceLabel.textAlignment = NSTextAlignmentRight;
[self addSubview:_priceLabel];
_timeLabel = [[UILabel alloc] initWithFrame:CGRectZero];
_timeLabel.font = [UIFont systemFontOfSize:14.0];
[self addSubview:_timeLabel];
self.clipsToBounds = YES;
}
5. Add code to layout your button (.m)
This is the final piece of the custom button code. Note that layoutSubviews will usually be called several times during the controls lifetime, so do not add sub-views here. Use it to position and size the internals of your button. self.bounds.size represents the current size of your button, so this is a good reference for all other elements.
- (void)layoutSubviews {
// Layout sub-elements
CGFloat buttonWidth = self.bounds.size.width;
CGFloat buttonHeight = self.bounds.size.height;
_serviceLabel.frame = CGRectMake(30.0, 2.0, buttonWidth * 0.7, buttonHeight * 0.5);
_priceLabel.frame = CGRectMake(buttonWidth - 40, 5.0, 30.0, buttonHeight * 0.4);
_timeLabel.frame = CGRectMake(30.0, buttonHeight * 0.5, buttonWidth * 0.7, buttonHeight * 0.4);
_sideBarView.frame = CGRectMake(0.0, 0.0, 10.0, buttonHeight);
self.layer.cornerRadius = 10.0;
}
6. Use it!
To use it you create a regular button in Storyboard, then in the Identity Inspector, select your new button class. As usual, tie the button to a variable in your view controller class. The variable should be of your button class, of course. That's it for the design!
#property (weak, nonatomic) IBOutlet MyButton *button;
Now don't forget to set the properties.
self.button.backgroundColor = [UIColor lightGrayColor];
[self.button setServiceText:#"FULL SERVICE HAIRCUT"];
[self.button setPriceText:#"$30"];
[self.button setTimeText:#"30 minutes"];
A couple of things I didn't address here was the gradient and the drop shadow. The gradient is probably best done with a CAGradientLayer added to the button's view's layer. The drop shadow needs a bit more coding, since you are clipping the button to have rounded corners. Probably you need to add one more UIView in between to contain everything that you then clip, and then add shadow to the button view.
I wouldn't go with a UIButton but with a UIView with Labels from Xib. Tutorial on that can be found here: link.
Then you may add a target to your UIView, so that when tapping it will call some method (as would UIButton do):
yourView.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(buttonTapped))
yourView.addGestureRecognizer(tap)
And method:
#objc func buttonTapped() {
// do what you need on tap here
}
Hope it helps! Please comment if you have questions.
PS. I'm not sure but from what I see you are probably building a UITableView. is it? Can you please show a design of the full screen? If you have many "buttons" like that then it's not a single "button" but a Table View.
To display multiple lines use below code
button.titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
button.titleLabel.numberOfLines = 2; // or 0(Zero)
And you need to add UIView and add tap gesture to that view and design that view like above image.
For Gestures
//Create tap gustures for view
UITapGestureRecognizer *viewTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(onClickView:)];
[self.yourViewName addGestureRecognizer: viewTap];
- (void) onClickView:(UITapGestureRecognizer *)recognizer {
//Write your code
}
This is one approach.
And second one is...
For this use UITableViewCell and design that cell.
This is second approach.
I have a UIViewController that has its UI elements setup in storyboard, and things are showing up fine. Now I created a new UIView in separate xib, .h and .m files, I'll call it "overlay"; then I present that overlay which should cover everything below, so that only the overlay can be seen:
// in controller.m
OverlayView *overlayView = [[OverlayView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
overlayView.layer.zPosition = 1000;
[self.view addSubview:overlayView];
The overlay appears in a weird position (the top of it is some 50px away from the bottom of the navigation bar). What's more, apart from the background of the overlay, no elements in the overlay can be seen. It's just a blank red canvas. I have double checked that the elemenets' alpha values are 1, they are set to be not hidden, and they are also set explicitly in the initWithFrame of the overlay:
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor redColor]; // red canvas shows up
self.title.textColor = [UIColor blackColor]; // title can't be seen; why not?
self.body.textColor = [UIColor blackColor]; // body can't be seen; why not?
}
return self;
}
The reason I don't directly put overlay in the controller's xib and then simply change it's hidden property is that this overlay is to be used by multiple controllers, and I'd like to re-use it, thus putting it as a separate view.
What am I doing wrong?
The parent view (view controller's view) is responsable to set the child view's frame. If you use Auto Layout you need to use setTranslateAutoresizingMaskIntoConstraints to NO and add the desired constraints in code. Keep in mind that the child's frame will be set after viewDidLayoutSubviews is called.
If you don't use Auto Layout just set the frame using setFrame method in viewWillAppear.
It turns out I had to load the nib from within the view's own initWithFrame.
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
id mainView;
if (self) {
NSArray *subviewArray = [[NSBundle mainBundle] loadNibNamed:#"MyOverlayView" owner:self options:nil];
mainView = [subviewArray objectAtIndex:0];
}
return mainView;
}
- (void)awakeFromNib {
[super awakeFromNib];
}
The code above fixed it.
In my app I need to create a button with a 1px colored border with a corner radius. Width may be different.
Naturally I will create a UIButton setting its layer.cornerRadius and layer.borderWidth.
Is it optimal?
Is there a betterway to achieve this?
Another developer told me that doing this way is expensive.
What do you think?
Thanks.
Subclass UIButton. May be named ViewWithThinBorder .
In its implementation in awakeFromNib method add border corner and width properties.
Now you can go to interface builder select button and set its class to ViewWithThinBorder from identity inspector.
This is not only optimized its more reusable anywhere around the interface builder. Also you can change buttons around all view hierarchy from central location.
#import "ViewWithThinBorder.h"
#implementation ViewWithThinBorder
-(void)awakeFromNib
{
self.layer.borderColor = [UIColor groupTableViewBackgroundColor].CGColor;
self.layer.borderWidth = 1.0f;
self.layer.cornerRadius = 1.0f;
}
#end
I have a very simple UIView, that is only drawing a triangle. It implements a UIView drawRect method that is drawing a figure. It is working fine on iOS7, but when I run the same code on iOS8 it is not even called. Any ideas what has changed on iOS8? My code in nutshell:
#interface MyView : UIView
#end
#implementation MyView
- (instancetype)init {
self = [super init];
if (self) {
self.backgroundColor = [UIColor clearColor];
}
return self;
}
- (void)drawRect:(CGRect)rect {
// draw things
}
#end
myView = [MyView new];
[self addSubview:myView];
Update
My view hierarchy: UIViewController View->Custom UILabel->MyView
It is an error message bubble, and my view is a beak pointing to something.
I have used a new UI debugging tool, but it is not visible there. Init method is called, drawRect is not. Something must have changed in iOS8, because iOS7 is calling drawRect.
Update 2
Of course I'm using constraints (and Masonry pod), and this is why I did not specify the frame.
[myView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(make.superview.mas_bottom);
make.centerX.equalTo(make.superview);
make.width.equalTo(#10.0f);
make.height.equalTo(#5.0f);
}];
I have also tried adding [myView setNeedsDisplay] in various places, but it didn't help.
Problem finally solved. I'm not sure what exactly caused the issue (Masonry [constraints framework] or iOS8 changes to UILabel), but the solution was to change the view hierarchy. I created another UIView that contains both UILabel and my UIView (drawn beak) instead of adding the UIView to UILabel as subview. Now drawRect method is called both on iOS7 and iOS8.
Previous hierarchy:
UIViewController View->Custom UILabel->MyView
New hierarchy:
UIViewController View->Container UIView->Custom UILabel & MyView
To sum up, if your drawRect method is not called on iOS8, and you are adding some UIView to UILabel as subview, try to use some container which will contain both UIView and UILabel.
make sure that myView's superview property clipToBounds = NO, and that myView rect is on screen
There is an important detail missing in your approach. You need to specify a frame that determines the origin and size of your view. This can be done with the initWithRect method of UIView, or you can set the frame after allocation/initialization. Since you have a custom init method already I would do the following:
myView = [MyView new];
myView.frame = CGRectMake(0.0,0.0,100.0,100.0);
[self addSubview:myView];
This will draw your custom view with an origin point of (0.0,0.0) and a width and height of 100.0.
Furthermore, adding a call to setNeedsDisplay should force the drawRect method to be called if you are still having trouble:
[myView setNeedsDisplay];
I don't want the drop shadow when the UIPopoverController's view appears. Is there a way to remove this drop shadow look?
Not straight forward, but since iOS 5, you can make your own custom popover background using UIPopoverBackgroundView.
See the answer for this question: Using UIPopoverBackgroundView class. It's pointing to a good tuto.
Then, in the initWithFrame of your UIPopoverBackgroundView implementation, you can use a clearColor for the drop shadow. Using offset and radius did not work for me.
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.layer.shadowColor = [[UIColor clearColor] CGColor];
}
return self;
}
The shadow is an attribute of the popover view's layer. If you could get access to the layer, you could set it's shadow radius to 0.0 and shadow offset to {0.0, 0.0}. However, it looks like the view must be a private ivar of the popover controller, so there's not an easy way to get to it. Moreover, if you're looking to distribute this app through the app store, using a private ivar and changing the look of standard UI elements both are likely to get your app rejected.
You just have to use your custom UIPopoverBackgroundView and there implement this function:
+ (BOOL)wantsDefaultContentAppearance {
return NO;
}