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.
Related
While reading an audio file (which can be big), I want to display a progress view on a main view controller subview (it is an UIView).
I wrote a method for that in my UIViewController:
- (void) displayFileReadingView
{
self.fileReadingView = [[UIView alloc] initWithFrame:CGRectMake(20., 100., 300., 120.)];
self.readingProgress = [[UIProgressView alloc] initWithFrame:CGRectMake(20., 90., 200., 8.)];
self.readingMessage = [[UILabel alloc] initWithFrame:CGRectMake(45., 10., 200., 20.)];
... elements parameter settings ...
[fileReadingView addSubview:self.readingMessage];
[fileReadingView addSubview:self.readingProgress];
[self.view addSubview: self.fileReadingView];
}
In the viewDidAppear methods, after all last initializations, I read an audio file (using AVURLAsset) and analyse the data to plot it.
When displayFileReadingView is called from viewDidLoad, or viewWillAppear, the view is displayed correctly. When it is called from viewDidAppear, as a first line, it is not displayed until several seconds after the end of the viewDidAppear execution. This is my first question:
• Why an UI method to allocate views, subviews and add them to the appropriate view, allow display or not depending of the calling method, since the displayFileReadingView method is not dependent of any initialization parameter from the main view managed by the owning UIViewController?
Here is the second problem. My UIProgressView is supposed to be updated every second, using a NSTimer scheduled to call updateReadingProgress
- (void) updateReadingProgress {
[self.readingProgress setProgress:myNewValue animated:YES];
[self.fileReadingView setNeedsDisplay];
}
But unfortunately, the NSTimer is not fired in time. If the allocation (using either ScheduledTimerWithTimerInterval... or TimerWithInterval... + adding in main loop (all modes tested) is made normally, it is never fired. If I put the same allocation in a dispatch_main_async() (or dispatch_after...) the timer is fired many seconds after for the first time, sometimes up to 25 seconds.
The result, depending on my code choices explained here: sometimes the view is not displayed, sometimes not. When it is, its UIProgressView subview is never updated, or updated dozen of seconds after all other calculations are done (after the owner view should be removed from display).
I also tried to replace NSTimer with CADisplayLink to fire updateReadingProgress method, using an "ordinary" call or "dispatched in main queue" included call, but in this CADisplayLink case it is never fired. Spent two days and nights in trying combinations, reading developer websites... no solution!
This is my second question:
• How can I be sure that a method updating the UI get called periodically without giant delay (some .2 or .5 second delay is not a problem?
[Using Xcode9 for iOS10 target, tested on Simulator and devices (iPhone/iPad)]
I get one problem is that,
This's showads function.
[self.mMainView addSubview:adViewController.view];
//...(my function to display ads)
This's hideads function.
//...(my function to hide ads)
[[AdViewController sharedAdViewController].view removeFromSuperview];
[self.mMainView setNeedsDisplay];
The mMainView is a UIView.
The AdViewController is a UIViewController.
The problems is that after calling showads fucntion, the my ads display on mMainView. But after calling hideads function, the my ads don't disappear, it still appear on mMainView.
Note: After calling hideads and make an interrupt then resume the app, the my ads will disappear.
So, i want to remove it, could you please explain a bit in more detail and show me how to fix it if you can pls ?
You don't need to call setNeedsDisplay when removing or adding subviews. The fact that it updates after an interrupt makes me suspect that you're not calling [[AdViewController sharedAdViewController].view removeFromSuperview]; on the main thread. What is the context of that code?
If it's not on the main thread, you can schedule on it:
dispatch_async(dispatch_get_main_queue(), ^{
[[AdViewController sharedAdViewController].view removeFromSuperview];
});
I use a dispatch_once NSObject to create data pointers. So all game asset pointers are made when the main viewcontroller appears. In order to play a game, the user taps a UIButton corresponding to a particular level on a UIViewController. Let me call it LevelSelectionController. When the game is over, the user will tap a label (SKLabel). And all actions and nodes will be removed.
[self removeAllActions];
[self removeAllChildren];
[self removeFromParent];
Moreover, an SKScene subclass for a particular level delegates the task of returning the user to LevelSelectionController to the viewcontroller presenting game SKView as follows.
- (void)closeScene {
SKView *spriteView = [[SKView alloc] init];
[spriteView presentScene:nil];
[self.navigationController popViewControllerAnimated:YES];
}
The only issue that I have is that the memory remains high when the user leaves the game scene (SKScene). The game requires a lot of assets. So when the game starts, the memory usage will jump to 200 MB. When the user returns to the original level selection view controller, the game simulator is still consuming 200 MB according to Activity Monitor. When the user enters a different level, the memory usage will jump by another 10 MB. So how can I release the memory for the last game once the user leaves SKScene?
I'm using ARC. The Xcode version is 5.1. The development target is iOS 7.1.
Thank you for your help.
-- Edit 1 --
I'm silly. I know what the problem is. When I close the scene, I'm creating a new SKView, which I then set it to nil to get of out the current scene. It works. But that should not be the way of doing it. Instead, I need to set the current SKView to a variable before presenting it. When I close the scene, I need to set that variable to nil. Hmm... I wasn't thinking.
-- Edit 2 --
There's little change when the current scene is presented with nil. Removing it from removeFromSuperview doesn't do much.
It has been noticed by a few people on SO that an SKScene gets deallocated when the containing SKView is removed from it's superview.
Have a look at these questions and their answers:
Deallocate SKScene after transition to another SKScene in SpriteKit
iOS 7 Sprite Kit freeing up memory
Also, try modifying the closeScene method like so:
- (void)closeScene {
SKView *spriteView = (SKView*)self.view;
[spriteView presentScene:nil];
[self.navigationController popViewControllerAnimated:YES];
}
Put NSLog() to SKScene's dealloc method to be sure that it's deallocating.
Also, resources may be not released just after reference count of your scene reaches 0. Due to internal optimizations assets may stay in memory until receiving Memory warning signal.
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];
Most of the time I'm doing projects with storyboard but now I need to mantain an old project that used xib files. My problem is this, I have a ViewController with a scroller \ UIButton \ UITextField on it. If I write something like: [scroller setHidden:YES]; inside ViewDidLoad the scroller will be hidden, but, if I'll put it inside
-(IBAction)checkMember:(id)sender{
[scroller setHidden:YES];
[self checkMemberLog];
Only when those are complete the setHidden will fire up.
How can I force it to fire up instantly? What am I missing?
Try calling checkMemberLog like this:
[self performSelector:#selector(checkMemberLog) withObject:nil afterDelay:0];
Even with a delay of 0, I think this will allow your scroller to be hidden because the selector is queued on the current thread’s run loop, and has to be dequeued before it will run. Even though this happens almost instantly, I'm guessing that an update of the display caused by the setHidden: message is queued first.
If you're more comfortable with storyboard maybe it's worth your time to start a new project and import all your old header/class files. It could save you some time and frustration in the end, depending on how much there is to bring over.