iOS Stretchable image on UIButton showing wonky corners - iphone

OK folks,
I've searched all over and cannot seem to find a solution to my issue.
I am trying to use a sizeable image for a button background to get a custom look for a UIButton and to reduce the overall size of the app and because it seems like the right thing to do.
However, when I try to set the image on the button the button gets all weird looking and the corners do not match the standard corner radius of a regular UIButton.
I have tried creating several different sizes of images but nothing I do seems to work.
I know the caps are supposed to be even and I have that plus the 1 pixel middle to stretch.
My UIButton is 44 high. If I create an image that is 44 pixels high and 21 pixels wide and has the same rounded corner radius as the default button (it likes an aspirin caplet) and set my background image like this:
UIImage *btnImage = [UIImage imageNamed:#"buttontest1.png"];
UIImage *newImg = [btnImage stretchableImageWithLeftCapWidth:10 topCapHeight:0];
the corners just don't match and look weirdly widely stretched AND the button grows in height!
I know the stretchableImagewithLeftCapWidth is deprecated but the usage of resizableCapWithInsets makes even less sense to me and when I try to do that the button image just seems to repeat over and over.
Can anyone figure out what I'm doing wrong? Is there anyplace that explains this crap simply? I just cannot seem to get it.
Thanks!
-TJ
EDIT - adding images after using the resizableImageCapWithInserts to show results. They can't be typical as I see examples all over that supposedly work but the examples never work for me. I just figured out how to add images here so maybe this helps some.
Here is the PNG file 21px wide by 39px tall
This is the result of using the following code to set the background image:
[self.button1 setBackgroundImage:[[UIImage imageNamed:#"buttontest4.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 10, 0, 10)] forState:UIControlStateNormal];
As I understand it this should copy the left 10 pixels and right 10 pixels of my 21 pixel wide image as is and stretch the middle 1 pixel across, which it appears to do but it is also making my button get larger vertically and I'm getting this weird repeat. It should be the same size as the BTN next to it.
Here is my Xcode layout:
No matter what image I use I see similar results.
I'm obviously not groking something here.
I appreciate the answers so far. It's becoming slightly less foggy.
TJ
EDIT2: showing samples using the cap insets method with image 21px by 44px.
All 4 buttons are 44px high when designed in storyboard.
As you can see the orange buttons are both larger than the white buttons for scale comparison.
The top button is button1, bottom is button2.
I found a way to get it closer by using the optional resizingMode parameter of UIImageResizingModeStretch.
However, notice the buttons are larger than what they should be.
Here is the code for setting the button images.
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self.button1 setBackgroundImage:[[UIImage imageNamed:#"buttontest1a.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(10, 10, 10, 10)] forState:UIControlStateNormal];
[self.button2 setBackgroundImage:[[UIImage imageNamed:#"buttontest1a.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 10, 0, 10) resizingMode:UIImageResizingModeStretch] forState:UIControlStateNormal];
}
Doing the top image with (10,10,10,10) gets me an image that does not repeat the top part of the image like previously. I know the top image is not using the optional resize parameter as this is a test to see what each gets me.
Now, to complicate things even more. If I set my top button size to 43, that is one pixel smaller than my image and make a call with the optional resize parameter I get nearly perfect results except my button is not the right size.
What the heck is going on here?
I appreciate everyone who is trying to pound some knowledge through my thick skull.
TJ

UIEdgeInsets is a little difficult to wrap your head around, but it is the more modern usage. I will explain the way it works. Basically, using four offsets, you are dividing your image into 9 slices. If you want to see an image of what this potentially looks like, have a look at the "Scaleable area" section of this page (Note, it is for Android, but the concept is the same. Android was just doing it first). You will notice four lines going through the image. These will correspond to your four insets. So your nine sections, from left-to-right and top-to-bottom will be:
X: 0 -> Left Inset, Y: 0 -> Top Inset
X: Left Inset -> (Width - Right Inset), Y: 0 -> Top Inset
X: (Width - Right Inset) -> Width, Y: 0 -> Top Inset
X: 0 -> Left Inset, Y: Top Inset -> (Height - Bottom Inset)
X: Left Inset -> (Width - Right Inset), Y: Top Inset -> (Height - Bottom Inset)
X: (Width - Right Inset) -> Width, Y: Top Inset -> (Height - Bottom Inset)
X: 0 -> Left Inset, Y: (Height - Bottom Inset) -> Height
Left Inset -> (Width - Right Inset), Y: (Height - Bottom Inset) -> Height
X: (Width - Right Inset) -> Width, Y: (Height - Bottom Inset) -> Height
Sections 1, 3, 7, and 9 are set and will not stretch.
Sections 2 and 8 will stretch horizontally only
Sections 4 and 6 will stretch vertically only
Section 5 will stretch in both directions.
New in iOS 6, you can select a mode. You can either stretch the stretchable tiles, or repeat them to get the effect that you want (colors, etc should stretch while textures should repeat).

Try UIEdgeInsets
[button setBackgroundImage:[[UIImage imageNamed:#"buttontest1.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(10, 10, 10, 10)] forState:UIControlStateNormal];

The 2016 way is to do what we are told in WWDC2016 video 213 around this point: https://developer.apple.com/videos/play/wwdc2016-213/?time=1084
We can stretch assets. Sorry for just a link this time; I'll provide more details later. The instructions in the video is a minute or two at most. In short, we use the Asset Slicer tool of Xcode to define what parts of an image should not get stretched when an image is used as background of a UIButton. The UIButton can expand to any size without distorting roundd edges etc.

Related

Progress view not rounded on iOS 7

I have set some images to custom a UIProgressView and give a rounded style to it, but the problem is when the progress value is changed the control is not anymore rounded on iOS 7.
Here is the code that i use:
self.progressBar.progressImage = [UIImage imageNamed:#"prog_progress"];
self.progressBar.trackImage = [UIImage imageNamed:#"prog_track"];
CGAffineTransform transform = CGAffineTransformMakeScale(1.0f, 5.0f);
self.progressBar.transform = transform;
Progress View Changes:
Please add category to UIProgressView as below
- (CGSize)sizeThatFits:(CGSize)size
{
return CGSizeMake(self.frame.size.width, self.frame.size.height);
}
#end
The progressbar has roundet corners. Have a closer look. Due your skaling you are compressing the image along with its corners. Even if setting insets this aproach will never work. Smal numbers will at one point drop below the inset.
There are many aproaches, but given your image aproach:
A simple blue view under an imageview. the image view has your white image with the grey bar. Give the grey area trabsparency, so the blue is visible. Now u can set the blue view to any size without loosing the round corners.

Custom UIProgressView

I'm using a UIProgressView in my application, which is working great right now. But I want to make it larger (height-wise) and color it a different color.
I'm looking to use PDColoredProgressView for the color, and alter the -(void)drawRect:(CGRect)rect method to change the height, however I can't decide where I would actually alter the height. Any suggestions on what to change?
So it turns out you can resize it like any other view.
[coloredProgressView setFrame:CGRectMake(0, 0, 300, 25)];
Setting the frame side didn't seem to work for me. Setting the transform to a CGAffineTransformMakeScale() can scale it up - not sure if that causes any other problems though.
To change the height of progressView try below code: ( works with Swift 5 )

progressView.transform = CGAffineTransform(scaleX: 1, y: 4) // y present the wanted height
and 1 present the current width, so if you change it to 3 then it will mean current width x 2

Resize a UIBarButtonItem when the title gets too wide

Some background: I wanted to have 3 buttons on a UIToolBar. I managed to get the middle one centered and everything by putting it into a UIToolBar itself into a UIView.
Everythings looks just like it should apart from when the title of the middle buttons gets too big.
It then gets displayed under the left or right buttons.
I can't get the UIToolBar or the UIBarButtonItems's width to be able to resize them when they're too big.
The 'UIBarButtonItem' has a really nice width property that would allow me to resize the control if it's too big.
But I can't know when it's too big!
EDIT: I did the hard way in the end. I calculate the size of the text and compare it to the maximum pixel size I saw fit on the device. Ugly but it works.
+ (CGFloat)calculateTextWidth:(NSString *)text
{
CGSize fullSize = [UIScreen mainScreen].applicationFrame.size;
UIGraphicsBeginImageContext(fullSize);
CGContextRef context = UIGraphicsGetCurrentContext();
// calculate the text size
CGContextSelectFont(context, "Helvetica", 17, kCGEncodingMacRoman);
CGContextSetTextMatrix(context, CGAffineTransformMakeScale(1.0, -1.0));
CGContextSetTextDrawingMode(context, kCGTextInvisible);
// measure the text
CGPoint initialTextPosition = CGContextGetTextPosition(context);
CGContextShowTextAtPoint(context, 0, 0, [text cStringUsingEncoding:NSASCIIStringEncoding], text.length);
CGPoint finalTextPosition = CGContextGetTextPosition(context);
return finalTextPosition.x - initialTextPosition.x;
}
Put a Flexible Space Bar Button Item between the left and right buttons and the centre button; that will allow the side buttons to take up only as much width as they need and keep the centre button centred whilst allowing it to grow into the space around it.
Here's a couple of screenshots from Interface Builder of something I was working on this morning that shows the effect: the double-headed arrows are IB's way of showing the flexible space items...
...and the same with a longer title on the centre button...
and here's the toolbar with short and long centre button as seen on my iPhone 3GS:
Here is a better way to find the text width:
+ (CGFloat)calculateTextWidth:(NSString *)text
{
UIFont *font = [UIFont fontWithName:#"Helvetica" size:17];
return [text sizeWithFont:font].width;
}

When UIButton content mode is "center," background image is pixelated

Here's how I'm setting the background image and content mode:
[btn setBackgroundImage:[UIImage imageNamed:#"dots_game_horiz_blue.png"]
forState:UIControlStateNormal];
[btn setContentMode:UIViewContentModeCenter];
Is there a reason that the background image would be pixelated? The frame is slightly larger than the image size, and I just want the image to be centered.
I guess a background image is not considered to be "content" by the UIButton, so the content mode did not apply to the background image. Instead, I set the image of the button and it worked just fine:
[btn setImage:[[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"dots_game_horiz_blue" ofType:#"png"]] forState:UIControlStateNormal];
[btn setContentMode:UIViewContentModeCenter];
In your xib / storyboard, in the Attributes inspector, you need to:
Set your UIButton type to Custom
Set your image in the property Image, not Background
Validating step 2, you can see the button frame changed according to the image set. Set the frame as you want it to be, the image will resize itself to fit in
Select Image for the property Edge in order to apply inset to the button image
Enter inset values you need. If you set 5 to Top, Bottom, Left, Right, the image will be centered inside the button, with a border of 5
You can see the changes in the xib / storyboard view, no need to launch a debug.
Tip: Inset is the same for every images of the button (State Config)
You could also implement - (CGRect)backgroundRectForBounds:(CGRect)bounds to control how the background is drawn.
The correct solution is to change the type from System to Custom. Then the background image will honor the content type instead of just centering. In my case I set it to Aspect Fill and apply a 45 degree corner radius to round it. Simple and works perfectly.
I needed to create rounded buttons for avatar photos and found the answers to this question to help but they did not get me all of the way there. I implemented the backgroundRectForBounds method and scaled the image to fit and it works well.
I have the code on GitHub.
https://github.com/brennanMKE/CircleButton
The method is also listed below. It is important to set the background image and not the image for the button which does not work with this method.
- (CGRect)backgroundRectForBounds:(CGRect)bounds {
UIImage *backgroundImage = [self backgroundImageForState:self.state];
if (backgroundImage) {
CGFloat maxWidth = CGRectGetWidth(self.frame);
CGFloat xDelta = maxWidth / backgroundImage.size.width;
CGFloat yDelta = maxWidth / backgroundImage.size.height;
CGFloat delta = xDelta > yDelta ? xDelta : yDelta;
CGFloat x = floorf((self.bounds.size.width - (backgroundImage.size.width * delta)) / 2);
CGFloat y = floorf((self.bounds.size.height - (backgroundImage.size.height * delta)) / 2);
return CGRectMake(x, y, backgroundImage.size.width * delta, backgroundImage.size.height * delta);
}
else {
return [super backgroundRectForBounds:bounds];
}
}

What's a simple/fast way to display half-star ratings in an iphone view

I have a (float) rating value as a percentage from 0..100 (where 50 = Just OK, 0 = terrible and 100=best).
What's a simple way to display this as a 5 star rating on the iphone, with the following requirements:
simple (ideally just using drawing operations based on a png of a single star or five of them, and without needing to resort to photoshop.)
reasonably fast (this is part of a cell in a table view)
includes half stars (or more fine grained)
displays something reasonable for a rating of 0 (or close to 0)
(This is display only so I don't need it to respond to touch events, though that would be nice - currently I'm just using a slider to capture the rating in the first place)
I've come up with a simple way to do this without using any PNGs at all. I've subclassed a UIView and then in the drawRect I've done this:
- (void)drawRect:(CGRect)rect {
// Drawing code
CGContextRef context = UIGraphicsGetCurrentContext();
[textColor set];
NSString* stars=#"★★★★★";
rect=self.bounds;
UIFont *font = [UIFont boldSystemFontOfSize: 16];
CGSize starSize = [stars sizeWithFont: font];
rect.size=starSize;
[#"☆☆☆☆☆" drawInRect:rect withFont: font];
CGRect clip=rect;
clip.size.width=clip.size.width*rating/100;
CGContextClipToRect(context,clip);
[stars drawInRect:rect withFont:font];
}
(those strings are 5 empty and 5 full stars, in case they are only displaying on my mac. I just entered them within IB using the 'special characters' menu)
I've added a textColor (UIColor*) and rating (int) property, so that is where those are coming from.
To add the view within IB, I just change the class type to the name of my UIView subclass. Then I can add the view to any container within IB.
I would consider having two UIImageViews layered directly on top of each other. The bottom image view would contain a PNG of five 'empty' stars - the top layer a PNG of five 'full' stars. I would resize the width of the top layer depending on the rating to expose the empty starts underneath.
You can determine a suitable width of the top layer with something like the following:
newStarLayerWidth = fullStarLayerWidth * (percentage / 100)
You would need to ensure that the image in the top layer is not resized and is aligned left by setting the views contentMode to UIViewContentModeLeft, as well as set clipsToBounds = YES, otherwise your image will not clip based on the frame size.
If this is not fast enough I would take the same approach but draw the cell directly as described here - however you may find that this is not necessary.