I've got a Custom Button class that I'm setting up the text for in my storyboard file. The text is a simple "Update Email Address" and does not change throughout the life of the app.
However, I have an image I want to add to the button, and when I do that, I set the image in the following way, in a method inside the common button class:
[self setTintColor:[UIColor whiteColor]];
UIImage *icon = [UIImage imageNamed:iconName];
CGFloat iconWidth = icon.size.width;
[self setImage:icon forState:UIControlStateNormal];
[self setWidth:self.width+iconWidth*2];
[self setOriginX:self.x-iconWidth];
[self setTitleEdgeInsets:UIEdgeInsetsMake(0, -iconWidth*2, 0, 0)];
CGFloat leftMargin = 10;
CGFloat moveLeft = self.titleLabel.frame.size.width + self.titleLabel.x + leftMargin;
[self setImageEdgeInsets:UIEdgeInsetsMake(0, moveLeft , 0, 0)];
However, by setting it this way, it appears the titleLabel's width is 0 so the image isn't being set in the correct location on the button.
My thoughts on why this are happening are potentially because I never set the text programmatically, only in the storyboard, although that really doesn't make sense, it's all I got.
Does anyone see anything wrong here? If you need any other code let me know and I can post up the relevant info.
When do you do this? Maybe you are trying to do this after the resource was loaded but before the frames of all the elements are set.
Related
I have a UIButton, and I would like to access the UIImageView of its background image so that I can make the image circular. I know that I can affect the image itself but I would prefer to do this more elegantly. I also know that I can use the button.currentBackgroundImage property to get the UIImage in the background, but I want the view itself. Once I have the view I intend to use this code:
buttonImageView.layer.cornerRadius = buttonImageView.frame.size.width / 2;
buttonImageView.clipsToBounds = YES;
How can I access the buttonImageView?
EDIT:
Due to #vhristoskov's suggestions, I tried cropping the button itself with this code:
self.button.layer.cornerRadius = self.button.frame.size.width/2;
self.button.clipsToBounds = YES;
And it made this shape:
After debugging I found that frame.size.width was 46, when it should have been 100. So I tried this code instead:
self.button.layer.cornerRadius = self.button.currentBackgroundImage.size.width/2;
self.button.clipsToBounds = YES;
And that produced this shape:
And this time, the cornerRadius was being set to 65. So it actually seems like my problem is that I don't have the correct width at the moment. Which property should I access to get the correct number?
Well as I guessed and as you've already found - the problem is in the button size. To be sure that your button's size at runtime is what you expected to be - review your constraints. In the example below the button has vertical and horizontal central alignment and fixed width and height.
Constraints:
To have perfectly circular button you should have button.width == button.height. If this condition is met your code should work like a charm:
self.button.layer.cornerRadius = CGRectGetWidth(self.button.frame) / 2;
self.button.clipsToBounds = YES;
Assuming that you called something like:
[self.myButton setBackgroundImage:[UIImage imageNamed:#"image"] forState:UIControlStateNormal];
in viewDidLoad or earlier, you can then call the following in viewDidAppear:
if (self.myButton.subviews.count > 0 && [self.myButton.subviews[0] isKindOfClass:[UIImageView class]]) {
UIImageView *imageView = self.myButton.subviews[0];
imageView.layer.cornerRadius = imageView.frame.size.width / 2;
imageView.clipsToBounds = YES;
}
but it is not more elegant as the subview ordering is an implementation detail of UIButton and may change at any time.
After not finding an answer I have to again ask you all for help. I am creating a UIStepper programatically but it will not display. Labels, buttons, and switches are all displaying properly so I must be missing something related to the stepper.
Elsewhere in my code I declare and initialize UIView *v and NSMutableArray *steppers, and declare UIStepper *st. The code to create the stepper is:
st = [[UIStepper alloc] init];
st.frame = CGRectMake(xnear, ypos, 0, 0);
[st addTarget:self action:#selector(stepper1:) forControlEvents:UIControlEventValueChanged];
[st setMinimumValue:0];
[st setMaximumValue:99];
[st setWraps:NO];
[st setContinuous:NO];
[v addSubview:st];
[steppers addObject:st];
At runtime xnear = 100 and ypos = 250, so the stepper is within the display. A label immediately above, and a text field immediately below, are displaying. Other questions regarding the UIStepper state the width and height are ignored, so I used 0 for both. Is there anything obviously wrong with this code?
You are setting height and width of UIStepper as 0
st.frame = CGRectMake(xnear, ypos, 0, 0);
give some height and width
st.frame = CGRectMake(xnear, ypos,200, 100);
The answer is, quite frankly, embarrassing. A custom background color was set for the UIView and no color was set for the control. I was able to get the stepper to show up with the following line of code:
st.backgroundColor = [UIColor whiteColor];
Similarly, the UISegmentedControl *sc showed up when I added:
sc.backgroundColor = [UIColor whiteColor];
Thanks for the help and sorry to waste your time!
I'm creating a simple button and adding it as a subview to main view. It doesn't show up though.
This is the code for setting it up:
UIButton* contactButton = [[UIButton alloc] init];
contactButton.titleLabel.text = #"Contact Developer";
[contactButton sizeToFit];
[self.view addSubview:contactButton];
NSLog(#"X: %f || Y: %f || Width: %f || Height: %f", contactButton.frame.origin.x, contactButton.frame.origin.y, contactButton.frame.size.width, contactButton.frame.size.height);
As you may have noticed, I placed a bit of debug code at the end to see the position and dimensions of the button. They are: X: 0, Y: 0, Width: 30, Height: 34
This means it should be showing up in the upper left corner but it doesn't. Whats wrong?
One possible reason for this is that you used titleLabeldirectly. Consider using setTitle:forState: method instead.
To be sure, consider setting backgroundColor, as a debug step, to make sure it's appearing.
Edit As others suggested, try using buttonWithType: instead of [[UIButton alloc] init]. I suggest using UIButtonTypeSystem instead of UIButtonTypeCustom.
You should initialise the button using the following:
UIButton* contactButton = [UIButton buttonWithType:UIButtonTypeCustom];
Try constructing your button a different way:
UIButton* contactButton = [UIButton buttonWithType:UIButtonTypeCustom];
contactButton.backgroundColor = [UIColor redColor];
contactButton.titleLabel.text = #"Contact Developer";
[contactButton sizeToFit];
[self.view addSubview:contactButton];
Are you sure that the button it's not being shown?
I think you are setting the title of the button incorrectly and because of the fact that a default UIButton has clear background and white title, it´s not visible if your superview is white too.
Instead of setting:
contactButton.titleLabel.text = #"Contact Developer"
use:
[contactButton setTitle:#"Contact Developer" forState:UIControlStateNormal]
But you can first try to set a backgroundColor to the button, different that the superview, to see if it's added.
Hope it helps!
(Sorry for my english if i have a mistake)
I'm trying to build a simple UIScrollView with paging to horizontally scroll between 3 images.
The tricky part is that I would like that each image would be clickable and catch the click event.
My technique is to create 3 UIButtons that each consists UIImage. give each button a tag and set an action.
Problem: I can catch the click event - BUT it's not scrollable!
Here is my code:
- (void) viewDidAppear:(BOOL)animated {
_imageArray = [[NSArray alloc] initWithObjects:#"content_01.png", #"content_02.png", #"content_03.png", nil];
for (int i = 0; i < [_imageArray count]; i++) {
//We'll create an imageView object in every 'page' of our scrollView.
CGRect frame;
frame.origin.x = _contentScrollView.frame.size.width * i;
frame.origin.y = 0;
frame.size = _contentScrollView.frame.size;
//
//get the image to use, however you want
UIImage* image = [UIImage imageNamed:[_imageArray objectAtIndex:i]];
UIButton* button = [[UIButton alloc] initWithFrame:frame];
//set the button states you want the image to show up for
[button setImage:image forState:UIControlStateNormal];
[button setImage:image forState:UIControlStateHighlighted];
//create the touch event target, i am calling the 'productImagePressed' method
[button addTarget:self action:#selector(imagePressed:)
forControlEvents:UIControlEventTouchUpInside];
//i set the tag to the image #, i was looking though an array of them
button.tag = i;
[_contentScrollView addSubview:button];
}
//Set the content size of our scrollview according to the total width of our imageView objects.
_contentScrollView.contentSize = CGSizeMake(_contentScrollView.frame.size.width * [_imageArray count], _contentScrollView.frame.size.height);
_contentScrollView.backgroundColor = [ENGAppDelegate backgroundColor];
_contentScrollView.delegate = self;
}
Well, since UIButton is an UIControl subclass, it "eats up" the touches of your scroll view:
[UIScrollView touchesShouldCancelInContentView:] The default returned value is YES if view is not a UIControl object; otherwise, it returns NO.
(from https://developer.apple.com/library/ios/documentation/uikit/reference/UIScrollView_Class/Reference/UIScrollView.html#//apple_ref/occ/instm/UIScrollView/touchesShouldCancelInContentView:)
You could influence this by subclassing UIScrollView and overwriting touchesShouldCancelInContentView: (and/or touchesShouldBegin:withEvent:inContentView:). However, for your use case I'd not use buttons in the first place. Why not just add a tap gesture recognizer to the scrollview and use the touch point to determine which image has been tapped? That's much easier and should work without any issues.
This completely solved this issue for me:
scrollview.panGestureRecognizer.delaysTouchesBegan = YES;
Credit goes to https://stackoverflow.com/users/904365/kjuly for providing the right answer in this topic: ScrollView/TableView with UIControl/UIButton subviews not scrollable under iOS 8
I'm allocating a UIButtonTypeCustom to a UIView with a background image that is smaller than the button's frame.
Reason why the image is smaller is because I'm trying to add more of a "target area" for the UIButton. However, the image is being scaled to the full size of the frame, rather than just being the image's size.
I have tried setting the UIButton and UIButton's imageView's contentMode property to UIViewContentModeScaleAspectFit, but no luck, the image still gets stretched out.
Is there a way to do what I'm trying to do programmatically?
Thanks in advance!
A lot of people make the same mistake you do in regards to button images and then jump through hoops trying to make the button behave as they expect it to. Let's clear this up once and for all:
A UIButton has two types of images it can display -- a foreground image and a background image. The background image for a button is expected to replace the button's background texture. As such, it makes sense that it stretches to fill the entire background. However, the button's foreground image is expected to be an icon that may or may not display alongside text; it will not stretch. It may shrink if the frame is smaller than the image, but it will not stretch. You can even set the alignment of the foreground image using the Control alignment properties in Interface Builder.
A button's foreground and background image can be set in code like this:
// stretchy
[self setBackgroundImage:backgroundImage forState:UIControlStateNormal];
// not stretchy
[self setImage:forgroundImage forState:UIControlStateNormal];
You don't have access to the background imageView, but there is fully working workaround:
EDIT: There is an even better workaround then what I posted originally.
You can create a UIImage from any color, and call -setBackgroundImage:forState.
See bradley's answer, here:
https://stackoverflow.com/a/20303841/1147286
Original answer:
Instead of calling -setBackgroundImage:forState:, create a new UIImageView and add it as a subview of the button.
UIImageView *bgImageView = [[UIImageView alloc] initWithImage:img];
bgImageView.contentMode = UIViewContentModeScaleAspectFit;
[bgImageView setFrame:CGRectMake(0, 0, videoButton.frame.size.width, videoButton.frame.size.height)];
bgImageView.tag = 99;
[yourButton addSubview:bgImageView];
[yourButton bringSubviewToFront:yourButton.imageView];
Create the imageview
Set the content mode and frame
I also set a recognizable tag, so that when the screen rotates I can easily find my custom imageView in the button's subviews and reset its frame
Add it as a subview to the button
Bring the frontal imageView of the button to the front so our custom imageView doesn't overlap it
When the button needs to rotate just find the imageView by its tag and reset its frame:
UIImageView *bgImageView = (UIImageView *)[button viewWithTag:99];
[bgImageView setFrame:CGRectMake(0, 0, newWidth, newHeight)];
The cleanest and easiest way it probably to use the title insets of the button.
You set your image as the button image, and then you change the left title inset to match minus the width of your image:
myButton.titleEdgeInsets = UIEdgeInsetsMake(0, -myImage.width, 0, 0)
This will move the text back where it was before the image was added to its left. You can also use this value to add some padding to you button.
Stumbled on this problem too.
Adding image programmatically, as memmons thoroughly explained, did not help:(
I had a button 100x40 and image 100x100, it would appear squeezed, not fitted, as one would infer from "Aspect Fit" option. Actually, non of those view options had an effect.
I just had to rescale it so it would fit on a button, then use setImage:
UIImage *img=[UIImage imageNamed:#"myimage.png"];
CGImageRef imgRef = [img CGImage];
CGFloat imgW = CGImageGetWidth(imgRef);
CGFloat imgH = CGImageGetHeight(imgRef);
CGFloat btnW = myBttn.frame.size.width;
CGFloat btnH = myBttn.frame.size.height;
//get lesser button dimension
CGFloat minBtn=btnW;
if (btnW>btnH) {
minBtn=btnH;
}
//calculate scale using greater image dimension
CGFloat scl=imgH/minBtn;
if (imgW>imgH) {
scl=imgW/minBtn;
}
//scale image
UIImage *scaledImage = [UIImage imageWithCGImage:[img CGImage] scale:(img.scale * scl) orientation:(img.imageOrientation)];
//clean up
UIGraphicsEndImageContext();
//set it on a button
[myBttn setImage:scaledImage forState:UIControlStateNormal];
It is simple as:
ImageBtn.imageView?.contentMode = .scaleAspectFit
ImageBtn.setImage(chosenImage, for: .normal)
Another consideration is the BaseLine constraint. If your buttons have this constraint set (depicted as a horizontal or vertical line through multiple controls on your layout), it will cause your images to stretch without stretching the underlying button control. If your button is otherwise properly constrained (leading/trailing and top/bottom spaces, and etc...) removing the BaseLine constraint should have no impact on the layout, while allowing the foreground image to scale properly to the underlying button shape.
Answerbot answers the question with what is proper and correct to do. Don't fight the OS and use things as intended is always good advice. However, sometimes you need to break the rules.
I was able to mask the enlarged background image (not prevent it) by overlaying it with a black CAlayer then overlaying again with a properly resized image CAlayer. This was all done by creating a subclass of UIButton and overwriting the setHighlighted method.
NEED CODE?
- (void)setHighlighted:(BOOL)highlighted
{
super.highlighted = highlighted;
//
//Whenever an image needs to be highlighted, create a dimmed new image that is correctly sized. Below it is a englarged stretched image.
//
if (highlighted != _previousHighlightedSate)
{
_previousHighlightedSate = highlighted;
if (highlighted)
{
//Create a black layer so image can dim
_blackLayer = [CALayer layer];
_blackLayer.bounds = self.bounds;
CGRect rect = _blackLayer.bounds;
rect.size.width = rect.size.width*2;
rect.size.height = rect.size.height*2;
_blackLayer.bounds = rect;
_blackLayer.backgroundColor = [[UIColor blackColor] CGColor];
//create image layer
_nonStretchImageLayer = [CALayer layer];
_nonStretchImageLayer.backgroundColor = [UIColor blackColor].CGColor;
_nonStretchImageLayer.bounds = CGRectMake(0 , 0, self.bounds.size.width, self.bounds.size.height);
_nonStretchImageLayer.frame = CGRectMake(0 , 0, self.bounds.size.width, self.bounds.size.height);
_nonStretchImageLayer.contentsGravity = kCAGravityResizeAspect;//default is to resize
_nonStretchImageLayer.contents = (id)self.imageView.image.CGImage;
_nonStretchImageLayer.opacity = 0.5;
//add layers to image view
[self.imageView.layer addSublayer:_blackLayer];
[self.imageView.layer addSublayer:_nonStretchImageLayer];
}
else
{
//remove from image view
[_blackLayer removeFromSuperlayer];
[_nonStretchImageLayer removeFromSuperlayer];
//nil them out.
_blackLayer = nil;
_nonStretchImageLayer = nil;
}
}
Inspiration for this work around came from here
I guess the easiest solution is to use UIImage's resizableImageWithCapInsets method. Use UIEdgeInsetsMake to configure the free spaces.
might help someone
button.subviews.first?.contentMode = .scaleAspectFit
Swift version of Zoltán Matók answer
Just a copy of my code using SnapKit to do auto layout and syntatic sugar Then library for initilizations, it should work similar for normal Apples way of programatic layout.
let backButton = UIButton(type: .custom).then { (button) in
let image = UIImage(named: "backButtonIcon")?.withRenderingMode(.alwaysOriginal)
let imageView = UIImageView(image: image)
button.addSubview(imageView)
imageView.snp.makeConstraints { (make) in
make.center.equalTo(button.snp.center)
}
button.bringSubviewToFront(imageView)
button.contentMode = .scaleAspectFit
button.backgroundColor = .clear
button.isUserInteractionEnabled = true
button.isEnabled = true
button.imageView?.contentMode = .scaleAspectFit
button.imageView?.snp.makeConstraints({ (make) in
make.height.width.equalTo(24)
})
button.snp.makeConstraints { (make) in
make.height.width.equalTo(24)
}
}
You can use uibutton.imageView.contentMode for no stretching:
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(posX, posY, widthButton, heightButton);
[button setTitle:#"" forState:UIControlStateNormal];
[button setImage:[UIImage imageNamed:#"imageNamed"] forState:UIControlStateNormal];
[button setImage:[UIImage imageNamed:#"imageNamedHighlighted"] forState:UIControlStateHighlighted];
button.imageView.contentMode = UIViewContentModeScaleAspectFit;
[button addTarget:self action:#selector(functionMenu:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview: button];
What you need to do is add your image as a UIImageView.
Than add a button with transperent background (UIColor ClearColor) on top of it with your desired width and height.