My app is crashing when trying to access a method in my parentViewController. Here is the layout in StoryBoard
MainViewController = STLMMainViewController (ParentViewController)
Scene1 = STLMTimeDateViewController (ChildViewController)
Here is the code for STLMTimeDateViewController
#import "STLMTimeDateViewController.h"
#import "STLMMainViewController.h"
#interface STLMTimeDateViewController ()
#property (nonatomic, strong) STLMMainViewController *stlmMainViewController;
#end
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"The name of the controller %#",self.navigationController.parentViewController);
stlmMainViewController= (STLMMainViewController *) self.parentViewController;
[stlmMainViewController locationButtonSelected]; // This is where the App crashes
NSLog(#"TimeDateController");
}
The App Runs, but when STLMMainViewController is called, the app crashes with the following error:
2013-02-10 16:33:57.422 MyApp[9120:c07] The name of the controller <STLMMainViewController: 0x83850d0>
2013-02-10 16:33:57.434 MyApp[9120:c07] -[UINavigationController locationButtonSelected]: unrecognized selector sent to instance 0x8371a70
If I remove the following line:
stlmMainViewController = (STLMMainViewController *) self.parentViewController;
and just leave
[stlmMainViewController locationButtonSelected];
The App runs, no error, but the following method in [STLMMainViewController locationButtonSelected] does not get called (I never see the log):
-(void)locationButtonSelected
{
[LocationButton setSelected:YES];
[eatDrinkbutton setSelected:NO];
[timeCalButton setSelected:NO];
[carButton setSelected:NO];
[contactButton setSelected:NO];
NSLog(#"LocationButtonSelected Method");
}
All the properties in the locationButtonSelected method and the method itself is declared in .h of STLMMainViewController for public access.
Thanks
You might try this:
self.stlmMainViewController= (STLMMainViewController *)self.navigationController.parentViewController;
(EDIT: actually, as someone else just pointed out, you might want to use presentingViewController instead.)
It looks like you had it right in the log message right before this. You want your navigation controller's parent in this case.
BTW, the reason you don't crash when you delete this line, is because you end up sending the locationButtonSelected to nil. That won't crash, but it also won't do anything either.
Related
I am completely stumped and have been researching for days. Probably something really simple that I am missing.
I have a ViewController which contains a custom UIView called GameView, and a UIView called buttonBox which contains a "next level" button. What I am trying to achieve is when the level is completed in GameView, it fires a function in my ViewController which shows the buttonBox so the user can click the "next level" button. It simply will not work.
I have attempted this in 3 ways, neither have worked:
Creating an IBOutlet in the ViewController, connecting it to the hidden UIView (and it was definitely connected) and calling setHidden:NO.
Calling the [self.view viewWithTag:xxx] and then calling setHidden:NO.
Using hidden=NO instead of setHidden:NO.
Relevant code for ViewController as follows:
#interface PlayViewController : UIViewController
#property GameView *gv;
#property (strong, nonatomic) IBOutlet UIView *buttonBox;
-(void) showButtonBox;
#end
#implementation PlayViewController
#synthesize buttonBox;
...
- (IBAction)showButtonBox {
UIView *uiv = (UIView*) [self.view viewWithTag:999];
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Showing box function");
NSLog(#"%#", uiv);
uiv.hidden = NO;
});
}
#end
And my custom view:
#implementation GameView
...
dispatch_async(bgQueue, ^{
_loopRunning = true;
//NSLog(#"Calling main loop...");
while ([self loopRunning])
{
...
PlayViewController * pvc = [[PlayViewController alloc]init];
[pvc showButtonBox];
...
}
#end
The thing is, the variable uiv is returning null in NSLog, which is obviously why hidden is not working, but I have no idea why. It also didn't work when I was using IBOutlet.
Also, current output from NSLog is as follows:
2015-11-24 00:18:38.612 ib[12579:1264539] Showing box function
2015-11-24 00:18:38.612 ib[12579:1264539] (null)
Thanks in advance.
Correct Answer:
The problem was that I was using StoryBuilder to build my UI, but by using the alloc init method was creating a new view controller (which is never shown) instead of correctly referencing the view controller which was being displayed. This is achieved by passing the view controller being displayed to the view in the viewDidLoad function, see below:
#implementation PlayViewController
#synthesize buttonBox;
#synthesize gv;
- (void)viewDidLoad
{
[super viewDidLoad];
gv = [self.view viewWithTag:777];
[gv setPlayViewController:self];
}
...
Man, it's simple. Let's take a look at:
#implementation GameView
...
dispatch_async(bgQueue, ^{
_loopRunning = true;
//NSLog(#"Calling main loop...");
while ([self loopRunning])
{
...
PlayViewController * pvc = [[PlayViewController alloc]init];
[pvc showButtonBox];
...
}
#end
Here we have the issue:
dispatch_async(bgQueue, ^{
I assume, bgQueue stands for "background queue", which means this is not served by the main thread (the UI thread).
Having that said, it's quite naive to expect
[pvc showButtonBox];
to work properly. Just move this code into the main thread. For instance, you can just wrap the aforementioned line of code into a dispatch_async on the main queue. That should solve your probem, if your outlets and/or tags are OK. Cheers.
[[PlayViewController alloc]init];
This creates a new instance of PlayViewController. Where have you defined your outlets and views?
In a storyboard? You can't use this initialiser - nothing from the storyboard will be picked up, you have to use a segue or initializeViewControllerWithIdentifier:.
In a xib file? Is it called PlayViewController.xib? If not, it won't be picked up by the initialiser. Plain alloc/init of a view controller will only find a nib file as described in the documentation of the nibName property.
Do you really want alloc / init at all? Do you actually want to make a new view controller, or is one already on the screen?
From your comments it seems option 3 is the right answer. The PlayViewController is already on the screen, alloc/init is creating a new instance of it, which is never being put on screen, which never loads any views regardless of storyboards or nibs.
You need to get a reference to the existing instance of PlayViewController. Without knowing the structure of your app it's not too easy to say how that's done - is it presenting the game view? Is the game view a subview of the view controller's view? You may need to pass in a reference (weak) to the game view when it is created, at viewDidLoad, or set up an outlet in the storyboard.
I am using a Category to add functionality to my ViewControllers. When the function from the category is run I get an error unrecognized selector sent to instance 0x7970ebf0. To test out the function I'm calling, I originally had the code within my viewDidLoad where I am calling the added function and it worked fine, so I don't think it is a problem with the function itself. So here is my code for the category and where I call it. Am implementing the Category incorrectly?
Here is "UIViewController+StatusBar.h"
#import <Foundation/Foundation.h>
#interface UIViewController (StatusBar)
-(void) addStatusBarBackground;
#end
Here is "UIViewController+StatusBar.m"
#import "UIViewController+StatusBar.h"
#implementation UIViewController (StatusBar)
-(void) addStatusBarBackground(){
//for making the background of the UIStatus bar black
UIView *statusBarView = [[UIView alloc] initWithFrame:CGRectMake(0, -20, [[self view] bounds].size.width, 22)];
statusBarView.backgroundColor = [UIColor blackColor];
[self.navigationController.navigationBar addSubview:statusBarView];
}
#end
And then I call the function in viewDidLoad of my controller after including UIViewController+StatusBar.h like so
[self addStatusBarBackground]; This is where the error happens, when this is called.
Thanks for the help in advance!
I figured out what I was doing wrong. It ended out to be nothing to do with the category. The category was implemented correctly except for the declaration -(void) addStatusBarBackground(). The parenthesis needed to be deleted. The problem was that I did not select the target memberships that my app has on the right panel in my UIViewController+StatusBar.m file. So the file was not being compiled for my project. I guess its kind of like it wasn't included. I haven't dealt with target memberships before so that was why I was unaware of the problem. Thanks for the comments helping me figure out the answer!
I am developing a project on iOS 7 using ARC, I want to release a private property when the viewController is released
Here is the TestViewController that is presented as a modal view controller, setting a value to the private property testAVPlayer in viewDidLoad:
//TestViewController.m
#import "TestAVPlayer.h"
#interface TestViewController () {
TestAVPlayer *testAVPlayer;
}
#end
- (void)viewDidLoad
{
[self setupPlayer];
}
- (void)setupPlayer {
AVPlayerItem *item = [AVPlayerItem playerItemWithURL:[[NSBundle mainBundle] URLForResource:#"music" withExtension:#"mp3"]];
testAVPlayer = [TestAVPlayer playerWithPlayerItem:item];
[testAVPlayer setActionAtItemEnd:AVPlayerActionAtItemEndNone];
[testAVPlayer play];
}
- (void)dealloc {
NSLog(#"dealloc TestViewController: %#", self);
}
TestAVPlayer is a subclass of AVPlayer, I put a NSLog into the dealloc
// TestAVPlayer.h
#import <AVFoundation/AVFoundation.h>
#interface TestAVPlayer : AVPlayer
#end
// TestAVPlayer.m
#import "TestAVPlayer.h"
#implementation TestAVPlayer
- (void)dealloc {
NSLog(#"dealloc testAVPlayer: %#", self);
}
#end
When TestViewController is dismissed, the testAVPlayer seems never be released, I see the "dealloc TestViewController", but there is no "dealloc testAVPlayer" in console log
I tried your code, the problem is that even if you call [TestAVPlayer playerWithPlayerItem:item] the TestAVPlayer class doesn't have such method, so it will call playerWithPlayerItem: function from the AVPlayer base class, which will return an instance of the AVPlayer class instead of the TestAVPlayer class. The compiler won't give you any warning because the playerWithPlayerItem: method returns a type of id. If you check this with the debugger you'll see that the private variable's type is not TestAVPlayer:
The dealloc of the TestAVPlayer will never be called as no such object was created.
The AVPlayer instance gets deallocated when the TestViewController is deallocated. You can check this by using Instruments or simply adding a Symbolic Breakpoint to [AVPlayer dealloc].
Select the Breakpoint Navigator and click on the + button and add a Symbolic Breakpoint.
Write [AVPLayer dealloc] to the Symbol field and press Enter. When you run the application and the TestViewController gets deallocated then you will see that the breakpoint will be hit, hence the AVPlayer really gets deallocated.
You are using a class factory method to initialize your object, which means that you do not own the testAVPlayer object and thus are not responsible for releasing it.
See Class Factory Methods from the Concepts in Objective-C Programming guide for more details.
If you indeed want to own and control the lifetime of this object, use the following initializer:
testAVPlayer = [[TestAVPlayer alloc] initWithPlayerItem:item];
and your dealloc method will be called.
testAVPlayer = [AVPlayer playerWithPlayerItem:playerItem];
You are using AVPlayer, not your TestAVPlayer.
Try implementing - viewDidUnload then nil the testAVPlayer:
- (void) viewDidUnload
{
[super viewDidUnload];
testAVPlayer = nil;
}
Although you are calling it as [TestAVPlayer playerWithPlayerItem:item], you are really getting back an instance of AVPlayer, not TestAVPlayer. In fact, the AVPlayer instance you created really IS getting deallocated. You won't be able to see your log in dealloc because an instance of that class is never created.
As suggested by another, replace [TestAVPlayer playerWithPlayerItem:item] with [[TestAVPlayer alloc] initWithPlayerItem:item]; and you should start seeing your logs.
What I have:
four TableViewController(A, B, C, D) - which would show four category
of content.
fake TabBarController based on UITabBarController, it
works fine.
What I want to do:
Add a button on ATableView, and it will use a method on my FakeTabBarController (because I want to share the method so I can use it on BTableViewController, CTableViewController rather than duplicate it two or three times)
So I just make the method public (on the .h file), and included the .h file in my ATableViewController. Then,
addTarget:action:forControlEvents: as always, but.. it doesn't work, please help!
Error:
[ATableViewController assisitantButtonPressed:]: unrecognized selector sent to instance 0x715a8d0
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[ATableViewController assisitantButtonPressed:]: unrecognized selector sent to instance 0x715a8d0'
*** First throw call stack:
(0x1ca1012 0x10dee7e 0x1d2c4bd 0x1c90bbc 0x1c9094e 0x10f2705 0x262c0 0x26258 0xe7021 0xe757f 0xe66e8 0x2ea1d3 0x1c69afe 0x1c69a3d 0x1c477c2 0x1c46f44 0x1c46e1b 0x1bfb7e3 0x1bfb668 0x22ffc 0x1c8d 0x1bb5)
libc++abi.dylib: terminate called throwing an exception
FakeTabBarController.h:
#import <UIKit/UIKit.h>
#interface CustomTabBarController : UITabBarController
- (IBAction)assisitantButtonPressed:(UIButton *)sender;
#end
FakeTabBarController.m:
...
- (IBAction)assisitantButtonPressed:(UIButton *)sender
{
switch ([sender tag]) {
case 0: // AA
NSLog(#"AA");
break;
case 1: // BB
NSLog(#"BB");
break;
default:
break;
}
}
...
ATableViewController.m:
#import "ATableViewController.h"
#import "ATableCell.h"
#import "FakeTabBarController.h"
...
- (void)viewDidLoad
{
[super viewDidLoad];
UIImage *AAImage = [UIImage imageNamed:#"search.png"];
UIButton *AAButton = [[UIButton alloc] initWithFrame:CGRectMake(self.view.frame.size.width * 0.73, 0, AAImage.size.width, AAImage.size.height)];
[AAButton setTag:0];
[AAButton setImage:searchImage forState:UIControlStateNormal];
[AAButton addTarget:self action:#selector(assisitantButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:AAButton];
}
This line
[AAButton addTarget:self action:#selector(assisitantButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
sets the target to method assisitantButtonPressed: of self, which is ATableViewController. It throws exception because ATableViewController doesn't have that method. The correct way is to add a weak property to ATableViewController to keep a reference to CustomTabBarController and addTarget to that TabBar, rather than self
#interface ATableViewController
#property (weak, nonatomic) CustomTabBarController* tabBar;
...
#end
#implementation ATableViewController
-(void)viewDidLoad {
[super viewDidLoad];
self.tabBar = <you must somehow obtain the tab bar reference, either here or in init>
...
[AAButton addTarget:self.tabBar action:#selector(assisitantButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
...
}
#end
It's good that you're trying to avoid code duplication. However since ATableViewController, BTableViewController, ... are very similar (judging from your question and their naming), you may want to use just one single TableViewController but with different actual data. Besides, if the method assisitantButtonPressed: really belongs to the table view controller and you moved it to the navBar just to avoid code duplication, it is a very bad practice.
I'm using Reachability in my iPad app and discovered some issues when using modalViewControllers.
In my mainViewController I have a BOOL variable determining weather I'm online or not. Here's my code:
// mainViewController.h
BOOL online;
// mainViewController.m
- (void)reachabilityChanged:(NSNotification *)note
{
if([[note object] isReachable]) {
online = YES;
}
else {
online = NO;
}
}
- (void)getOnline
{
NSLog(#"%d", online);
}
// modalViewController.m
#import "mainViewController.h"
- (IBAction)dismissMe
{
mainViewController *main = [[mainViewController alloc] init];
[main getOnline];
[self dismissModalViewControllerAnimated:YES];
}
When I'm calling [self getOnline] within the mainViewController, it returns 1 ('cause I am online).
But: when I'm calling [main getOnline] within the modalViewController, it returns 0 in the log.
Does anybody know why?!
I also tried to put the online variable as a #property into the modalViewController to handle the if online stuff within the modal. But when I assign a value to it (from the main), and log it within the modal, it always returns (NULL).
Hope, you can help me! With best regards, Julian
Short answer: because they use different instances of the online variable.
Long answer: you should only declare BOOL online in the header, not define it. Defining should happen in the .m file, like this:
In the mainViewController.h:
extern BOOL online; // Declare the variable
In the mainViewController.m:
BOOL online; // Define the variable
// the rest of your code
The way your code is written, a separate BOOL online is created for each .m file that includes mainViewController.h; I am sure this is not what you intended.