Resize a UIBarButtonItem when the title gets too wide - iphone

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;
}

Related

How can I resize and reposition labels on iPhone orientation change?

I have a view controller which has two UILabels and a UIButton centered and stacked at the bottom. The UILabels have 20 pixels between their outer edges and the border of the view, and there are 10 pixels between each control.
When I rotate the device to a landscape orientation, the labels stay centered, and the padding and spacing stays consistent, but the heights of the labels do not change - if the label could previously contain three lines, it is still three lines high, despite the text being only two lines in the new orientation
I've seen similar questions on Stack Overflow with instructions on how to find and change the label height, but these seem to leave the upper left hand corner of the label where it would be if the label were still a larger height. This is despite constraints which specifically state there should only be 10 pixels between the two labels. I'm assuming this is because the solutions I've found change the height of the frame, but this also forces the origin to remain the same.
I can add more code to calculate the new origin of the label, in addition to calculating the height and setting the new correct width, but for something which must be reasonably common, there has to be an easier way.
Is there any way to have labels follow constraints, and ignore their set origin? Or have labels change size and position properly to fit their text?
Here's the code I'm currently using:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
CGSize maximumLabelSize = CGSizeMake(self.view.frame.size.width - 40, CGFLOAT_MAX);
CGSize expectedLabelSize = [helpMessageText sizeWithFont:[helpMessageLabel font] constrainedToSize:maximumLabelSize lineBreakMode:[helpMessageLabel lineBreakMode]];
CGRect newFrame = helpMessageLabel.frame;
newFrame.size.height = expectedLabelSize.height;
[helpMessageLabel setText:helpMessageText];
[helpMessageLabel setFrame:newFrame];
NSLog(#"%f", newFrame.size.height);
[helpMessageLabel setBackgroundColor:[UIColor blueColor]];
expectedLabelSize = [errorMessageText sizeWithFont:[errorMessageLabel font] constrainedToSize:maximumLabelSize lineBreakMode:[errorMessageLabel lineBreakMode]];
newFrame = errorMessageLabel.frame;
newFrame.size.height = expectedLabelSize.height;
[errorMessageLabel setText:errorMessageText];
errorMessageLabel.frame = newFrame;
NSLog(#"%f", newFrame.size.height);
[errorMessageLabel setBackgroundColor:[UIColor greenColor]];
}
Here's what the app looks like with no special code:
Here's what the app looks like in landscape with the above code:
Here's closer to what I'd like it to look like:

How do you fix missing "blocks" of data when taking screenshots on an iOS device?

I have a problem that I can't seem to fix. I am trying to take a screen-shot of a UIScrollView (including off-screen content) but when the view is long the renderInContext doesn't get all the contents of the scroll view. The produced image dimensions are correct but the rendered data appears to be missing chunks of the display leaving white space where those chunks should be. The missing blocks are from the content in a UIWebView, which I believe is set to "scaleToFit". It doesn't happen everytime, it appears to only happen when the UIWebView's height if fairly large. Which makes me think is has to do with the scaling of the UIWebView.
If I adjust the coreLayer.bounds CGRECT below I get different results, sometimes the missing blocks are at the bottom and sometimes they are in the middle of the image.
I started with the code from the accepted answer of this question and when I noticed the cutoff issue, I modified it to the following:
UIGraphicsBeginImageContext(scrollView.contentSize);
{
CGPoint savedContentOffset = scrollView.contentOffset;
CGRect savedFrame = scrollView.frame;
//hide the scroll bars
[scrollView setShowsHorizontalScrollIndicator:NO];
[scrollView setShowsVerticalScrollIndicator:NO];
scrollView.contentOffset = CGPointZero;
scrollView.frame = CGRectMake(0, 0, scrollView.contentSize.width, scrollView.contentSize.height);
//adjust layer for cut-off
CALayer *coreLayer = scrollView.layer;
coreLayer.bounds = CGRectMake(0, 0, scrollView.contentSize.width, scrollView.contentSize.height);
[coreLayer renderInContext: UIGraphicsGetCurrentContext()];
//[scrollView.layer renderInContext: UIGraphicsGetCurrentContext()];
image = UIGraphicsGetImageFromCurrentImageContext();
scrollView.contentOffset = savedContentOffset;
scrollView.frame = savedFrame;
//reset the scroll bars to default
[scrollView setShowsHorizontalScrollIndicator:YES];
[scrollView setShowsVerticalScrollIndicator:YES];
}
UIGraphicsEndImageContext();
The cut-off adjustment helped (fixed it with some views) but its still getting cut-off when the UIScrollView is fairly long. I've been working on this for a while and can't seem to find a fix. Do you have any suggestions? Has anyone ever encountered this issue?
Please help!
Is your scroll view a table view? If so, pretty much only the onscreen content actually exists due to cell reuse. Even if it's a regular scroll view, it's plausible that the OS is making optimizations by not rendering some offscreen elements of the scroll view. If that's true, you may be able to get this to work by programmatically scrolling one screenful at a time and rendering each of those into your context at the right position.

NSString sizeWithFont: for multiple lines?

Is there an equivalent to NSString's sizeWithFont: method that can be used for calculating the height of text in a UITectView for a given width? All of the methods from NSString only operate on a single line from what I can tell.
From Apple's reference for these NSString methods, you could use -sizeWithFont:constrainedToSize: or -sizeWithFont:constrainedToSize:lineBreakMode: for "Computing Metrics for Multiple Lines of Text".
CGSize size = [theString sizeWithFont:font
constrainedToSize:CGSizeMake(width, 100000)];
return size.height;
For UITextView, all you have to do is call -sizeToFit on the view, and it will automatically resize its height until it can fit all the text available. All you need to do is set the width of the text view, set the text, then call -sizeToFit. The text view will resize its height just enough to fit all the text.
UPDATE:
Apparently text views only shrink when there's excess height, but they don't grow if there's insufficient height to display all the text. In addition, once you call -sizeToFit, the text view's y coordinate is reset back to 0.0f. So here's what you do:
CGFloat textViewWidth = 300.0f;
CGFloat textViewPadding = 10.0f;
UITextView * textView = [[[UITextView alloc] init] autorelease];
textView.text = ...; // Really long string
textView.frame = CGRectMake(0.0f, 0.0f, textViewWidth, CGFLOAT_MAX);
[textView sizeToFit]; // Shrinks the height to fit all the text
textView.frame = CGRectMake(textViewPadding, textViewPadding,
textViewWidth, textView.frame.size.height);
[self.view addSubview:textView];
First, you set the frame just so you can set the width like you want it. You use CGFLOAT_MAX to pretty much indicate infinite height. Next, calling -sizeToFit shrinks the height until it just fits all the text. However, it also resets the y coordinate, so we go ahead and set the frame again to configure the x and y coordinates—in this example, 10.0f for both x and y—, leaving the width alone and keeping the height set to whatever -sizeToFit calculated.
actually, you could use the property contentSize.

sizeWithFont doesn't give correct height for UITextView if there is a long string in the text being wrapped

Is there a way to get the correct size of an NSString using:
- (CGSize)sizeWithFont:(UIFont *)font forWidth:(CGFloat)width lineBreakMode:(UILineBreakMode)lineBreakMode
that doesnt get thrown off by 2 or 3 hundred character strings. At the moment if I try to use this method on these long strings it incorrectly calculates them and I end up with lots of whitespace at the bottom of the UITextView.
I've tried using UILineBreakModeWordWrap and UILineBreakModeCharacterWrap.
the resizing is being done in
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGFloat result = 44.0f;
NSString* text = nil;
CGFloat width = 0;
CGFloat tableViewWidth;
CGRect bounds = [UIScreen mainScreen].bounds;
tableViewWidth = bounds.size.width;
width = tableViewWidth - 150;
text = stringWithLongWords;
if (text) {
CGSize textSize = { width, 20000.0f };
CGSize size = [text sizeWithFont:[UIFont systemFontOfSize:10.0f] constrainedToSize:textSize lineBreakMode:UILineBreakModeWordWrap];
size.height += 50.0f;
result = MAX(size.height, 44.0f+30.0f);
}
return result;
}
UITextView is not exactly like a UILabel wrapped in a UIScrollView. It has line spacing different from the font size and margins that sizeWithFont:constrainedToSize:linkBreakMode: doesn't account for.
Knowing your font size you might be able to calculate the # of lines and take line spacing into account. You can guess at the margins and try to trick sizeWithFont: to give a more useful answer.
The popular solutions seem to be:
just use a UILabel if you don't need any UITextView functionality
if you need hyperlinks, overlay UIButtons that look like hyperlinks over a UILabel
use an off-screen UITextView and its sizeToFit method to get a real answer
I had no luck w/ the 3rd option but it sounds like it should work, so perhaps I did something wrong.
I'm going to try using a UILabel and overlaying buttons for hyperlinks. We'll see how that turns out.
If that fails, there is always the option taken by Loren Brichter (of Tweetie fame): draw everything into a UIView yourself using CoreGraphics.
Good luck!
Check out this post How do I size a UITextView to its content?
It looks like textView.contentSize.height should work (with the caveat that the the correct contentSize is only available after the UITextView has been added to the view with addSubview)
You said that you have a UITableView with differing heights. Have you set the reuse identifier to the same thing for all of the cells? It could be that older cells with their height already set are being reused. If this is the problem, you should resize the cell again when it's being reused.
The best solution I have found so far is to have a separate hidden UITextView with the same font settings, and set its text. After that its contetSize should be accurate.
The width you are using is the width for your UITextView... but you aren't concerned with that width, you are concerned with the width of the actual text area nested inside the text view.
UITextViews, by default, have padding around their borders to produce a space in-between the typed text and the edge of the UITextView a few pixels wide (and long for the top)... To get the correct size you shouldn't use
textView.frame.size.width
but rather,
textView.frame.size.width-(textView.contentInset.left+textView.contentInset.right+textView.textContainerInset.left+textView.textContainerInset.right+textView.textContainer.lineFragmentPadding/*left*/+textView.textContainer.lineFragmentPadding/*right*/)
^Which takes the width of the UITextView and subtracts out all the padding so you are left with the width of just the type-able text area.
Same goes for height except for lineFragmentPadding doesn't have a bottom so you only subtract it out once instead of twice.
The final code is something like this:
CGSize textViewContentSize = CGSizeMake(theTextView.frame.size.width-(theTextView.contentInset.left+theTextView.contentInset.right+theTextView.textContainerInset.left+theTextView.textContainerInset.right+theTextView.textContainer.lineFragmentPadding/*left*/+theTextView.textContainer.lineFragmentPadding/*right*/), theTextView.frame.size.height-(theTextView.contentInset.top+theTextView.contentInset.bottom+theTextView.textContainerInset.top+theTextView.textContainerInset.bottom+theTextView.textContainer.lineFragmentPadding/*top*//*+theTextView.textContainer.lineFragmentPadding*//*there is no bottom padding*/));
CGSize calculatedSize = [theTextView.text sizeWithFont:theTextView.font
constrainedToSize:textViewContentSize
lineBreakMode:NSLineBreakByWordWrapping];
CGSize adjustedSize = CGSizeMake(ceilf(calculatedSize.width), ceilf(calculatedSize.height));
Inspired by #MrNickBarker's answer, here's my solution:
CGFloat width = 280.0f;
UITextView *t = [[UITextView alloc] init];
[t setFont:[UIFont systemFontOfSize:17]];
[label setText:#"some short or long text, works both"];
CGRect frame = CGRectMake(0, 0, width, 0);
[t setFrame:frame];
// Here's the trick: after applying the 0-frame, the content size is calculated and can be used in a second invocation
frame = CGRectMake(0, 0, width, t.contentSize.height);
[t setFrame:frame];
The only issue remaining for me is that this doesn't work with modified insets.
Still can't believe such twists are required, but since -[NSString sizeWithFont:forWidth:lineBreakMode:] does not respect insets, paddings, margins, line spacings and the like, it seems this is the only working solution at the moment (i.e. iOS 6).

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.