UILabel (CALayer) is using large amounts of virtual memory - ios

In Xcode and Instruments I see UILabel (CALayer) using large amounts of virtual memory (Anonymous VM). I see about 235 KB of virtual memory per UILabel.
I think this perhaps is a new issue with iOS 7.1 or 7.1.1.
Is this expected?
I created a simple program that creates 500 UILabels and Instruments shows 115MB of memory used. At about 1500 labels the application is terminated by the OS.
for (int i = 0; i < 500; i++)
{
index = (int)[self.items count];
index++;
frame = CGRectMake(10.0, 20, 300.0, 50.0);
UILabel *newLabel = [[UILabel alloc] initWithFrame:frame];
newLabel.text = [NSString stringWithFormat:#"This is text for label: %d", index];
newLabel.backgroundColor = [UIColor whiteColor];
[self.view addSubview:newLabel];
[self.items setObject:newLabel forKey:[NSNumber numberWithInteger:index]];
}
Thoughts?

UILabel, and any view that uses drawRect (at least on iOS 7+) is backed by a texture, so every UILabel will use a lot of memory, the larger the label, the more memory used.
I've found this especially painful in my photo editing extension for You Doodle which allows adding text in the Photos app. Unfortunately I have to greatly restrict the scale range of the text because photo extensions are much more limited to memory usage than regular apps before they crash.
This can easily be verified by running instruments and allocating a bunch of large UILabel's and setting their text and ensuring they are all visible, i.e.:
CGRect f = CGRectMake(0.0.0f, 0.0f, 300.0f, 300.0f);
for (NSInteger i = 0; i < 100; i++)
{
UILabel* l = [[UILabel alloc] initWithFrame:f];
l.backgroundColor = UIColor.blueColor;
l.textColor = UIColor.whiteColor;
l.text = #"UILabel text for the memory test";
l.numberOfLines = 0;
[self.view addSubview:l];
}

When reporting this kind of thing (to Stack Overflow or to Apple), you really should eliminate unnecessary excess code. This code is sufficient to reproduce the phenomenon:
for (int i = 0; i < 500; i++)
{
CGRect frame = CGRectMake(10.0, 20, 300.0, 50.0);
UILabel *newLabel = [[UILabel alloc] initWithFrame:frame];
newLabel.backgroundColor = [UIColor whiteColor];
[self.view addSubview:newLabel];
}
That causes the app to use 129MB on my machine. (No need to use Instruments: Xcode now shows memory usage directly.)
My first response was: "I guess I don't find this terribly surprising. If you change the frame to a smaller rect, less memory is used. Views are expensive! They are backed by a bitmap."
However, if you change the UILabel to a plain UIView, only 13MB is used. I think that's a sufficient difference to warrant filing a bug.

Related

UIPageControl: dots with rectangular format

I use native UIPageControl in my objective-c application and I want to have a rectangular dots instead of circular one. I searched about that and for a library providing this but I didn’t found anything.
I tried to do it like this but it has no effect:
-(void)updateDotsFormat {
for (int i = 0; i < [self.pageControl.subviews count]; i++)
{
UIView* dot = [self.pageControl.subviews objectAtIndex:i];
UIView* dotView = [[UIView alloc] init];
dotView.backgroundColor = [UIColor blueColor];
CGRect frame = dotView.frame;
frame.size.width = 50;
frame.size.height = 20;
dotView.frame = frame;
[dot addSubview:dotView];
}
}
How can I set the dots format to rectangle?
and I want to have a rectangular dots instead of circular one. I searched a that and for a library providing this but I didn’t found anything
Well, you would have to design your own view/control, since UIPageControl has no such ability. There are libraries for such a thing, but asking for one is not permitted on Stack Overflow; or you can write it yourself.

Calculate the minimum rectangle around text in UIlabel

I programmatically create some text.
The text gets attributes and then returns to the view controller where is displayed.
Originally i create a rectangle UIlabel with max height 40.
Therefore if the the text is too big the font size decreases to fit into the rectangle by
applying to the text adjustsFontSizeToFitWidth.
If the text is small there is a lot of empty space in the rectangle (on top and below).
Is it possible at that point to get the minimum rectangle eclosing my text.
Thank you
.
NSAttributedString * Text=[circleModel.Selected_set objectForKey:#"sentence_text"];
CGRect recty;
recty= CGRectMake(mainScreen.size.width*0.55, 100, mainScreen.size.width*0.43,40);
UILabel *Latex_text = [[UILabel alloc] initWithFrame:recty];
Latex_text.AttributedText = Text;
Latex_text.numberOfLines = 0;
Latex_text.adjustsFontSizeToFitWidth = YES;
Latex_text.textAlignment = NSTextAlignmentCenter;
[self.view addSubview:Latex_text];
//Latex_text.backgroundColor = [UIColor whiteColor];
What about using textRectForBounds? It should give you at least an estimate if the adjustsFontSizeToFitWidth doesn't play well with it.
UILabel *Latex_text = [[UILabel alloc] init];
Latex_text.AttributedText = Text;
Latex_text.numberOfLines = 0;
Latex_text.adjustsFontSizeToFitWidth = YES;
Latex_text.textAlignment = NSTextAlignmentCenter;
recty.size = [Latex_text textRectForBounds:recty limitedToNumberOfLines:0].size;
[Latex_text setFrame:recty];

Adding UILabels to UIView dynamically

I have a web service that brings some text data back. The idea is to extract the text and create UILabels for the text to show on screen. I however, do not know two things:
How many labels are needed
The length of the text
Due to having no prior knowledge of these things I need a way to create some labels that have the right length for the text it contains.
I've managed to store the data into some objects that are in an array and then iterate through them creating the labels.. something like this:
for (BBItemAttributes *attribute in self.item.productAttributes){
UILabel *label = [[UILabel alloc]initWithFrame:CGRectMake(100, 100, 50, 10)];
label.text = attribute.displayTemplate;
[self.scrollView addSubview:label];
}
Obviously the problem here is due to creating the UILabels in code and having each one a hard coded CGRect size they are all onto of each other and sometimes don't fit in their respective boxes due to text being too long.
I need a way to line the labels up all on the same X axis point and on different Y axis points, so that they sit next to each other a certain space apart.
Is there a better way to do this?
You need to do something like this;
CGFloat yOrigin = 100;
CGFloat fixedSpace = 10;
for (BBItemAttributes *attribute in self.item.productAttributes)
{
UILabel *label = [[UILabel alloc]initWithFrame:CGRectMake(100, yOrigin, 50, 10)];
label.text = attribute.displayTemplate;
label.numberOfLines = 0;
[label sizeToFit]; // Resizes label to fit the text.
[self.scrollView addSubview:label];
yOrigin += label.frame.size.height + fixedSpace;
}
Try this. it will help you.
float yAxis = 0;
for (int i = 0; i< 50; i++)//for (BBItemAttributes *attribute in self.item.productAttributes){
{
CGSize size;
UIFont *font = [UIFont systemFontOfSize:15.0]; // Set Your Font
//Your String = attribute.displayTemplate
if (ios7)//Condition to check if ios7
{
//iOS 7
CGRect frame = [#"Your String" boundingRectWithSize:CGSizeMake(50, CGFLOAT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:font}
context:nil];
size = CGSizeMake(frame.size.width, frame.size.height+1);
}
else
{
//iOS 6.0
size = [#"Your String" sizeWithFont:font constrainedToSize:CGSizeMake(50, CGFLOAT_MAX) lineBreakMode:NSLineBreakByWordWrapping];
}
UILabel *label = [[UILabel alloc]initWithFrame:CGRectMake(100, yAxis, 50, size.height)];
label.text = #"Your String";//attribute.displayTemplate
label.numberOfLines = 0;
label.tag = i; // Set tag if you want to access in future. :)
[self.scrollView addSubview:label];
// Increase yAxis
yAxis = yAxis + size.height + 10;//10 is extra space if you want between two label
}
// Do Not forget to set Contentsize of your ScrollView.
self.scrollView.contentSize = CGSizeMake(320, yAxis + 20);
If your showing a data structure that is a like a list of labels, I would suggest that you use UITableViewController for showing the list.It has the built in facility and properties that make it easy to display a list of objects.
Now the answer to your first Question.You can get the number of labels by determining the number of objects that are in your array. Something like this :
NSInteger *noOfLabels = yourArray.count;
As far as the length of the text is concerned, I suggest not keeping it very lengthy because Labels are not good for lengthy texts. You can however, specify the number of a lines for a particular label in case if it has lengthy text.This will make the text appear in two lines. Though you will have to adjust width and height accordingly.
You can get the text length by doing something similar to this :
NSString *text;
NSInteger *i = (NSInteger*)[text length];
Hope this helps.

Multiple views with shadow and rotation becomes very slow

I'm trying to make multiple stacks of uiview's. Each of those UIView's with a shadow, slight rotation and scale. As a test I'm making 10 stack's of 10 views. Drawing all this is very slow.. Is there a good way to optimise this? I tried making the shadow and background from a image but that was ugly and equally as slow. I put these stacks in a UIScrollView
for (int k = 0; k < 9; k++) {
for (int i = 0; i < 9; i++) {
UIView *stack = [[UIView alloc] initWithFrame:CGRectMake((i * 106), (k * 106), 110, 110)];
for (int i = 0; i < 10; i++) {
CardView *cardView = [[CardView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
//cardView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"card_background.png"]];
cardView.layer.shadowOffset = CGSizeMake(0, 0);
cardView.layer.shadowOpacity = 0.3f;
cardView.layer.shadowPath = [UIBezierPath bezierPathWithRect:(CGRect){CGPointZero, cardView.layer.bounds.size}].CGPath; // Set shadow path, without this the performance is *really* bad
cardView.transform = CGAffineTransformMakeRotation(((arc4random() % 20) - 10.0f) / 100.0f);
cardView.transform = CGAffineTransformScale(cardView.transform, 0.35, 0.35);
cardView.layer.shouldRasterize = YES;
cardView.layer.rasterizationScale = 0.5;
cardView.center = CGPointMake(55, 55);
[stack addSubview:cardView];
}
[_backgroundView addSubview:stack];
}
}
Edit 1; Tried some stuff, disabling rasterzation isn't helping much, disabling the shadow doesn't help much either, rotation and scaling are recourse intensive with this much uiview's too. Would async drawing (one stack at the time) be an option?
Edit 2; Guess making 100 UIView's is just slow anyway. I'll report back if I've found a better (I guess async or something like that) solution
Are you using these images in some sort of animation? If not you can try turning off rasterizing to increase performance.
cardView.layer.shouldRasterize = NO;
Shadows are very computationally intensive. If shouldRasterize doesn't do it, make a UIImage for the shadow, and if your views can change size, load the image with resizableImageWithCapInsets.

Trouble with CALayer

I have this code and XCode Analyze does not issue any warnings, but in the console every time I get a message:
2012-07-19 23:15:35.122 AttachIt [5725:907] Received memory warning.
Where is my mistake, point it, please.
for(int j=0;j<images;j++){
#autoreleasepool {
NSInteger currentRow = 0;
for(int k = 0; k<i;k++)
currentRow = currentRow + [[assetGroups objectAtIndex:k] numberOfAssets];
asset = [assets objectAtIndex:j+currentRow];
float size = [self getRandomNumberBetweenMin:60.0 andMax:65.0];
CGRect rect;
if(iPad)
rect = CGRectMake(10+j*35.5, 75-size, size, size);
else
rect = CGRectMake(10+j*26, 75-size, size, size);
UIImageView *temp = [[UIImageView alloc] initWithImage:[UIImage imageWithCGImage:[asset thumbnail]]];
temp.frame = rect;
temp.layer.backgroundColor = [UIColor blueColor].CGColor;
temp.layer.shadowOffset = CGSizeMake(0, 3);
temp.layer.shadowRadius = 5.0;
temp.layer.shadowColor = [UIColor blackColor].CGColor;
temp.layer.shadowOpacity = 0.8;
temp.layer.masksToBounds = NO;
temp.layer.borderColor = [[UIColor whiteColor] CGColor];
temp.layer.borderWidth = 2;
[temp setTransform:CGAffineTransformMakeRotation(degreesToRadians([self getRandomNumberBetweenMin:-5 andMax:5]))];
temp.layer.shouldRasterize = TRUE;
[albumRow addSubview:temp];
}
}
Memory warnings are not the result of mistakes; they are a normal part of running on iOS. You're allocating a bunch of stuff, so getting a memory warning is not surprising.
If you're having problems where your app is being killed by the system due to excess memory usage, you might consider populating those images over time rather than in a loop. You could accomplish this via an NSOperationQueue or similar mechanism to load the images one at a time.
In any case, you could use the "Allocations" or "Activity Monitor" instruments to see if your memory usage is growing and by how much.

Resources