Initial particles from CAEmitterLayer don't start at emitterPosition - ios

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.

Related

Updating UILabel text too often

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 :)

Changing UICollectionView cell image at regular interval

I am developing an IOS 10.x application using UICollectionView and would like to change the image of specific cells, at regular interval.
The code below shows the current implementation. Even though it should change the cell background image every half of second, it changes the images immediately disregarding the NSThread SleepAt interval of 0.5 seconds.
I suspect something about the main thread handling or the ReloadItem method but hasn't reached a clear conclusion. Any insight is very welcome! Thank you.
NSNumber* originalCardSelected;
int position;
for (int i = 0; i < [opponentOriginalCardArray count]; i++) {
originalCardSelected = [opponentOriginalCardArray objectAtIndex:i];
position = [self convertLevelDataPositionToCellViewPosition:[originalCardSelected intValue]];
NSMutableArray *tmpBackgroundAssets = [self getNewAssetBackgroundBasedOnBackgroundType:playbackBackground Index:position];
self.backgroundAssets = tmpBackgroundAssets;
dispatch_async(dispatch_get_main_queue(), ^{
[collectionViewRoot reloadItemsAtIndexPaths:[collectionViewRoot indexPathsForVisibleItems]];
});
[NSThread sleepForTimeInterval:0.5];
}
You should use performBatchUpdates(_:completion:) methods and add your for loop inside it.
Remember to not keep strong references inside the block to avoid retain cycles.
EDIT:
and in the completion block, you can check if finished and add your NSThread methods

UIimageView masks into a diamond

Running into a super weird bug in my iOS application I cannot figure out.
I load a UIImageView for a user in my iOS application and then round it into a circle.
- (void)viewDidLoad {
[super viewDidLoad];
self.profileImage.file = [[self.profileObject objectForKey:#"UserID"] valueForKey:#"ProfilePhoto"];
[self.profileImage loadInBackground:^(UIImage *image, NSError *error) {
self.profileImage.image = image;
self.profileImage.layer.cornerRadius = self.profileImage.frame.size.width / 2;
self.profileImage.layer.masksToBounds = YES;
self.profileImage.contentMode = UIViewContentModeScaleAspectFill;
}];
}
This runs perfectly, until I dismiss that view and go back into it a few moments later.
- (void)dismissView {
[self dismissViewControllerAnimated:YES completion:nil];
self.profileImage.image = nil;
}
When I go back into the view, the image rounds from the point it was already rounded prior. Which means it then turns into somewhat of a diamond. See here: http://cl.ly/image/3p1P0M0M1d2H
Any ideas on why this would be happenig?
Workaround
I found that if I load the image in viewDidLoad and round it in viewDidAppear it works just fine. But that seems to "hacky" and doesn't load everything at the same time properly.
Any ideas on what I should try?
The problem is that you're loading the image using sone background threading technique, and the first time you do so, it has to presumably fetch the image from somewhere, the second time it presumably has a cached version so it can run the completion block immediately.
Why should this matter?
At viewDidLoad, under Autolayout, your image view's frame will be zero, yet you're using it to round the corners.
On the first run, the delay in loading the image is enough for the view to have performed a layout pass, so it can round properly. On the second run, it hasn't (because the image is cached), so it can't.
The solution, as you've already discovered, is to set the corner radius when layout has happened - either in viewDidLayoutSubviews or viewDidAppear. Setting the radius can be totally separate to loading the image, and isn't "hacky" at all.
A better solution would be to write an image view subclass that performed it's own corner rounding on layoutSubviews. It's not really the view controller's job to do that rounding.
try changing
self.profileImage.layer.cornerRadius = self.profileImage.frame.size.width / 2;
to
self.profileImage.layer.cornerRadius = self.profileImage.bounds.size.width / 2;
in general it is always better to work internally with bounds rather than frame rect, because this is the internal perspective. self.bounds will reflect autoresizing, auto layout, rotation, scaling and other geometry issues which self.frame will not. Good luck

CALayer layer not autoreleased

I've got a question regarding the following code:
#interface ViewController ()
#property (nonatomic, weak) CALayer *someLayer;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.someLayer = [CALayer layer];
self.someLayer.frame = CGRectMake(10.0, 10.0, 100.0, 100.0);
self.someLayer.backgroundColor = [UIColor yellowColor].CGColor;
[self.view.layer addSublayer:self.someLayer];
}
#end
From my understanding [CALayer layer] should return an autoreleased value, which should live as long as the method call is in progress. It is then referenced by the weak property someLayer.
Since the layer is later retained by the view's layer everything should be fine and the layer should appear.
What it does, but only on devices < iPhone 5S. At least when I run the code in the simulators.
On newer devices the layer won't be displayed.
Changing the property attribute to strong solves the problem, but I'd like to know why the layer gets released immediately.
Do you have any hints how I can debug this behaviour to see what has changed?
Thanks!
Update:
Using self.someLayer = [CALayer [alloc]] init] gives an appropriate warning
which I understand because when using alloc the value is owned by the caller. Since the owner does not retain the value there's no strong reference.
An convenience initializer like layer should autorelease an retained value, which should be accessible until the autorelease pool is drained. Also there's no warning by Xcode in this case.
Update #2:
It gets more interesting...
I tested the code on an iPhone 6 with iOS 8 and on an iPhone 4 with iOS 6.
The layer won't be displayed on neither of them...
BUT
if I set a breakpoint at the line of the creation and step over it, the layer will be visible on simulators < iPhone 5S and on the devices.
How could I check what's going on under the hood?
Update #3 and some more explanation:
There is a great article on Big Nerd Ranch about this behaviour ARC Gotcha - Unexpectedly Short Lifetimes
A look at the disassembly for the code above shows how ARC inserts _objc_retainAutoreleasedReturnValue
From the article:
you can see ARC's "avoid the autorelease pool" optimization is being used: if a method returns an autoreleased object, and the caller doesn't otherwise need to hang on to it, ARC can avoid the trip to the autorelease pool. The method will return a retained object and objc_retainAutoreleasedReturnValue will dispose of it.
So, to avoid the problem, either declare a strong property (as I mentioned above) or have a look at #graver's answer.
Since your someLayer property is weak there's nothing to hold a strong reference to your layer. You should change your code like this:
...
CALayer *layer = [CALayer layer]; // by default layer this is __strong, so layer holds a strong reference until the end of the scope
[self.view.layer addSublayer:layer]; // Now self.view.layer retains the layer
self.somelayer = layer; // weak reference it
self.someLayer.frame = CGRectMake(10.0, 10.0, 100.0, 100.0);
self.someLayer.backgroundColor = [UIColor yellowColor].CGColor;
// In the end of the scope layer would be released, but it's also retained by self.view.layer and weak referenced by self.someLayer
...
#graver 's code works for me on a real iPhone 6 device, which means layer is showed, you should always rely on real devices instead of simulators. You can refer to this question and xcode debugger hide issue with weak proper, it seems that the debugger would hold a strong refrence to the autorelease object, so it is kept and it is showed.
You can always test self.somelayer by logging it out.
self.someLayer = [CALayer layer];
NSLog(#"someLayer is %#",_someLayer);
It should log as Null, but on some simulators it would has value, which shouldn't happen.

MKOverlayView performance

I am adding about 3000 MKOverlays to my map, and as you can imagine, it takes a while, up to about eight seconds sometimes. I'm looking for a way to use threading to improve performance, so the user can move the map around while overlays are being added. Preferably, the overlays would be added sequentially, starting with the just the ones within the map's region. I have tried something along these lines with GCD:
- (MKOverlayView*)mapView:(MKMapView*)mapView viewForOverlay:(id)overlay {
__block MKPolylineView* polyLineView;
//do the heavy lifting (I presume this is the heavy lifting part, but
// because this code doesn't compile, I can't actually *test* it)
// on a background thread
dispatch_async(backgroundQueue, ^ {
polyLineView = [[[MKPolylineView alloc] initWithPolyline:overlay] autorelease];
[polyLineView setLineWidth:11.0];
//if the title is "1", I want a blue line, otherwise red
if([((LocationAnnotation*)overlay).title intValue]) {
[polyLineView setStrokeColor:[UIColor blueColor]];
} else {
[polyLineView setStrokeColor:[UIColor redColor]];
}
//return the overlay on the main thread
dispatch_async(dispatch_get_main_queue(), ^(MKOverlayView* polyLineView){
return polyLineView;
});
});
}
But because GCD blocks are defined with void parameter and return types, this code doesn't work- I get an incompatible pointer type error on the return line. Is there something I am missing here, or another way to thread this? Or perhaps an entirely different way to improve the performance of the overlay-adding process? I appreciate any and all help!
Edit:
I have found that the problem is not where I actually add the overlays here:
for(int idx = 1; idx < sizeOverlayLat; idx++) {
CLLocationCoordinate2D coords[2];
coords[0].latitude = [[overlayLat objectAtIndex:(idx - 1)] doubleValue];
coords[0].longitude = [[overlayLong objectAtIndex:(idx - 1)] doubleValue];
coords[1].latitude = [[overlayLat objectAtIndex:idx] doubleValue];
coords[1].longitude = [[overlayLong objectAtIndex:idx] doubleValue];
MKPolyline* line = [MKPolyline polylineWithCoordinates:coords count:2];
[line setTitle:[overlayColors objectAtIndex:idx]];
[mapViewGlobal addOverlay:line];
}
Adding all 3000 takes maybe 100ms here. The part that takes a long time (I assume) is where I actually create the overlays, in the first method I showed.
There is a little gap between what you want and what the compiler can do. When you are calling dispatch_async, you are actually telling the CPU "here, have this chunk of code, and run it whenever you feel like it, not now, not blocking my user interface thread". But, your method has to return now. There is simply no way for you to create anything in a background thread, because you are going to have to wait for it anyway before mapView:viewForOverlay: returns, since it has to return something.
This method is not the place to use GCD or any background code. If your problem is the addition of a big number of overlays at once, I would split all the overlays into chunks of say 100 and add them to the map with a delay of 100ms between each batch.

Resources