How to retain ViewContoller when popped Objective -C - ios

How do you retain a view contoller when its is popped from the stack ?
Basically i have an Mp3 Player view controller. When i'm in that view controller, i can play music. when the music is playing and i navigate to a different controller, The music stops playing, and when i navigate back to that controller, it starts another instance of it. so my guess for a fix, will probably be to retain the view controller ?
This is how i initiate my music player view controller:
NSString *songLargeArtworkURL = [self.songArtworkURL stringByReplacingOccurrencesOfString:#"large" withString:#"crop"];
MusicPlayerViewController *musicPlayer = [[MusicPlayerViewController alloc] init];
musicPlayer.songName = self.testString;
musicPlayer.songArtWork = songLargeArtworkURL;
musicPlayer.songStream = self.songStreamURL;
[self.navigationController pushViewController:musicPlayer animated:YES];

Instead of using a local variable for MusicPlayerViewController, use an instance variable. Only for the first time, allocate the musicPlayer instance. Shown below:
NSString *songLargeArtworkURL = [self.songArtworkURL stringByReplacingOccurrencesOfString:#"large" withString:#"crop"];
if(! musicPlayer) {
musicPlayer = [[MusicPlayerViewController alloc] init];
}
musicPlayer.songName = self.testString;
musicPlayer.songArtWork = songLargeArtworkURL;
musicPlayer.songStream = self.songStreamURL;
[self.navigationController pushViewController:musicPlayer animated:YES];
The above code makes sure that, only for the first time the music player is instantiated. Subsequent times, it pushes the same instance of the music player

Related

cocos2d-js game iOS Crash when enter the game at the second time

We are using cocos2d-js to develop an iOS App which can launch different games. So I add an button in the native app viewcontroller and start the game by clicking the button, just like this:
-(void)didClickGame2Btn:(id)sender
{
//加载游戏
cocos2d::Application *app = cocos2d::Application::getInstance();
// Initialize the GLView attributes
app->initGLContextAttrs();
cocos2d::GLViewImpl::convertAttrs();
// Use RootViewController to manage CCEAGLView
RootViewController *rootViewController = [[RootViewController alloc] init];
rootViewController.wantsFullScreenLayout = YES;
[self.navigationController presentViewController:rootViewController animated:YES completion:^{
// IMPORTANT: Setting the GLView should be done after creating the RootViewController
cocos2d::GLView *glview = cocos2d::GLViewImpl::createWithEAGLView((__bridge void *)rootViewController.view);
cocos2d::Director::getInstance()->setOpenGLView(glview);
NSString *documentDir = [SEGetDirectories dirDoc];
NSString *wPath = [NSString stringWithFormat:#"%#/GameData/Game2",documentDir];
NSLog(#"document------:%#",documentDir);
std::vector<std::string> searchPathList;
searchPathList.push_back([wPath UTF8String]);
cocos2d::FileUtils::getInstance()->setSearchPaths(searchPathList);
//run the cocos2d-x game scene
app->run();
}];
[[UIApplication sharedApplication] setStatusBarHidden:true];
}
the rootViewController contains the game view. And then we add an button in the game, which is used to exit the game. The click event code of the exit game button likes:
//exit the game and close the view controller
gameEndCallBack:function(sender){
cc.log("director end............");
cc.director.end();
var ojb = jsb.reflection.callStaticMethod("ViewControllerUtils", "dismissCurrentVC");
}
We use the reflection to dismiss the rootViewController:
+(void)dismissCurrentVC
{
UIViewController *currentVC = [ViewControllerUtils getCurrentVC]; //这里获取最顶层的viewcontroller
[currentVC dismissViewControllerAnimated:YES completion:^{
NSLog(#"xxx");
}];
}
Everything is ok when the first time to enter the game, but after dismissing the rootViewController, we try to enter the game again, it crash.
The crash line is in the ScriptingCore::runScript metod and executing the code:
evaluatedOK = JS_ExecuteScript(cx, global, *script, &rval);
And the crash info is "exc_bad_access".
It is much the same problem as this topic, but the approaches in it did not solve the problem.
http://discuss.cocos2d-x.org/t/how-to-destroy-a-cocos-game-on-ios-completely/23805
This problem has been confusing me serveral days, I have no solution for this. Can anyone give me some help?
You can make the app to support multiple games with in the app.
All you have done is required but in addition to that please follow the below instructions.
First of all, cocos provide a singleton instance of cocos2d::Application that can not be restarted again especially in iOS. So the approach of ending the Director cc.director.end(); won't help you.
You should start the application only once by using the function call cocos2d::Application::getInstance()->run(); and next time if you want to start the game layer, you should not call this method again.
Instead, just pause cocos2d::Director::getInstance()->pause(); and resume cocos2d::Director::getInstance()->resume(); the director when you want to stop the game.
In this approach, if you dismiss/dealloc the view-controller instance then you should create the glview cocos2d::GLView instance again without calling the run method.
One more problem is, take care of the delay in loading the new scene. GLView will display the previous game scene for a while. Do a work around that will show blank screen while the new scene is ready.
Hope this will help you.

AVAudioPlayer stop all sounds playing on different view controllers

I've an app with 3 view controllers. The first one generate a 5min track and when it goes to second view it keeps on playing in setVolume:2.0 and same goes for 3rd view controller.But on third ViewCOntroller there is a UIButton that re generate the first ViewController. And when it goes back to first view controller it starts that sound again and the previous one is already playing so it's a mix.Now the sound was init on first ViewController so how can I stop it on third ViewController? It there any code that stops all the sounds playing?
You can declare the AVAudioPlayer as a global variable in your first ViewController. The effect will be that every instance of your first ViewController will be able to share the same player.
AVAudioPlayer *player;
After that you can do something like this in ViewDidLoad:
if (!player) {
AVAudioPlayer *player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
[player play];
} else if (!player.playing) {
[player play];
}
You can create a shared instance and be called from all viewController. So you can refer when to stop/play/load, etc.
#interface MainAVPlayer : AVAudioPlayer
+ (instancetype)shared;
#end
#implementation MainAVPlayer
+ (instancetype)shared {
static id _sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedInstance = [[self alloc] init];
});
return _sharedInstance;
}
#end
When accessing from other view controller call:
[[MainAVPlayer shared] initWithContentsOfURL:url error:&error];
[[MainAVPlayer shared] play];
p/s: there might be typo, writing from memory.

AVMusic plays double when switching views

When i switch views the music keep playing the the background what is fine with my app. But when the user comes back to the initial view the music starts again over the original one so the user hears the music double. I have already got some code to check whether the sound is already playing but it doesnt work. :(
Any thoughts?
if (audioPlayer.playing == 0 ) {
NSURL *url = [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/CheeZeeLab.mp3", [[NSBundle mainBundle] resourcePath]]];
NSError *error;
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
audioPlayer.numberOfLoops = 0;
audioPlayer.volume = 1;
[audioPlayer prepareToPlay];
[audioPlayer stop];
if (audioPlayer == nil)
NSLog(#"werkt niet");
else
[audioPlayer play]; }
else{
}
I bet your problem is in how you transition back to your initial view.
If you're pushing (or doing a segue from the child back to the parent... i.e. a circular segue), you're creating a new instance of your parent view controller.
And creating a new instance starts a fresh version of the audio player playing that sound.
You need to properly pop the view to go back to the previous view controller.
Depending on how you have set up the AVAudioPlayer, with manual setter and getter, with property or even in the constructor. You are confronted with some issues.
When you initialize the ViewController you are allocating an AVAudioPlayer, and if it keeps playing it's not being deallocated, and still retained. And the ViewController won't be deallocated either until every property's retain count is 0.
After going back to the main view, and trying to set up the same ViewController again, my guess is that you are not trying to push the same instance to the stack. But rather making a new instance and pushing that one onto the stack? And that also makes a AVAudioPlayer.
This is ofc a bit of guesswork as a can't see you entire code. But if this is the case, I think you could set things up a bit better. Make sure that the ViewController gets properly deallocated and releasing all it's properties before making a new instance to push to the stack.

Allow only one instance of UIView to appear in Storyboards

I'm creating an app using storyboards where a first screen has a list of songs in a table view and I want to pass that song to the next view (let's call it the music player). I can accomplish this, but I also need to music player to keep track of various exercise statistics while the music player is playing music.
The problem is, if I enter the music player, go back to the song list view, and then select a cell again, it creates an entirely new instance of the music player. This is problematic as it creates a new timer and I lose all my exercise statistics. I also discovered that the original music player instance still exists because the timer continues to fire.
Is there anything I can do to make sure only one instance of the music player continues to appear?
Here is my current code (in order of execution):
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
self.nextScreen = [segue destinationViewController];
}
^ This stores the next screen as a member variable. I have to do this because prepareForSeque: is called before didSelectRowAtIndexPath: and I need to set data on the next view.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
self.nextScreen.currentSong = [self.allSongs objectAtIndex:indexPath.row];
}
^This just sets the song.
I then proceed to set the next view up as needed in viewDidLoad:.
Is there a trick to fix this? If not, any suggestions how I get around this? I'm thinking one workaround is I'll have to set up some sort of singleton, I'm just not sure if that's the best option since I will need to reset the data so often.
Thanks!
You can't do this with segues. Segues (with the exception of unwind segues) always create new instances when they are performed. You can push to your new controller (assuming your embedded in a navigation controller), and in the method where you do so, only create a new instance if it's the first time.
-(IBAction)goToNext {
if (! self.next){
self.next = [[NextViewController alloc] init];
self.next.whateverProperty = whatever you want to pass;
self.next.delegate = self; // set the delegate here if you're using delegation to send info back to this controller
}
[self.navigationController pushViewController:next animated:YES];
}
You can still set up your controllers in the storyboard if you want, just don't connect them together. In that case you would instantiate them with the following instead of using alloc init:
self.next = [self.storyboard instantiateViewControllerWithIdentifier:#"next"];

iOS ZXingWidget - use view of ZXingWidgetViewController in own ViewController.view as subview

I am trying to use the iOS zxing Widget for QR Code Scanning. I have a ViewController which is pushed as an Item in my UINavigationController or presented Modally from another ViewController. This ViewController has a SegmentedControl for 3 different views. Two of those Views are UIWebViews which load simple Websites, nothing special about them.
The selection looks something like this:
- (IBAction)segmentedControlValueChanged:(id)sender {
NSString *urlString;
ZXingWidgetController *widController;
QRCodeReader* qrcodeReader;
NSSet *readers;
switch (segmentedControl.selectedSegmentIndex) {
case 0:
[self.view bringSubviewToFront:self.productSearchWebView];
urlString = [[SACommunicationService sharedCommunicationService] getURLforKey:kURLTypeProductSearch];
[self.productSearchWebView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:urlString]]];
break;
case 1:
[self.view bringSubviewToFront:self.marketSearchWebView];
urlString = [[SACommunicationService sharedCommunicationService] getURLforKey:kURLTypeMarketSearch];
[self.marketSearchWebView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:urlString]]];
break;
case 2:
widController = [[ZXingWidgetController alloc] initWithDelegate:self showCancel:YES OneDMode:NO];
qrcodeReader = [[QRCodeReader alloc] init];
readers = [[NSSet alloc] initWithObjects:qrcodeReader,nil];
widController.readers = readers;
[self.QRCodeScannerView addSubview:widController.view];
[self.view bringSubviewToFront:self.QRCodeScannerView];
break;
default:
break;
}
}
I tried to debug and go step by step and find out where the problem comes from:
Decoder (which is part of the underlying ZXing logic) tries to call "failedToDecodeImage:" from its delegate (which should be the ZXingWidgetController class) and crashes (EXC_BAD_ACCESS)
While stepping through I found out that the "cancelled" Method of the ZXingWidgetController gets called. Now I don't really know why this method gets called. The Widget shouldn't stop right after initializing and starting the decoder.
So the answer is a very simple one.
I was using iOS 5.0 and ARC. The ZXing ViewController is instantiated locally inside the method. Since the ViewController itself does not get viewed ARC sets a release at the end of the method and the ViewController gets freed. Since the ViewController is released, the view which was retained by the ViewController will be released as well. Cancelled is called because the Main ViewController does not exist anymore and calling some method on a nil pointer results in a BAD_ACCESS.
The Solution here was to set the ZXingViewController as a global strong property. This kept the object from being released right at the end of that method and thus the view which was added as a subview to another ViewControllers view was held in memory as long as the ViewController was alive.
You're not supposed to add controller views as subviews of another view. You're suposed to present controllers using the various UIViewController mechanisms.
By doing what you're doing, you're violating UIViewController contracts. The widget isn't getting things like viewWillAppear, viewDidAppear, etc.
If you want to use ZXing at the UIView/CALayer level instead of the UIViewController level, look at the classes in the ZXing objc directory.
Try this... also in the .h file make this ZXingWidgetController *widController;
And also to the viewScanner set clipToBounds to true.
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self performSelector:#selector(openScanner) withObject:nil afterDelay:0.5];
}
-(void)openScanner
{
self.widController = [[ZXingWidgetController alloc] initMiniWithDelegate:self showCancel:NO OneDMode:YES];
NSMutableSet *readers = [[NSMutableSet alloc ] init];
MultiFormatReader* reader = [[MultiFormatReader alloc] init];
[readers addObject:reader];
self.widController.readers = readers;
[viewScanner addSubview:self.widController.view];
}

Resources