I have heard that IOS will batch animations.
for example, I have about 4 methods thats do some updates (on main thread) and when they are done, I call a method to fade in a image of a check mark showing its complete. (see below) However it seems they all appear at the same time. How could I make them appear after each method call?
[mySubClass1 UpdateAllGeneralData_Single];
[self FadeImageGeneralInfo];
[mySubClass1 UpdateAllMeetingData_Single];
[self FadeImageMeetingList];
[mySubClass1 UpdateAllSpeakerData_Single];
[self FadeImageSpeakerList];
You could use a timer to delay each subsequent call to FadeImage____List by a certain amount, or you could use the UIView animateWithDuration:delay:options:animations:completion: class method to delay each subsequent animation.
If you choose option 2, the easiest way to implement it is probably to change FadeImage______List to accept a delay argument. Then, for example (if each animation took 0.5 seconds):
[mySubClass1 UpdateAllGeneralData_Single];
[self FadeImageGeneralInfoWithDelay:0];
[mySubClass1 UpdateAllMeetingData_Single];
[self FadeImageMeetingListWithDelay:0.5];
[mySubClass1 UpdateAllSpeakerData_Single];
[self FadeImageSpeakerListWithDelay:1.0];
Related
I'm using NSTimer to fire a method that scrolls UITableView
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.1
target:self
selector:#selector(scroller)
userInfo:nil
repeats:YES];
-(void)scroller
{
[self.row1TableView setContentOffset:CGPointMake(self.row1TableView.contentOffset.x, self.row1TableView.contentOffset.y - 50) animated:YES];
}
Problem is that the scrolling seems slow. It's closer to 1 second than .1 seconds in the interval.
What's the problem?
As far as I know, you can not change the default animation duration of setContentOffset:animated:. But what you can do is, setup a Core Animation display link (CADisplayLink - you can search for code samples on how to set up, but it is quite straight-forward. The class documentation should be a good place to start) and it will fire every frame, calling back a method you provide.
Inside that callback method, you can calculate how much you want to scroll your table view (how many points per frame), and call setContentOffset:animated: with the second parameter set to NO (immediate scrolling). You should implement some sort of easing to achieve better results.
Note: The reason for using CADisplayLink instead of NSTimer is, it is more reliable. It is what you would use in games before SpriteKit was available.
Addendum: This blog post has some sample code on how to setup the display link and the respective callback method.
Addendum 2: You can setup an instance variable to act as a "counter", and increment it by the ammount of time ellapsed since last frame, within each call of your callback (use properties duration and/or frameInterval). Once the counter reaches a critical value (that is, the animation has run for enough time) you can stop the display link update by calling the method:
-[CADisplayLink invalidate].
NSTimer that calls a selector on the current threads run loop. It may not be 100% precise time-wise as it attempts to dequeue the message from
the run loop and perform the selector.
On my button to start my game i have a few NSTimers to make things scroll, i want to add a delay to these timers, not too sure how to though.
any advice?
Here's the buttons with 1 of the NSTimers
-(IBAction)StartGame:(id)sender{
StartGame.hidden = YES;
backgroundMovement = [NSTimer scheduledTimerWithTimeInterval:0.06 target:self selector:#selector(backgroundMoving)
userInfo:nil repeats:YES];
}
Thank you
When you call [NSTimer scheduledTimerWithTimeInterval:...] the time interval is the delay. You will be called back after the time interval you specify.
Alternatively, call performSelector:withObject:afterDelay:.
However, note please that there are deep flaws in your proposed architecture. A repeating timer with a .06 interval is a terrible idea, and more than one is an atrocious idea. You need to rethink this completely. Consider using real animation, or a display link, or sprite kit. Or something. Anything, really.
If you'd like to add a delay only once, when starting, you could implement a - (void)startTimers method, where you create and start your timers, then do
[self performSelector:#selector(startTimers) withObject:nil afterDelay:1.0f];
in your IBAction.
^ Don't copy-paste the performSelector method, as I might have made a typo, since I don't have Xcode open to verify.
Options to do this:
1) call a method with performSelector using a delay. In that method you create the NSTimer
[self performSelector:#selector(createTimer_backgroundMovement) withObject:nil
afterDelay:0.1];
2) if the delay is dependent of an action you can use this information in a flag and that flag can be used in the backgroundMoving method:
-(void) backgroundMoving {
if (!blAction_whichEnablesTimer) {
return;
}
// your backgroundMoving timer code
}
3) consider moving views with eg. block animations. Using block animations you can use delay also. Please see doc: https://developer.apple.com/library/ios/documentation/windowsviews/conceptual/viewpg_iphoneos/animatingviews/animatingviews.html
I have encountered a delay/pause that I was not expecting and the reason so far has me scratching my head. I have a simple game setup where the UIViewController shows a number of UIButtons [PLAY GAME] [VIEW SCORES] etc. which in turn present a different SKScene
My problem is that when I try and set the visibility of these buttons to visible (perviously set to hidden in viewDidLoad) from the UIViewController they take about 5 seconds to actually show.
#implementation ViewController
- (void)presentTitleScene {
// SHOW BUTTONS
[[self logoLabel] setHidden:NO];
[[self gameButton] setHidden:NO];
[[self scoreButton] setHidden:NO];
[[self creditsButton] setHidden:NO];
// PRESENT SCENE
SKScene *titleScene = [TitleScene sceneWithSize:[[self spriteKitView] bounds].size];
[titleScene setName:#"TITLE_SCENE"];
[titleScene setScaleMode:SKSceneScaleModeAspectFill];
[(SKView *)[self view] presentScene:titleScene];
[self setCurrentScene:titleScene];
}
#end
What happens is all the code runs, the SKScene presents correctly, then after about 5-6 seconds the buttons appear? Is there anything I can do about this (force an update) or is it just a case of design it out or live with it?
This happens on both the simulator and the device.
EDIT:
Looking at the output log you can clearly see that after calling preloadTextureAtlases:withCompletionHandler: that execution jumps to another thread. The method preloadTextureAtlases:withCompletionHandler: is called on the main thread and its supposed to preload the textureAtlas(s) on a background thread, but I was under the impression that the completionHandler would call back onto the main thread, is this assumption right or am I wrong?
EDIT_002:
Moved to answer below.
With regards to preloadTextureAtlases:withCompletionHandler: the completionHandler gets called on a background thread, I would assume the same one that was used to preload the atlases. The problem that I was having was that I was using the completion handler to send an NSNotification to my viewController saying "the assets have loaded, start the game" The issue with this is that "Notifications are delivered in the same thread that they are sent from" so my game also started in the background thread. As a consequence the code that set the UIButtons to visible was also running on that same background thread, hence the delay in them reacting to either being made visible or hidden.
How would you add a delay between certain method being called?
This is my code that I want to only trigger 30 times per second:
- (void) scrollViewDidScroll: (UIScrollView*)scrollView {
[self performSelector:#selector(needsDisplay) withObject:nil afterDelay:0.033];
}
- (void) needsDisplay {
[captureView setNeedsDisplay];
}
If I leave it like this, it only gets called after the user stopped scrolling.
What I want to do is call the method when the user is scrolling, but with a delay of 33 milliseconds between each call.
There are different delegate methods which will call on different occausion. This method will only call when user finish scrolling. So you can perform some task if you want to. If you want to do some thing while scrolling or before scrolling you can use different delegate method. Select one of the below depending on your functionality.
– scrollViewDidScroll:
– scrollViewWillBeginDragging:
– scrollViewWillEndDragging:withVelocity:targetContentOffset:
– scrollViewDidEndDragging:willDecelerate:
– scrollViewShouldScrollToTop:
– scrollViewDidScrollToTop:
– scrollViewWillBeginDecelerating:
– scrollViewDidEndDecelerating:
For detail description upon these delegates please follow this link.
https://developer.apple.com/library/ios/documentation/uikit/reference/uiscrollviewdelegate_protocol/Reference/UIScrollViewDelegate.html
Delegate should call when it should be... other wise you gonna cause some glitch.
Since I couldn't find a solution, and other scroll view delegate methods weren't good, I did it by limiting based on scroll view's content offset, as suggested in a comment. (Ex: only calling it if the user scrolled more then 3 pixels).
This is a decent solution, since I doubt someone can scroll more then 90 pixels per second and STILL read the text in between those 90 pixels.
if (ABS(self.oldOffset.y - webView.scrollView.contentOffset.y) > 3) {
[captureView setNeedsDisplay];
}
The delegate method will get called, you can't setup a limit in that. I think a good option for you is to add the operations in NSOperationQueue, and since you are calling the same selector again and again, make sure you only keep a maximum of 30 operations in the queue at the same time. NSOperationQueue Class Reference.
One cannot have the control over the calling of the delegate methods, and also there is no parameter to set how often delegate method should call or to set the sensitivity of the scrollview,
U cannot control it.
Only thing remaining is to ignoring the call (return at the beginning of the function call if you do not need that) or else process the call..
I have an app that launches to a tab bar controller. When the app either starts up or returns from the background it checks a server for updates to its data. If updates are available, it can take several seconds to get the data and update it.
I would like to simply present an overlay view saying to the user that the app's data is updating and to please wait for a few seconds. The way that I am trying to do this is as follows: in my class that takes care of the updates I have:
AppDelegate *delegate = [UIApplication sharedApplication].delegate;
[delegate.tabBarController.selectedViewController.view addSubview:updatingDataView];
[self runUpdateMethods];
The problem is that the updatingDataView appears on screen only after the update methods have completed. How can I get it to appear before the updating methods start?
It looks like you are running [self runUpdateMethods]; on the main thread (This will block your UI from updating). You would want to run this on a background thread. Something like:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),
^{
[self runUpdateMethods];
});
or
[self performSelectorInBackground:#selector(runUpdateMethods) withObject:nil];
Update
Since you want to do something after [self runUpdateMethods]; completes you would want to do something like this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),
^{
[self runUpdateMethods];
dispatch_async(dispatch_get_main_queue(), ^{
[self doSomethingAfterUpdate];
});
});
Doing it this way would still give you the ability to know when runUpdateMethods returns and will not hang the UI.
Your update method (the connection) is probably being executed on the Main Thread, what blocks UI updates. You should use async methods (gcd, NSThread, NSOperationQueue, etc) to run your update.
You need to empty into your run loop so that the views you've added will get drawn on the screen. Drawing on iOs is not done real time. You basically set up your drawing and exit your method, and the run loop actually draws it. So what you need to do is delay the execution of runUpdateMethods until after you've exited your routine. Try instead:
[self performSelector:#selector(runUpdateMethods) withObject:nil afterDelay:0.0];