Calculating the number of lines needed for a UILabel does not work on iOS9 but did on previous versions - uilabel

The following block of code was working in iOS7 and 8 but when I updated to iOS9 the number of lines is always returning as 0.
Is there anything obvious that needs to change>
NSInteger lineCount = 0;
CGSize textSize = CGSizeMake(self.messageLabel.frame.size.width, MAXFLOAT);
int rHeight = (int)lroundf([self.messageLabel sizeThatFits:textSize].height);
int charSize = (int)lroundf(self.messageLabel.font.leading);
lineCount = rHeight/charSize;
NSLog(#"No of lines: %li",(long)lineCount);

Related

Two lines of code that are saying the same thing?

I am folowing this exercise in a book, and at one point, there is an implementation for a method that creates labels placed at random in the view. Here is the code:
1 - (void)drawHypnoticMessage:(NSString *)message{
2 for(int i =0; i<20; i++){
3 UILabel *messageLabel = [[UILabel alloc] init];
4 messageLabel.backgroundColor = [UIColor clearColor];
5 messageLabel.textColor = [UIColor whiteColor];
6 messageLabel.text = message;
7 [messageLabel sizeToFit];
8 int width = self.view.bounds.size.width - messageLabel.bounds.size.width;
9 int randomX = arc4random() % width;
10 int height = self.view.bounds.size.height - messageLabel.bounds.size.height;
11 int randomY = arc4random() % height;
12 CGRect frame = messageLabel.frame;
13 frame.origin = CGPointMake(randomX, randomY);
14 messageLabel.frame = frame;
15 [self.view addSubview:messageLabel];
16 }
17 }
This works fine. My question is regarding the lines 12 and 14. When I was copying this exercise to Xcode from the book and I reached line 12, I instinctively changed it to:
12 CGRect frame;
This didn't work, and I don't understand why. To me, lines 12 and 14:
12 CGRect frame = messageLabel.frame;
14 messageLabel.frame = frame;
Are saying the same thing twice, surely. Can anyone please explain why it is not so?
Objective-C overloads C's struct member access operator (.) to also access properties of Objective-C objects. The problem is that you can't mix the two in a single assignment statement because the compiler's parser gets confused.
messageLabel is an object that has a property called frame. frame is a struct with two members: origin and size.
To get around this limitation, you need to use a temporary variable to hold the struct (e.g. frame), manipulate this copy (structs are copied on assignment), and then assign the new struct (frame) to the property, which updates the property's value.
You are modifying the frame in line 13, i.e. changing the origin value. The book is showing you a common technique to change the frame, because you can't change point or size value by itself.

Setting UILabel Text Multiple Times Within A Loop

I'm fooling around in XCode, trying to learn a little about the iOS SDK and Objective-C.
I have this for loop below, and it should print out several values to the screen (depending on the amount of months chosen), but instead, it's only printing out the final value.
Can anybody point out why?
Thanks a bunch, in advance!
for (int i = 1; i <= myMonthsDouble; i++)
{
myPaymentAmount = (myBalanceDouble/10) + myInterestDouble;
myBalanceDouble -= myPaymentAmount;
//convert myPaymentAmount double into string named myPaymentAmountString
NSString *myPaymentAmountString = [NSString stringWithFormat:#"%f", myPaymentAmount];
NSString *paymentInformation = [[NSString alloc] initWithFormat:#"%# months, %# per month.", monthsString, myPaymentAmountString];
myInterestDouble = (myBalanceDouble * (myInterestDouble/100))/12;
self.label.text = paymentInformation;
}
It is only printing the last value to the screen because you only have one label. Each time you get to the end of the loop, you are setting that label's text which is overriding the last value. If you want to print all of them to the screen, you will need to have either multiple labels or you will have to append the strings together and put them in either a label or a UITextView that is formatted so that they can all be seen (most likely a text view but it can be done with a label.)
One example of doing this would be:
label.text = [label.text stringByAppendingString:newString];
numLines++; //this starts at 0;
and then at the end:
label.numberOfLines = numLines;

NSString font size specific to frame width

I am using drawRect for a text display, calling NSString. I am trying to implement using sizeWithFont to auto resizing font (shrinking) with default font size of 17 and using a loop to reduce the font size by 1 if it does not fit the size of width. Can anyone help me how to implement this? Example would be nice right now I just have the font size set to 17.0
[[self.string displayName] drawAtPoint:CGPointMake(xcoord, ycoord) withFont:[UIFont boldSystemFontOfSize:17.0]];
CGSize size = [[self.patient displayName] sizeWithFont:[UIFont boldSystemFontOfSize:17.0]];
max_current_y = size.height > max_current_y ? size.height : max_current_y;
xcoord = xcoord + 3.0f + size.width;
OK never mind. Here's modified version of the same method that takes NSString for which to return a font:
-(UIFont*)getFontForString:(NSString*)string
toFitInRect:(CGRect)rect
seedFont:(UIFont*)seedFont{
UIFont* returnFont = seedFont;
CGSize stringSize = [string sizeWithAttributes:#{NSFontAttributeName : seedFont}];
while(stringSize.width > rect.size.width){
returnFont = [UIFont systemFontOfSize:returnFont.pointSize -1];
stringSize = [string sizeWithAttributes:#{NSFontAttributeName : returnFont}];
}
return returnFont;
}
Here's how to call it:
NSString* stringToDraw = #"Test 123";
CGRect rect = CGRectMake(100., 100., 100., 200.);
UIFont* font = [self getFontForString:stringToDraw toFitInRect:rect seedFont:[UIFont systemFontOfSize:20]];
[stringToDraw drawInRect:rect withFont:font];
Code is for iOS7+
Trying font sizes with step 1.0 may be very slow. You can tremendously improve the algorithm by making two measures for two different sizes, then using linear approximation to guess the size that will be very close to the right one.
If it turns out not close enough, repeat the calculation using the guessed size instead of one of the previous two until it is good enough or stops changing:
// any values will do, prefer those near expected min and max
CGFloat size1 = 12.0, size2 = 56.0;
CGFloat width1 = measure_for_size(size1);
CGFloat width2 = measure_for_size(size2);
while (1) {
CGFloat guessed_size = size1 + (required_width - width1) * (size2 - size1) / (width2 - width1);
width2 = measure_for_size(guessed_size);
if ( fabs(guessed_size-size2) < some_epsilon || !is_close_enough(width2, required_width) ) {
size2 = guessed_size;
continue;
}
// round down to integer and clamp guessed_size as appropriate for your design
return floor(clamp(guessed_size, 6.0, 24.0));
}
is_close_enough() implementation is completely up to you. Given that text width grows almost linearly of font size, you can simply drop it and just do 2-4 iterations which should be enough.
I wanted to try to make a version that didn't have to repeatedly check font sizes using a do...while loop. Instead, I assumed that font point sizes were a linear scale, then worked out the size difference between the required frame width and the actual frame width, then adjusted the font size accordingly. Therefore, I ended up with this function:
+ (CGFloat)fontSizeToFitString:(NSString *)string inWidth:(float)width withFont:(UIFont *)font
{
UILabel *label = [UILabel new];
label.font = font;
label.text = string;
[label sizeToFit];
float ratio = width / label.frame.size.width;
return font.pointSize * ratio;
}
Pass in a font of any size, as well as the string and the required width, and it will return you the point size for that font.
I also wanted to take it a bit further and find out the font size for a multi-line string, so that the longest line would fit without a line break:
+ (CGFloat)fontSizeToFitLongestLineOfString:(NSString *)string inWidth:(float)width withFont:(UIFont *)font
{
NSArray *stringLines = [string componentsSeparatedByString:#"\n"];
UILabel *label = [UILabel new];
label.font = font;
float maxWidth = 0;
for(NSString *line in stringLines)
{
label.text = line;
[label sizeToFit];
maxWidth = MAX(maxWidth, label.frame.size.width);
}
float ratio = width / maxWidth;
return font.pointSize * ratio;
}
Seems to work perfectly fine for me. Hope it helps someone else.
Original poster didn't specify what platform he was working on, but for OSX developers on Mavericks, sizeWithFont: doesn't exist and one should use sizeWithAttributes :
NSSize newSize = [aString sizeWithAttributes:
[NSDictionary dictionaryWithObjectsAndKeys:
[NSFont fontWithName:#"Arial Rounded MT Bold" size:53.0],NSFontAttributeName,nil
]];
Here's a method which can return you font that will fit in a rect:
-(UIFont*)getFontToFitInRect:(CGRect)rect seedFont:(UIFont*)seedFont{
UIFont* returnFont = seedFont;
CGSize stringSize = [self sizeWithFont:returnFont];
while(stringSize.width > rect.size.width){
returnFont = [UIFont systemFontOfSize:returnFont.pointSize -1];
stringSize = [self sizeWithFont:returnFont];
}
return returnFont;
}
You can add this method to a NSString category. You can find more about how to add a category here: http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html#//apple_ref/doc/uid/TP40011210-CH6-SW2
If you don't want to create a category, you can add this method to one of your utility classes and pass in the string for which you want the font to be returned.
Here is another method, inspired by #puru020 & #jowie answers. Hope it helps someone
-(UIFont *) adjustedFontSizeForString:(NSString *)string forWidth:(float)originalWidth forFont:(UIFont *)font
{
CGSize stringSize = [string sizeWithFont:font];
if(stringSize.width <= originalWidth)
{
return font;
}
float ratio = originalWidth / stringSize.width;
float fontSize = font.pointSize * ratio;
return [font fontWithSize:fontSize];
}
I modified a bit the solution of #puru020 , added the support for attributes, and improved a bit:
Note: The method should be wrapped in a NSString Category
- (UIFont*)requiredFontToFitInSize:(CGSize)size seedFont:(UIFont*)seedFont attributes:(NSDictionary*)attributes{
UIFont *returnFont = [UIFont systemFontOfSize:seedFont.pointSize +1];
NSMutableDictionary *mutableAttributes = attributes.mutableCopy;
CGSize stringSize;
do {
returnFont = [UIFont systemFontOfSize:returnFont.pointSize -1];
[mutableAttributes setObject:returnFont forKey:NSFontAttributeName];
stringSize = [self sizeWithAttributes:mutableAttributes];
} while (stringSize.width > size.width);
return returnFont;
}

Draw polyline between given points on the map

I am implementing an iOS application, and I want to draw a polyline between several given coordinates on the map.
I wrote the code and got the polylines being drawn from my point reaching an infinite point. In other words the beginning point of the line starts from the my given lat and long point, but the end of the line is infinite and not the other point.
This is my code...
I filled the coordinates in a NSMutableArray called routeLatitudes. The array cells are being filled one for latitude and one for longitude.
MKMapPoint* pointArr = malloc(sizeof(CLLocationCoordinate2D) * [routeLatitudes count]);
for(int idx = 0; idx < [routeLatitudes count]; idx=idx+2)
{
CLLocationCoordinate2D workingCoordinate;
workingCoordinate.latitude=[[routeLatitudes objectAtIndex:idx] doubleValue];
workingCoordinate.longitude=[[routeLatitudes objectAtIndex:idx+1] doubleValue];
MKMapPoint point = MKMapPointForCoordinate(workingCoordinate);
pointArr[idx] = point;
}
// create the polyline based on the array of points.
routeLine = [MKPolyline polylineWithPoints:pointArr count:[routeLatitudes count]];
[mapView addOverlay:self.routeLine];
free(pointArr);
and overlay delegate
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay
{
MKOverlayView* overlayView = nil;
if(overlay == routeLine)
{
self.routeLineView = [[[MKPolylineView alloc] initWithPolyline:self.routeLine] autorelease];
self.routeLineView.fillColor = [UIColor colorWithRed:51 green:51 blue:255 alpha:1];
self.routeLineView.strokeColor = [UIColor colorWithRed:204 green:0 blue:0 alpha:1];
self.routeLineView.lineWidth = 3;
overlayView = routeLineView;
}
return overlayView;
}
So I need the lines to be drawn between the points over the map. The beginning of the line is the first dropped pin, and the end is on the last dropped pin.
According to the code, the routeLatitudes array has objects listed like this:
index 0: latitude for point 1
index 1: longitude for point 1
index 2: latitude for point 2
index 3: longitude for point 2
index 4: latitude for point 3
index 5: longitude for point 3
...
So if routeLatitudes.count is 6, it actually has only 3 points.
This means the malloc is allocating the wrong number of points and the polylineWithPoints call is also specifying the wrong number of points for the overlay.
The other problem is that since pointArr will contain only half the objects that routeLatitudes has, you can't use the same index value for both arrays.
The for loop index counter idx is being incremented by 2 at each iteration because that's how the routeLatitudes points are layed out but then the same idx value is used to set pointArr.
So for idx=0, pointArr[0] is set but then for idx=2, pointArr[2] is set (instead of pointArr[1]), and so on. This means every other position in pointArr is left uninitialized resulting in the lines "going to infinity".
So the corrected code might look like this:
int pointCount = [routeLatitudes count] / 2;
MKMapPoint* pointArr = malloc(sizeof(MKMapPoint) * pointCount);
int pointArrIndex = 0; //it's simpler to keep a separate index for pointArr
for (int idx = 0; idx < [routeLatitudes count]; idx=idx+2)
{
CLLocationCoordinate2D workingCoordinate;
workingCoordinate.latitude=[[routeLatitudes objectAtIndex:idx] doubleValue];
workingCoordinate.longitude=[[routeLatitudes objectAtIndex:idx+1] doubleValue];
MKMapPoint point = MKMapPointForCoordinate(workingCoordinate);
pointArr[pointArrIndex] = point;
pointArrIndex++;
}
// create the polyline based on the array of points.
routeLine = [MKPolyline polylineWithPoints:pointArr count:pointCount];
[mapView addOverlay:routeLine];
free(pointArr);
Also note in the malloc line, I corrected sizeof(CLLocationCoordinate2D) to sizeof(MKMapPoint). This technically wasn't causing a problem because those two structs happen to be the same length but it's correct to use sizeof(MKMapPoint) since that's what the array is going to contain.

Core Graphics pointillize effect on CGImage

So I have been writing a lot of image processing code lately using only core graphics and i have made quite a few working filters that manipulate the colors, apply blends, blurs and stuff like that. But I'm having trouble writing a filter to apply a pointillize effect to an image like this:
what I'm trying to do is get the color of a pixel and fill an ellipse with that color, looping through the image and doing this every few pixels here is the code:
EDIT: here is my new code this time its just drawing a few little circles in the bottom of the image am I doing it right like you said?
-(UIImage*)applyFilterWithAmount:(double)amount {
CGImageRef inImage = self.CGImage;
CFDataRef m_dataRef = CGDataProviderCopyData(CGImageGetDataProvider(inImage));
UInt8* m_pixelBuf = (UInt8*)CFDataGetBytePtr(m_dataRef);
int length = CFDataGetLength(m_dataRef);
CGContextRef ctx = CGBitmapContextCreate(m_pixelBuf,
CGImageGetWidth(inImage),
CGImageGetHeight(inImage),
CGImageGetBitsPerComponent(inImage),
CGImageGetBytesPerRow(inImage),
CGImageGetColorSpace(inImage),
CGImageGetBitmapInfo(inImage));
int row = 0;
int imageWidth = self.size.width;
if ((row%imageWidth)==0) {
row++;
}
int col = row%imageWidth;
for (int i = 0; i<length; i+=4) {
//filterPointillize(m_pixelBuf, i, context);
int r = i;
int g = i+1;
int b = i+2;
int red = m_pixelBuf[r];
int green = m_pixelBuf[g];
int blue = m_pixelBuf[b];
CGContextSetRGBFillColor(ctx, red/255, green/255, blue/255, 1.0);
CGContextFillEllipseInRect(ctx, CGRectMake(col, row, amount, amount));
}
CGImageRef imageRef = CGBitmapContextCreateImage(ctx);
CGContextRelease(ctx);
UIImage* finalImage = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
CFRelease(m_dataRef);
return finalImage;
}
One problem I see right off the bat is you are using the raster cell number for both your X and Y origin. A raster in this configuration is just a single dimension line. It is up to you to calculate the second dimension based on the raster image's width. That could explain why you got a line.
Another thing: seems like you are reading every pixel of the image. Didn't you want to skip pixels that are the width of the the ellipses you are trying to draw?
Next thing that looks suspicious is I think you should create the context you are drawing in before drawing. In addition, you should not be calling:
CGContextRef contextRef = UIGraphicsGetCurrentContext();
CGContextSaveGState(contextRef);
and
CGContextRestoreGState(contextRef);
inside the loop.
EDIT:
One further observation: your read RGB values are 0-255, and the CGContextSetRGBFillColor function expects values to be between 0.f - 1.f. This would explain why you got white. So you need to divide by 255:
CGContextSetRGBFillColor(contextRef, red / 255, green / 255, blue / 255, 1.0);
If you have any further questions, please don't hesitate to ask!
EDIT 2:
To calculate the row, first declare a row counter outside the loop:
int row = 0; //declare before the loop
int imageWidth = self.size.width; //get the image width
if ((i % imageWidth) == 0) { //we divide the cell number and if the remainder is 0
//then we want to increment the row counter
row++;
}
We can also use mod to calculate the current column:
int col = i % imageWidth; //divide i by the image width. the remainder is the col num
EDIT 3:
You have to put this inside the for loop:
if ((row%imageWidth)==0) {
row++;
}
int col = row%imageWidth;
Also, I forgot to mention before, to make the column and row 0 based (which is what you want) you will need to subtract 1 from the image size:
int imageWidth = self.size.width - 1;