Help with flickering when reloading an openGL scene - objective-c

I'm starting to feel bad for asking so many questions and not being able to answer anyone's, but as soon as I find some that I can, I will! Confession out of the way...
Besically I have a gameLoop that runs everything in a game that I'm making and everything works fine in the menu and then in the game, but when I quit the game and then reload the menu screen the screen flickers.
Here is my game loop, I hope it provides some insight.
//A game loop that is triggered by a timer with intervals of 1/60 seconds
- (void)gameLoop
{
// we use our own autorelease pool so that we can control when garbage gets collected
NSAutoreleasePool * apool = [[NSAutoreleasePool alloc] init];
thisFrameStartTime = [levelStartDate timeIntervalSinceNow];
deltaTime = lastFrameStartTime - thisFrameStartTime;
lastFrameStartTime = thisFrameStartTime;
// add any queued scene objects
if ([objectsToAdd count] > 0)
{
[sceneObjects addObjectsFromArray:objectsToAdd];
[objectsToAdd removeAllObjects];
}
// update our model
[self updateModel];
// send our objects to the renderer
[self renderScene];
// remove any objects that need removal
if ([objectsToRemove count] > 0)
{
[sceneObjects removeObjectsInArray:objectsToRemove];
[objectsToRemove removeAllObjects];
}
[apool release];
if (needToLoadScene)
{
[sceneObjects removeAllObjects];
[self loadScene];
}
if (needToEndScene)
{
[sceneObjects removeAllObjects];
[self stopAnimation];
//We'll need to add unloading sounds later on
[inputController endScene];
[self renderScene];
needToEndScene = NO;
}
}
The only other thing that I should add is that the view is controlled by a class MusicAndViewController which I use as a view and then add subsequent views to. The menu and game views are actually the same openGL view but I render a clean view before switching to the other view

Anyone who has a similar problem to what I have had what solved it for me is this.
When setting a timer I had to override the setter so that it invalidated and then set the timer. On my gameScene I hadn't done this so when I make the timer = nil it continues to fire (as it hadn't been invalidated before setting it to nil, and was still running) and then cause a flickering on the next loaded scene (the menu)

Related

shouldPerformSegueWithIdentifier always executed first

I have a function called "openButtonPressed" which gets executed when a button on the ui gets pressed. Now I would like to show a loading view at first and then execute the segue. For some reason, the segue always gets called first.
Does somebody have a clue?
Thank you!
- (void)openButtonPressed:(id)sender
{
NSDebug(#"Open Button");
[self showLoadingView];
static NSString* segueToContinue = kSegueToContinue;
if ([self shouldPerformSegueWithIdentifier:segueToContinue sender:self]) {
[self performSegueWithIdentifier:segueToContinue sender:self];
}
}
- (void)showLoadingView
{
if(!self.loadingView) {
self.loadingView = [[LoadingView alloc] init];
[self.view addSubview:self.loadingView];
}
[self.loadingView show];
}
It looks like LoadingView is being initialized without a size.
It also may be executing the segue very quickly and you don't have any time to see the loading view.
You can set a breakpoint at the performSegue call and use the view debugger in Xcode to confirm if the view was loaded and what size the view was initialized to.
Maybe the view is not in the right place or in the right size. But even if you fix that you need to add a delay so the user can see the loading... I replicated your scenario here and after giving the view a initWithFrame it appears, but cannot be seen because is too fast...
If you want to show the loading for a period of time before the performSegue, you could use the performSelector:withObject:afterDelay.
If you need to wait an action from the loadingView, create a completionHandler in the loadingView initializer...
The hints below were very helpful to me and totally a better solution but I ended up using [CATransaction flush]; to force the refresh.
- (void)openButtonPressed:(id)sender
{
NSDebug(#"Open Button");
[self showLoadingView];
[CATransaction flush];
static NSString* segueToContinue = kSegueToContinue;
if ([self shouldPerformSegueWithIdentifier:segueToContinue sender:self]) {
[self performSegueWithIdentifier:segueToContinue sender:self];
}
}

UISwitch does sometimes not send UIControlEvents

I have a model object which includes a boolean flag. I want to show the value of the flag using a UISwitch. The value can be changed in two ways:
First by the user by toggling the switch. I register the UIControlEventTouchUpInside for that. I also tried UIControlEventValueChanged – it has the exact same effect.
Second by some external state change. I use a timer to check for that state change and set the on property of the switch accordingly.
However, setting the value of the switch in the timer method has the effect that the touchUpInside action is sometimes not triggered even though the user has touched the switch.
So I face the following problem: If I set the switch state in the timer when the state changes externally, I loose some state changes from the user. If I don't use the timer I get all state changes from the user. However, I miss all the external state changes, then.
Now I have run out of ideas. How can I achieve what I want, getting both types of state changes in the model and reflected them correctly in the switch view?
Here is a minimal example that shows the problem. I have replaced the model object by a simple boolean flag, and in the timer I don't change the flag at all, I just call setOn:animated:. I count the invocations of the action method. Like that I can easily find out how many touches were missed:
#import "BPAppDelegate.h"
#import "BPViewController.h"
#implementation BPAppDelegate {
NSTimer *repeatingTimer;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions: (NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
BPViewController *viewController = [[BPViewController alloc] init];
self.window.rootViewController = viewController;
[self.window makeKeyAndVisible];
[self startTimer];
return YES;
}
- (void) startTimer {
repeatingTimer = [NSTimer scheduledTimerWithTimeInterval: 0.2
target: self.window.rootViewController
selector: #selector(timerFired:)
userInfo: nil
repeats: YES];
}
#end
#import "BPViewController.h"
#implementation BPViewController {
UISwitch *uiSwitch;
BOOL value;
int count;
}
- (void)viewDidLoad
{
[super viewDidLoad];
value = true;
uiSwitch = [[UISwitch alloc] init];
uiSwitch.on = value;
[uiSwitch addTarget:self action:#selector(touchUpInside:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:uiSwitch];
}
- (void)touchUpInside: (UISwitch *)sender {
count++;
value = !value;
NSLog(#"touchUpInside: value: %d, switch: %d, count: %d", value, sender.isOn, count);
}
- (void) timerFired: (NSTimer*) theTimer {
NSLog(#"timerFired: value: %d, switch: %d, count: %d", value, uiSwitch.isOn, count);
// set the value according to some external state. For the example just leave it.
[uiSwitch setOn:value animated:false];
}
#end
Use UIControlEventValueChanged to determine when the switch has been toggled (programmatically or by the user). Don't use touchUpInside. Also touchUpInside doesn't work well for when the user drags the UISwitch.
Also, don't replicate a property (e.g. your value property) that is already maintained by the UISwitch (i.e. the "on" property)...its redundant and will just get you into trouble
EDIT: The following code does exactly what you want, if not exactly the way you want it. If insures that both timer changes and touch changes to the control receive an action method. The method is only going to be sent once per change, which is hopefully what you want.
Note the switch to UIControlEventValueChanged.
[sw addTarget:self action:#selector(touched:) forControlEvents:UIControlEventValueChanged];
- (void) timerFired: (NSTimer*) theTimer
{
// set the value according to some external state. For the example just leave it.
BOOL oldOn = sw.on;
BOOL newOn = YES;
[sw setOn:newOn];
if(newOn != oldOn) {
for(id target in [sw allTargets]) {
for(NSString *action in [sw actionsForTarget:target forControlEvent:UIControlEventValueChanged]) {
[target performSelector:NSSelectorFromString(action) withObject:self];
}
}
}
}
EDIT2:
I [original poster] just saw the new code in your answer and tried it out. I am not sure it is doing exactly what I want. However, I am not sure if I understand it correctly. I don't want to do exactly the same thing in both cases.
Q: When the user touches the switch I want to toggle the boolean value in the model. I not necessarily need to change the switch programmatically because it is done automatically by touching it anyway. I want to count the touches to make sure none are lost.
A: Well, that's not really possible. However, you can certainly add a counter to the action method and see if the taps you make equal the counter. When the user taps the switch, then "sender == theSwitch". If you send the action method otherwise, you can use a different sender (to differentiate them).
If I get the timer event in my minimal example I just want to leave the boolean value like it currently is. However, I need to set the switch state programmatically so that the view reflects the correct model state. – Bernhard Pieber 3 mins ago
Thats fine - but you keep saying you want the action method called. This code does that. If you misstated that, and you don't want the action method called, then delete the "if" block.
- (void)insureValueIs:(BOOL)val
{
BOOL oldVal = sw.on;
if(val != oldVal) {
for(id target in [sw allTargets]) {
for(NSString *action in [sw actionsForTarget:target forControlEvent:UIControlEventValueChanged]) {
[target performSelector:NSSelectorFromString(action) withObject:self];
}
}
}
}
Of course, I do want to achieve the same thing: That the model state is correctly reflected in the view. Does that make sense?
Frankly, you have not done a good job in describing EXACTLY what you want, and I keep responding to your comments, but even now I am still not totally clear. I believe you have all the information you need here to do whatever it is you want.
Your timer is firing so often it probably prevents the switch ever finishing the animated transition to the alternative value, so it never sends the valueChanged event message. You are setting the value to YES every 0.2 seconds which is faster, I think, than the duration of the animation that occurs when the user taps the switch.
My original code works as expected in iOS 7. So it seems to have been a bug in iOS 6.

Adding an activity indicator in another method to avoid hanging

I have implemented Reachability.h from Apple into my demo app. The problem is that I noticed that my app stalls while checking connection.
So, I added an Activity Indicator (From MBProgressHUD) . But the indicator does not animate. It stalls with the application too.
So, I thought of putting the activity indicator inside another thread different than the main thread but still it is not animating.
Note: I'm not very experienced
UPDATE: Also, I have tried the native Activity Indicator with no luck.
- (void)anotherThread
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
HUD = [[MBProgressHUD alloc] initWithView:self.navigationController.view];
[self.navigationController.view addSubview:HUD];
// Set determinate mode
HUD.mode = MBProgressHUDModeDeterminate;
HUD.delegate = self;
HUD.labelText = #"loading";
// myProgressTask uses the HUD instance to update progress
[HUD showWhileExecuting:#selector(crazyCounter) onTarget:self withObject:nil animated:YES];
[pool release];
}
UI code should be kept in main thread. So instead of putting the activity indicator into another thread, you may want to use GCD (grand central dispatch) to throw your checking connection code in another thread. When it finishes, you can then remove or hide your activity indicator.
PS. I'm not quite sure what MBProgressHUD does, but you do want to make sure you have something like [activityIndicator startAnimating]. At least for normal activity indicators, you need to manually turn it on.

How to repaint NSTableView immediately after reloadData

I have added a NSTableView and set the delegate and datasource correctly.
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
_items = [[NSMutableArray alloc] init];
[_items addObject:#"ready"];
[self.mainTableView reloadData];
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return [self.items count];
}
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
NSTableCellView *result = [tableView makeViewWithIdentifier:tableColumn.identifier owner:self];
result.textField.stringValue = [self.items objectAtIndex:row];
return result;
}
There is also a NSButton connected with the following code:
- (IBAction)buttonPressed:(id)sender {
for (int i = 1; i <= 10; i++) {
NSString *item = [NSString stringWithFormat:#"%d", i];
[_items replaceObjectAtIndex:0 withObject:item];
[self.mainTableView reloadData];
[NSThread sleepForTimeInterval:0.2f];
}
}
The program ran correctly and shown "ready" in the table view, but when I clicked the button, the content of the table view didn't change immediately. It changed after 2 second, and just show the last number - 10.
What I want is change the content of NSTableViw from 1 to 10 but not directly get the final number. How can I make it repaint immediately after reloadData?
[NSThread sleepForTimeInterval:0.2f];
You're sleeping the thread and wondering why things seem to be delayed?!
[_items replaceObjectAtIndex:0 withObject:item];
You're replacing every item in the array and wondering why only the last item is displayed?!
You shouldn't be sleeping the main thread at all. Remove that line; sending reloadData will make the table view redisplay itself as soon as possible.
To fill up the array, do this:
for (int i = 1; i <= 10; i++) {
NSString *item = [NSString stringWithFormat:#"%d", i];
[_items addObject:item];
}
[self.mainTableView reloadData];
Notice that I've moved the reloadData outside of the loop. You shouldn't send that until you've finished updating the data and are ready for the table view to redraw.
How can I make it repaint immediately after reloadData?
By returning after reloadData. There is no other way. You cannot cause the table view to redraw while your code is still running. Even if you used another thread, it wouldn't work the way you're envisioning.
If you want to modify the table over time, you can use an NSTimer to modify the data periodically, and then call reloadData (or better yet, reloadRowsAtIndexPaths:withRowAnimation:) after each change.
The user interface will never draw in the middle of your code. You have to return to let it update.
It is not possible for the UI to update while your code runs on the main thread. There are several approaches to providing UI updates during processing:
Do some work, schedule to do some more work with a timer or performSelector:withObject:afterDelay:, return so that the UI can update. This is what I described above.
Break your work into small units and schedule them using an NSOperationQueue or GCD queue. If you run these on the main queue, this is effectively the same as the first option; you're just letting the OS do the scheduling.
Run your processing on a background thread and update the data asynchronously. Periodically update the UI with a timer. This is good for simple progress meters, but it's hard to synchronize if you want the UI updates to be at specific points. This introduces all of the headaches of multi-threaded code, such as locking and atomicy.

How can I fade out an AVAudioPlayer on a background thread?

I have an audio file which needs to fade out while the user is scrolling a UIScrollView. However, any performSelector:withObject:afterDelay: method is blocked until the user has stopped scrolling. So I have tried to create some code to perform a fadeout on another thread:
- (void)fadeOut
{
[NSThread detachNewThreadSelector:#selector(fadeOutInBackground:) toTarget:self withObject:self.audioPlayer];
}
- (void)fadeOutInBackground:(AVAudioPlayer *)aPlayer
{
NSAutoreleasePool *myPool = [[NSAutoreleasePool alloc] init];
[self performSelector:#selector(fadeVolumeDown:) withObject:aPlayer afterDelay:0.1];
[myPool release];
}
- (void)fadeVolumeDown:(AVAudioPlayer *)aPlayer
{
aPlayer.volume = aPlayer.volume - 0.1;
if (aPlayer.volume < 0.1) {
[aPlayer stop];
} else {
[self performSelector:#selector(fadeVolumeDown:) withObject:aPlayer afterDelay:0.1];
}
}
It gets as far as the performSelector, but no further because I guess it's trying to perform on a thread it has no access to. I can't even change it for performSelector:onThread:withObject:waitUntilDone: because there is no delay option.
Any ideas? Why have they made it so hard to just fade out a sound? moan
Thanks!
I resolved a similar issue by scheduling the selector in a different run loop mode than the default one. This way it is not interfering with the scrolling events. Using the NSRunLoopCommonModes worked for me:
[self performSelector:#selector(fadeVolumeDown:)
withObject:aPlayer
afterDelay:0.1
inModes:[NSArray arrayWithObject: NSRunLoopCommonModes]];
Inspired by the answer above
while (theAudio.volume > 0.000000)
[self fadeVolumeDown];
- (void) fadeVolumeDown{
theAudio.volume -=0.01;
}