Animation issue on iOS 7 but not on iOS 8 - ios

I have an animation that works fine in iOS 8 but not in iOS 7. In fact, it seems that sometimes, parts of the block are animated and parts are not (or maybe for different animation durations). It's quite hard to explain this.
__block BTQMenuItemView *previousToClose = nil;
[UIView animateWithDuration:(animated ? kAnimationDuration : 0.f) animations:^{
[_menuItemViews enumerateObjectsUsingBlock:^(BTQMenuItemView *current, NSUInteger idx, BOOL *stop) {
BTQMenuItemView *previous = (idx > 0 ? _menuItemViews[idx-1] : nil);
if(previous.expanded && sender == previous) {
current.top = current.savedFrame.origin.y + sender.item.contentView.height + (sender.item.titleView ? sender.item.titleView.height - sender.height : 0) + sender.item.marginTopContent;
sender.height += sender.item.contentView.height;
sender.height += sender.item.titleView.height;
sender.item.contentView.top = (sender.item.titleView ? sender.item.titleView.bottom : sender.titleLabel.bottom) + sender.item.marginTopContent;
sender.actionView.height += (sender.item.titleView ? sender.item.titleView.height - sender.actionView.height : 0);
} else {
if(previous.expanded) {
previous.height = previous.savedFrame.size.height;
previous.actionView.height = previous.titleLabel.height;
previous.expanded = NO;
previousToClose = previous;
}
current.height = current.savedFrame.size.height;
current.top = previous.bottom;
}
}];
BTQMenuItemView *lastView = [_menuItemViews lastObject];
_bottomCacheView.height = self.superview.height - self.top - lastView.bottom - _edgeInsets.bottom;
_bottomCacheView.top = lastView.bottom;
} completion:^(BOOL finished) {
BTQMenuItemView *lastView = [_menuItemViews lastObject];
self.height = lastView.bottom;
}];
[previousToClose iconWillOpen:NO];

Related

UISlider is flickering

we are using UISlider in our project, we are using that older in order to play audio.for specific audio, when we are playing that, UISlider is flickering for first 13 seconds. for another audio, that UISlider is flickering for first 8 seconds. what can be the solution for this? Thanks in advance.
I have assigned right value to the slider.
- (void)setSliderValue
{
if(self.jwAudioPlayer == nil)
return;
if(self.slider && self.slider.maximumValue == 1.0)
{
self.slider.minimumValue = 0.0;
self.slider.maximumValue = self.jwAudioPlayer.duration;
self.slider.value = 0;
}
}
- (void)setPlayerCurrentPlayingTime
{
if(self.jwAudioPlayer == nil)
return;
if(self.slider.maximumValue <= 0 && self.jwAudioPlayer.duration > 0){
self.slider.maximumValue = self.jwAudioPlayer.duration;
}
CGFloat durationInSeconds = self.jwAudioPlayer.duration;
CGFloat currentTimeInSeconds = self.jwAudioPlayer.position;
if(currentTimeInSeconds >= durationInSeconds)
{
currentTimeInSeconds = durationInSeconds;
[self pause];
}
if(self.durationTime)
{
self.durationTime.text = [self timeFormatted:durationInSeconds];
}
if(self.currentPlayingTime)
{
self.currentPlayingTime.text = [self timeFormatted:currentTimeInSeconds];
}
if(self.slider && self.slider.tag == 0)
{
[self setSliderValue];
[self.slider setValue:currentTimeInSeconds animated:YES];
}
if(self.playerController)
[self.playerController enableDisableSkipImage:currentTimeInSeconds];

Mutate subviews async

I have a problem with adding and removing views in background.
I tried [self performSelectorInBackground:#selector(loadDataInScrollView) withObject:nil];
And
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
//Background Thread
dispatch_async(dispatch_get_main_queue(), ^(void){
//Run UI Updates
});
});
And everytime i get Collection <CALayerArray: 0x165a7e50> was mutated while being enumerated.'
Code:
- (void)loadDataInScrollView {
int thresholdStart = currentPage * numberOfElementsPerLine - (numberOfPreloadedPages / 2) * numberOfElementsPerLine;
int start = thresholdStart > 0 ? thresholdStart : 0;
int thresholdEnd = currentPage * numberOfElementsPerLine + numberOfVisibleElements + (numberOfPreloadedPages / 2) * numberOfElementsPerLine;
int end = thresholdEnd > numberOfElements ? numberOfElements : thresholdEnd;
for (int i = start; i < end; i++) {
[self createElementAtIndex:i];
// [self performSelectorInBackground:#selector(createElementAtIndex:) withObject:[NSNumber numberWithInt:i]];
for (int i = TAG_CONFLICT; i < start + TAG_CONFLICT; i++) {
[self removeElementAtIndex:i];
}
for (int i = end + TAG_CONFLICT; i < numberOfElements + TAG_CONFLICT; i++) {
[self removeElementAtIndex:i];
}
}
self.isLoaded = YES;
NSLog(#"start: %d, end: %d", start, end);
}
- (void)removeElementAtIndex:(int)index {
UIView *view = [base viewWithTag:index];
if (view != nil && ![view isKindOfClass:[UIImageView class]]) {
[view removeFromSuperview];
}
}
- (void)createElementAtIndex:(int)index {
if (![base viewWithTag:index+TAG_CONFLICT]) {
int x = leftRightPadding + index % numberOfElementsPerLine * (sizeElement.width + flexibleSpace);
int y = topBottomPadding + index / numberOfElementsPerLine * (sizeElement.height + flexibleSpace);
UIView *element = [[UIView alloc] initWithFrame:CGRectMake(x, y, sizeElement.width, sizeElement.height)];
[self.dataSource gallery:self view:element atIndex:index];
if (border) {
element.layer.borderWidth = 1.0;
element.layer.borderColor = [borderColor CGColor];
}
element.layer.masksToBounds = YES;
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(elementTapped:)];
tap.numberOfTapsRequired = 1;
[element addGestureRecognizer:tap];
[element setTag:index+TAG_CONFLICT];
[element setAlpha:0];
[base addSubview:element];
[UIView animateWithDuration:0.3 animations:^{
element.alpha = 1;
}];
}
}
#pragma mark - ScrollView delegates
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
initialOffset = scrollView.contentOffset.y;
currentPage = (initialOffset - topBottomPadding) / (sizeElement.height + topBottomPadding);
direction = initialOffset > base.contentOffset.y ? BOTTOM : TOP;
[self loadDataInScrollView]; // Here i've tried different ways to send in background
}
The error message tells you what the problem is and what to do. You cannot mutate a mutable array while you are enumerating that array (i.e. in the middle of a for-loop). You need to walk through the array and make a separate nonmutable list (array) of the things you want to change. After the loop is completely over, do another loop where you use that list to make the changes.
Also, you must never touch the interface in a background thread. You are calling loadDataInScrollView in the background. Thus loadDataInScrollView calls removeElementAtIndex in the background. Thus removeElementAtIndex calls removeFromSuperview in the background - that is totally illegal.
So you're going to have to completely rearchitect your approach here on those two grounds.

SpriteKit - FPS dropping after changing texture

I am trying to change a SKSpriteNode's texture while it is animated , but I faced with FPS dropping ! it goes down from 60 to 30 ! on both device and simulator ! the node is a parallel scrolling background , and here is the codes :
- (void)createBricksEdge {
brickEdges = [[SKSpriteNode alloc]initWithImageNamed:#"edge.png"];
brickEdges.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));;
brickEdges.name = #"edge";
[self addChild:brickEdges];
bEdge1 = [[SKSpriteNode alloc]initWithImageNamed:#"edge.png"];
bEdge1.position = CGPointMake(brickEdges.position.x,brickEdges.position.y+(brickEdges.size.height));
bEdge1.name = #"edge";
[self addChild:bEdge1];
}
moving background :
- (void)moveBg
{
[self enumerateChildNodesWithName:#"edge" usingBlock: ^(SKNode *node, BOOL *stop)
{
SKSpriteNode * bg = (SKSpriteNode *) node;
if (isSpeedBonus == YES) {
SPEED = 200;
} else {
switch (speedCriteria) {
case 0:
SPEED = 7.0;
break;
case 1:
SPEED = 11;
NSLog(#"FPS drops");
[bg runAction:[SKAction setTexture:[SKTexture textureWithImage:[UIImage imageNamed:#"edgeSpeed.png"]]]];
//I also tried : [bg setTexture:textre];
break;
.
.
.
.
.
}
}
bg.position = CGPointMake(bg.position.x , bg.position.y - SPEED);
if (bg.position.y <= -bg.size.width)
{ bg.position = CGPointMake(bg.position.x ,bg.position.y + bg.size.height*2); }} ];
}
Updating frames :
- (void)speedOMeter
{
int i = (int)[scoreLabel.text integerValue];
if (i <= 1000) {
speedCriteria = 0;
} else if (i <= 1500) {
speedCriteria = 1;
} else if (i <=2000) {
speedCriteria = 2;
} else if (i <= 2500) {
.
.
.
.
}
- (void)update:(CFTimeInterval)currentTime {
if (_lastUpdateTime)
{
_dt = currentTime - _lastUpdateTime;
}
else
{
_dt = 0;
}
_lastUpdateTime = currentTime;
[self moveBg]; [self speedOMeter];
}
You're bypassing the Sprite Kit caching mechanisms by creating the image first as a UIImage. This will load the image from disk, possibly every time:
[bg runAction:[SKAction setTexture:[SKTexture textureWithImage:[UIImage imageNamed:#"edgeSpeed.png"]]]];
Besides that you're overdoing it with the action. Try again with the simplified version that'll give you the same result by simply assigning the texture:
bg.texture = [SKTexture textureWithImageNamed:#"edgeSpeed.png"];

ios - How to calculate how much memory a UIView animation will take?

I am new to IOS development.
In my application I've got several images that I am animating.
The number of images can vary.
When running on ipad 2 the animations works fine.
When running on ipad 1, which a large number of images (20+) the app just crashes with a memory warning.
I would like to calculate the amount of memory an animation would take in advance.
Based on this figure I could calculate if to go through with my animation or skip
to the final status.
How can this be done?
Edited:
My current code:
- (void)remix
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:animationDuration];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:
#selector(animationDidStop:finished:context:)];
self.currentStatus = canvas_status_animating;
NSMutableArray *circles = [[NSMutableArray alloc] init];
for (CircleView* view in self.subviews)
{
if(![view isKindOfClass:[CircleView class]])
continue;
[circles addObject:view];
}
[self animatePosition:circles];
[UIView commitAnimations];
}
-(void) animatePosition:(NSArray*)circles
{
int maxWidth = self.bounds.size.width;
int maxHeight = self.bounds.size.height;
for (CircleView* view in circles)
{
int selectedX = 0;
int selectedY = 0;
if ((arc4random()%200)+1 > 100)
{
selectedX = (arc4random() % maxWidth) + 1;
selectedY = (arc4random() % 200) + 1;
selectedY = (selectedY > 100) ? (maxHeight - selectedY) : selectedY;
}
else
{
selectedX = (arc4random() % 200) + 1;
selectedX = (selectedX > 100) ? (maxWidth - selectedX) : selectedX;
selectedY = (arc4random() % maxHeight) + 1;
}
view.frame = CGRectMake(selectedX - view.frame.size.width / 2,
selectedY - view.frame.size.height / 2,
view.frame.size.width,
view.frame.size.height);
}
}
You can call this function before and after, and calc the difference.
-(double)availableMemory
{
vm_statistics_data_t vmStats;
mach_msg_type_number_t infoCount = HOST_VM_INFO_COUNT;
kern_return_t kernReturn = host_statistics(mach_host_self(), HOST_VM_INFO, (host_info_t)&vmStats, &infoCount);
if (kernReturn != KERN_SUCCESS)
{
return NSNotFound;
}
return ((vm_page_size * vmStats.free_count) / 1024.0) / 1024.0;
}

iOS Allowing one touch event at one given time

I am using iOS 5 to implement an app.
In this app I have 4 button, each button triggers an animation to unhide a UIView. However, if I press a button and then another, the view that appeared first should disappear and the view for the new button would appear.
I have this working so far. But if the user taps two buttons rapidly it will display the two views. How can I insure that only once touch event is processed?
The action for the button is something like:
- (void)buttonPressed:(id)sender
{
MenuButton *aButton = (MenuButton *)sender;
switch (aButton.tag) {
case 0:
if (displayingView && currentlyDisplayingView != picker1)
[self toggleView:currentlyDisplayingView atIndex:currentIndex];
[self toggleView:picker1 atIndex:aButton.tag];
currentlyDisplayingView = picker1;
currentIndex = aButton.tag;
break;
case 1:
if (displayingView && currentlyDisplayingView != picker2)
[self toggleView:currentlyDisplayingView atIndex:currentIndex];
[self toggleView:picker2 atIndex:aButton.tag];
currentlyDisplayingView = picker2;
currentIndex = aButton.tag;
break;
case 2:
if (displayingView && currentlyDisplayingView != picker3)
[self toggleView:currentlyDisplayingView atIndex:currentIndex];
[self toggleView:picker3 atIndex:aButton.tag];
currentlyDisplayingView = picker3;
currentIndex = aButton.tag;
break;
default:
break;
}
NSLog(#"Pressed %#",[buttonNames objectAtIndex:aButton.tag]);
}
And the animation code:
- (void)toggleView:(UIView *)picker
atIndex:(NSInteger)index
{
if (picker) {
picker.hidden = NO;
[UIView animateWithDuration:0.5
animations:^{
picker.alpha = abs(picker.alpha - 1);
CGRect rect = picker.frame;
if (rect.size.height == 0){
rect.size.height = 76;
} else if (rect.size.height == 76) {
rect.size.height = 0;
}
picker.frame = rect;
for (int i = index+1; i < [viewButtons count]; i++) {
UIButton *aButton = [viewButtons objectAtIndex:i];
CGRect frame = aButton.frame;
if (rect.size.height == 0){
frame.origin.y -= 75;
} else if (rect.size.height == 76) {
frame.origin.y += 75;
}
aButton.frame = frame;
}
}
completion:^(BOOL success){
if (picker.alpha == 0){
picker.hidden = YES;
} else if (picker.alpha == 1) {
picker.hidden = NO;
}
displayingView = !displayingView;
}];
}
}
Call beginIgnoringInteractionEvents before calling animateWithDuration, and call endIgnoring... in the completion handler.

Resources