Updating UILabel text too often - ios

I need to show a tilt of the device in my app. The algorithm is pretty simple, I'm using CMMotionManager's attitude for calculating tilt, and I'm updating a label which shows degrees like so:
- (void)tiltUpdated:(float)tilt
{
_degreesLabel.text = [NSString stringWithFormat:#"%.1f°", tilt];
}
My problem is next - CMMotionManager calls gyroscope updates approximately 10 times per second. And every time I'm calculating new tilt and calling tiltUpdated method each time. And when I do that, my app starts to incredibly lag. Few things I need to clarify:
Cause of lags is in updating the label. I defined it pretty easily
by commenting on it. So it's not the tilt calculations (that is why I
didn't provide a code for that here)
Applications also show camera output all the time. I turned off
camera and things got a little better but still, the application is lagging.
Is there any way to optimize updating UILabel text? Thanks in advance!

Ok, ending up answering my own question, but I hope that'll come in handy for somebody :).
I didn't find any valuable and detailed information about UILabel performance (which is too bad because I'm interested to learn something about that), but I found an alternative, which is CATextLayer.
I'm creating CATextLayer:
_textLayer = [CATextLayer new];
_textLayer.frame = CGRectMake(0.0f, _degreesLabelView.frame.size.height*0.5f - textHeight*0.5f, _degreesLabelView.frame.size.width, textHeight);
_textLayer.backgroundColor = [UIColor clearColor].CGColor;
_textLayer.foregroundColor = [UIColor whiteColor].CGColor;
_textLayer.alignmentMode = kCAAlignmentCenter;
_textLayer.contentsScale = UIScreen.mainScreen.scale;
_textLayer.fontSize = 17.0;
_textLayer.string = #"0°";
[_degreesLabelView.layer addSublayer:_textLayer];
And here I'm updating text:
- (void)tiltUpdated:(float)tilt
{
_textLayer.string = [NSString stringWithFormat:#"%.1f°", tilt];
}
Also CATextLayer is kind of "animating" text changes with slight "fadeIn/fadeOut" and I like it :)

Related

Initial particles from CAEmitterLayer don't start at emitterPosition

My goal is to make an explosion-like animation where many particles are emitted in a short duration. My problem is that CAEmitterLayer, when it begins emitting, adds "future" particles to make it looks like the animation has been running for a while.
How can I disable or workaround this? When I increase the birthRate, I only want particles to start appearing from the emitterPosition, and not at all points along the CAEmitterCell's projected lifetime. Any help is appreciated.
#import "EmitterView.h"
#interface EmitterView ()
#property CAEmitterLayer* emitter;
#end
#implementation EmitterView
- (void) awakeFromNib {
[super awakeFromNib];
self.emitter = (CAEmitterLayer*)self.layer;
CAEmitterCell* snowflake = [CAEmitterCell emitterCell];
snowflake.contents = (id)[[UIImage imageNamed: #"snowflake"] CGImage];
snowflake.lifetime = 3;
snowflake.birthRate = 1;
snowflake.velocity = 50;
snowflake.emissionRange = 3.1415;
self.emitter.birthRate = 0;
self.emitter.emitterCells = [NSArray arrayWithObject: snowflake];
self.emitter.emitterPosition = CGPointMake(100, 100);
self.emitter.emitterSize = CGSizeMake(0, 0);
self.emitter.emitterShape = kCAEmitterLayerPoint;
}
+ (Class) layerClass {
return [CAEmitterLayer class];
}
- (void) burst {
self.emitter.birthRate = 10;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
self.emitter.birthRate = 0;
});
}
#end
This behavior actually changed with the release of iOS 7 and apparently hasn't gone back since then. It used to behave the way you would expect back before iOS 7, but either Apple introduced a bug that caused this, or they chose to change the behavior without telling anybody.
I filed a bug for this exact issue on August 28, 2013, which was closed as a duplicate of another bug report for the same issue. Apple's bug reporter is reporting that the other bug is still open, which means Apple hasn't addressed it yet, despite more than a year and a half to take care of it.
I recommend filing your own bug report with Apple, giving them a simple app that demonstrates the behavior, and maybe getting some renewed attention to it will help get them to do something about it.
EDIT:
After researching the issue a little, I found out that the solution is to add this line:
self.emitter.beginTime = CACurrentMediaTime();
It's important to know that CAEmitterLayer is a subclass of CALayer, which conforms to the CAMediaTiming protocol. Why this whole fact isn't better documented is beyond me.
Note that you probably want to call this from your - (void)burst method, but that if you call it again within a short period of time, while previous particles are still around, you might possibly see some odd behavior because of resetting the beginTime.

cocos2d: why isn't label appearing?

Hello I am making a side scrolling cocos2d game and I want a label to show how far the user has flown in the game. For some reason with the code I wrote the label is not appearing. Here is my GameEngine class that calls the class method that is supposed to make the label appear:
//Set the meterDistance
meterDistance = [MeterDistance createTheMeterDistance];
[self addChild:meterDistance z:10];
Here is the code in the MeterDistance class:
meters = 1;
meterLabel = [CCLabelBMFont labelWithString:#"0" fntFile:#"green_arcade-ipad.fnt"];
meterLabel.position = ccp(200, screenHeight - 100);
[self addChild:meterLabel z:10];
meterLabel.anchorPoint = ccp(1.0, 0.5);
[self schedule:#selector(updateLabel:)interval:1.0f/20.0f];
Here is the updateLabel method:
-(void)updateLabel:(ccTime)delta{
meters++;
NSString* scoreString = [NSString stringWithFormat:#"%d", meters];
[meterLabel setString:scoreString];
}
It's been a while since I last dealt with cocos2d code...
What you've written looks ok.
Take it one step at a time and see where it goes wrong.
Position your label in the middle of the screen (maybe screenheight is off, or anchorPoint moves the label outside of the screen).
Another possible cause is if the font file name is not exactly #"green_arcade-ipad.fnt".
Maybe you missed a capital letter?
Otherwise maybe some other element of your layer could be obstructing the label.

UITextView changes font when detects action

I've been looking for a solution to this problem for a while, and no one seems to have come across a similar issue.
Basically I have multiple UITextViews that I use to detect addresses, urls, phone numbers, etc (anything that can be detected via UIDataDectorTypeAll) from some EKEvent.notes. I then add these UITextViews as subviews of a UIScrollView.
Now, for some reason or another, once the UITextView detects an address or a phone number and becomes an actionable target, it will randomly draw with a font 2x its specified font!
I've setup tests to just redraw my views if I tap. When the UITextView is added to the view initially, I can see in black the proper text. Then it does its detection deal and becomes a actionable target. Sometimes it stays the proper size, sometimes it draws at 2x font (but still in proper frame, thus it gets clipped).
It's very straight forward, but here's my code below. All variable are correct values, frame is correct, text is correct, everything is correct and about 50% of the time it draws correct. Its just that other 50% of the time it becomes (apparently) 2x font! Any help is greatly appreciated!
UITextView *locationTextView = [[UITextView alloc] init];
locationTextView.dataDetectorTypes = UIDataDetectorTypeAll;
locationTextView.text = location;
locationTextView.font = [UIFont fontWithName:#"AvenirNext-Regular" size:17];
locationTextView.editable = NO;
locationTextView.userInteractionEnabled = YES;
locationTextView.contentInset = UIEdgeInsetsMake(-8,-8,-8,-8);
locationTextView.frame =CGRectMake(kBufferLeft, daySize.height, kBufferDayViewTextWidth, locationSize.height);
[scrollView addSubview:locationTextView];
Correct: http://i.imgur.com/3pJ43kj.jpg
Incorrect: http://i.imgur.com/DLq4gco.jpg
(Not allowed to post images yet, sorry.)
Same exact code produced both effect. Thank you for your time.
Cheers!
EDIT: I went with TTTAttributedLabels to fix this issue.
github.com/mattt/TTTAttributedLabel
You can set font at <UITextField> delegate.
-(BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
locationTextView.font = [UIFont fontWithName:#"AvenirNext-Regular" size:17];
}
I had the same problem because I was using a custom line breaking (layoutManager:shouldBreakLineByWordBeforeCharacterAtIndex:). Had to disable that.

iOS - Issue adding my CATextLayer frame to a UIScrollView

I am trying to add my CATextLayer frame to a UIScrollView in order to get some scrolling. I have been trying to use the technique mentioned here (How can i make my CATextLayer object Scrollable like UITextView?) with no success.
Actually due to the (suggested) 3 lines I've added to my code my CATextLayer does not display anymore.
I have attached my code below and noted these 3 lines. Perhaps someone can help me troubleshoot this or even propose a better way to approach this :-)
// Create the scrool view (FIRST LINE ADDED)
UIScrollView* scrollLayer = [[UIScrollView alloc] initWithFrame:CGRectMake(0.0, 0.0, 500.0, 500.0)];
// Create the new layer object
boxLayer = [[CATextLayer alloc] init];
// Give it a size
[boxLayer setBounds:CGRectMake(0.0, 0.0, 500.0, 500.0)];
// Give it a location
[boxLayer setPosition:CGPointMake(300.0, 350.0)];
// Make half-transparent red the background color for the layer
UIColor *reddish = [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.1];
// Get CGColor object with the same color values
CGColorRef cgReddish = [reddish CGColor];
[boxLayer setBackgroundColor:cgReddish];
// Make it a sublayer on the view's layer
[self.view.layer addSublayer:boxLayer];
// Create string
NSString *text2 = #"The article was about employment.\nHe leafed through it in an instant.\nHis feeling of anxiety resurfaced and he closed the magazine.\n\n-Hm…, he breathed.\n\n-Have you been looking for work long?, asked the stranger at his side.\nThe article was about employment.\nHe leafed through it in an instant.\nHis feeling of anxiety resurfaced and he closed the magazine.\n\n-Hm…, he breathed.\n\n-Have you been looking for work long?, asked the stranger at his side.\nThe article was about employment.\nHe leafed through it in an instant.\nHis feeling of anxiety resurfaced and he closed the magazine.\n\n-Hm…, he breathed.\n\n-Have you been looking for work long?, asked the stranger at his side.";
// Set font type
[boxLayer setFont:#"MarkerFelt-Thin"];
// Set font size
[boxLayer setFontSize:20.0];
// Text is left justified
[boxLayer setAlignmentMode:kCAAlignmentLeft];
// Text is wrapped
boxLayer.wrapped = YES;
// Assign string to layer
[boxLayer setString:text2];
// Define the text size (SECOND LINE ADDED)
scrollLayer.contentSize = CGSizeMake(500, 1000);
// Set the text layer as a sub layer of the scroll view (THIRD LINE ADDED)
[scrollLayer.layer addSublayer:boxLayer];
Pursuant to our discussion on another discussion, I wonder if you should even be adding CATextLayer objects at all. You can add UILabel objects, for example. For example, let's add a series of UILabel objects to our scroll view:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if (!self.addedLabels)
{
[self addLabelsToScrollView:self.scrollView];
self.addedLabels = YES;
}
}
- (void)addLabelsToScrollView:(UIScrollView *)scrollView
{
NSArray *gettysburgAddress = #[
#"Four score and seven years ago our fathers brought forth on this continent a new nation, conceived in liberty, and dedicated to the proposition that all men are created equal.",
#"Now we are engaged in a great civil war, testing whether that nation, or any nation, so conceived and so dedicated, can long endure. We are met on a great battle-field of that war. We have come to dedicate a portion of that field, as a final resting place for those who here gave their lives that that nation might live. It is altogether fitting and proper that we should do this.",
#"But, in a larger sense, we can not dedicate, we can not consecrate, we can not hallow this ground. The brave men, living and dead, who struggled here, have consecrated it, far above our poor power to add or detract. The world will little note, nor long remember what we say here, but it can never forget what they did here. It is for us the living, rather, to be dedicated here to the unfinished work which they who fought here have thus far so nobly advanced. It is rather for us to be here dedicated to the great task remaining before us—that from these honored dead we take increased devotion to that cause for which they gave the last full measure of devotion—that we here highly resolve that these dead shall not have died in vain—that this nation, under God, shall have a new birth of freedom—and that government of the people, by the people, for the people, shall not perish from the earth."
];
CGFloat y = 0.0;
UIFont *font = [UIFont fontWithName:#"MarkerFelt-Thin" size:20.0];
CGSize maxSize = CGSizeMake(scrollView.frame.size.width, 10000.0);
for (NSString *line in gettysburgAddress)
{
CGSize labelSize = [line sizeWithFont:font constrainedToSize:maxSize lineBreakMode:NSLineBreakByWordWrapping];
CGRect labelFrame = CGRectMake(0.0, y, labelSize.width, labelSize.height);
UILabel *label = [[UILabel alloc] initWithFrame:labelFrame];
label.font = font;
label.text = line;
label.numberOfLines = 0;
[scrollView addSubview:label];
y += labelSize.height + 16.0;
}
scrollView.contentSize = CGSizeMake(scrollView.contentSize.width, y);
}
Or even easier, rather than adding UILabel objects to a UIScrollView, you could just use a UITextView:
- (void)updateTextView
{
NSArray *gettysburgAddress = #[
#"Four score and seven years ago our fathers brought forth on this continent a new nation, conceived in liberty, and dedicated to the proposition that all men are created equal.",
#"Now we are engaged in a great civil war, testing whether that nation, or any nation, so conceived and so dedicated, can long endure. We are met on a great battle-field of that war. We have come to dedicate a portion of that field, as a final resting place for those who here gave their lives that that nation might live. It is altogether fitting and proper that we should do this.",
#"But, in a larger sense, we can not dedicate, we can not consecrate, we can not hallow this ground. The brave men, living and dead, who struggled here, have consecrated it, far above our poor power to add or detract. The world will little note, nor long remember what we say here, but it can never forget what they did here. It is for us the living, rather, to be dedicated here to the unfinished work which they who fought here have thus far so nobly advanced. It is rather for us to be here dedicated to the great task remaining before us—that from these honored dead we take increased devotion to that cause for which they gave the last full measure of devotion—that we here highly resolve that these dead shall not have died in vain—that this nation, under God, shall have a new birth of freedom—and that government of the people, by the people, for the people, shall not perish from the earth."
];
NSString *text = [gettysburgAddress componentsJoinedByString:#"\n\n"];
self.textView.text = text;
self.textView.font = [UIFont fontWithName:#"MarkerFelt-Thin" size:20.0];
self.textView.editable = NO;
}
It all comes down to whether you really need to use a CATextLayer.

iOS UIScrollView performance

I'm trying to increase the scrolling performance of my UIScrollView. I have a lot of UIButtons on it (they could be hundreds): every button has a png image set as background.
If I try to load the entire scroll when it appears, it takes too much time. Searching on the web, I've found a way to optimize it (loading and unloading pages while scrolling), but there's a little pause in scrolling everytime I have to load a new page.
Do you have any advice to make it scroll smoothly?
Below you can find my code.
- (void)scrollViewDidScroll:(UIScrollView *)tmpScrollView {
CGPoint offset = tmpScrollView.contentOffset;
//322 is the height of 2*2 buttons (a page for me)
int currentPage=(int)(offset.y / 322.0f);
if(lastContentOffset>offset.y){
pageToRemove = currentPage+3;
pageToAdd = currentPage-3;
}
else{
pageToRemove = currentPage-3;
pageToAdd = currentPage+3;
}
//remove the buttons outside the range of the visible pages
if(pageToRemove>=0 && pageToRemove<=numberOfPages && currentPage<=numberOfPages){
for (UIView *view in scrollView.subviews)
{
if ([view isKindOfClass:[UIButton class]]){
if(lastContentOffset<offset.y && view.frame.origin.y<pageToRemove*322){
[view removeFromSuperview];
}
else if(lastContentOffset>offset.y && view.frame.origin.y>pageToRemove*322){
[view removeFromSuperview];
}
}
}
}
if(((lastContentOffset<offset.y && lastPageToAdd+1==pageToAdd) || (lastContentOffset>offset.y && lastPageToAdd-1==pageToAdd)) && pageToAdd>=0 && pageToAdd<=numberOfPages){
int tmpPage=0;
if((lastContentOffset<offset.y && lastPageToAdd+1==pageToAdd)){
tmpPage=pageToAdd-1;
}
else{
tmpPage=pageToAdd;
}
//the images are inside the application folder
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
for(int i=0;i<4;i++){
UIButton* addButton=[[UIButton alloc] init];
addButton.layer.cornerRadius=10.0;
if(i + (tmpPage*4)<[imagesCatalogList count]){
UIImage* image=[UIImage imageWithContentsOfFile:[NSString stringWithFormat: #"%#/%#",docDir,[imagesCatalogList objectAtIndex:i + (tmpPage*4)]]];
if(image.size.width>image.size.height){
image=[image scaleToSize:CGSizeMake(image.size.width/(image.size.height/200), 200.0)];
CGImageRef ref = CGImageCreateWithImageInRect(image.CGImage, CGRectMake((image.size.width-159.5)/2,(image.size.height-159.5)/2, 159.5, 159.5));
image = [UIImage imageWithCGImage:ref];
}
else if(image.size.width<image.size.height){
image=[image scaleToSize:CGSizeMake(200.0, image.size.height/(image.size.width/200))];
CGImageRef ref = CGImageCreateWithImageInRect(image.CGImage, CGRectMake((image.size.width-159.5)/2, (image.size.height-159.5)/2, 159.5, 159.5));
image = [UIImage imageWithCGImage:ref];
}
else{
image=[image scaleToSize:CGSizeMake(159.5, 159.5)];
}
[addButton setBackgroundImage:image forState:UIControlStateNormal];
image=nil;
addButton.frame=CGRectMake(width, height, 159.5, 159.5);
NSLog(#"width %i height %i", width, height);
addButton.tag=i + (tmpPage*4);
[addButton addTarget:self action:#selector(modifyImage:) forControlEvents:UIControlEventTouchUpInside];
[tmpScrollView addSubview:addButton];
addButton=nil;
photos++;
}
}
}
lastPageToAdd=pageToAdd;
lastContentOffset=offset.y;
}
Here's a few recommendations:
1) First, understand that scrollViewDidScroll: will get called continuously, as the user scrolls. Not just once per page. So, I would make sure that you have logic that ensures that the real work involved in your loading is only triggered once per page.
Typically, I will keep a class ivar like int lastPage. Then, as scrollViewDidScroll: is called, I calculate the new current page. Only if it differs from the ivar do I trigger loading. Of course, then you need to save the dynamically calculated index (currentPage in your code) in your ivar.
2) The other thing is that I try not to do all the intensive work in the scrollViewDidScroll: method. I only trigger it there.
So, for example, if you take most of the code you posted and put it in a method called loadAndReleasePages, then you could do this in the scrollViewDidScroll: method, which defers the execution until after scrollViewDidScroll: finishes:
- (void)scrollViewDidScroll:(UIScrollView *)tmpScrollView {
CGPoint offset = tmpScrollView.contentOffset;
//322 is the height of 2*2 buttons (a page for me)
int currentPage = (int)(offset.y / 322.0f);
if (currentPage != lastPage) {
lastPage = currentPage;
// we've changed pages, so load and release new content ...
// defer execution to keep scrolling responsive
[self performSelector: #selector(loadAndReleasePages) withObject: nil afterDelay:0];
}
}
This is code that I've used since early iOS versions, so you can certainly replace the performSelector: call with an asynchronous GCD method call, too. The point is not to do it inside the scroll view delegate callback.
3) Finally, you might want to experiment with slightly different algorithms for calculating when the scroll view has actually scrolled far enough that you want to load and release content. You currently use:
int currentPage=(int)(offset.y / 322.0f);
which will yield integer page numbers based on the way the / operator, and the float to int cast works. That may be fine. However, you might find that you want a slightly different algorithm, to trigger the loading at a slightly different point. For example, you might want to trigger the content load as the page has scrolled exactly 50% from one page to the next. Or you might want to trigger it only when you're almost completely off the first page (maybe 90%).
I believe that one scrolling intensive app I wrote actually did require me to tune the precise moment in the page scroll when I did the heavy resource loading. So, I used a slightly different rounding function to determine when the current page has changed.
You might play around with that, too.
Edit: after looking at your code a little more, I also see that the work you're doing is loading and scaling images. This is actually also a candidate for a background thread. You can load the UIImage from the filesystem, and do your scaling, on the background thread, and use GCD to finally set the button's background image (to the loaded image) and change its frame back on the UI thread.
UIImage is safe to use in background threads since iOS 4.0.
Don't touch a line of code until you've profiled. Xcode includes excellent tools for exactly this purpose.
First, in Xcode, make sure you are building to a real device, not the simulator
In Xcode, choose Profile from the Product menu
Once Instruments opens, choose the Core Animation instrument
In your app, scroll around in the scroll view you're looking to profile
You'll see the real time FPS at the top, and in the bottom, you'll see a breakdown of all function and method calls based on total time ran. Start drilling down the highest times until you hit methods in your own code. Hit Command + E to see the panel on the right, which will show you full stack traces for each function and method call you click on.
Now all you have to do is eliminate or optimize the calls to the most "expensive" functions and methods and verify your higher FPS.
That way you don't waste time optimizing blind, and potentially making changes that have no real effect on the performance.
My answer is really a more general approach to improving scroll view and table view performance. To address some of your particular concerns, I highly recommend watching this WWDC video on advanced scroll view use: https://developer.apple.com/videos/wwdc/2011/includes/advanced-scrollview-techniques.html#advanced-scrollview-techniques
The line that is likely killing your performance is:
addButton.layer.cornerRadius=10.0;
Why? Turns out the performance for cornerRadius is AWFUL! Take it out... guaranteed huge speedup.
Edit: This answer sums up what you should do quite clearly.
https://stackoverflow.com/a/6254531/537213
My most common solution is to rasterize the Views:
_backgroundView.layer.shouldRasterize = YES;
_backgroundView.layer.rasterizationScale = [[UIScreen mainScreen] scale];
But it works not in every situation.. Just try it

Resources