Unable to set property on DetailViewController in an iPad app - objective-c

EDIT: Figured it out. I was able to access the property through the appDelegate instance with appDelegate.detailViewController.thisRequest = aRequest;
I'm porting a fairly simple iPhone Navigation based app to a Split View iPad app.
I have two nested levels of navigation on the Master view. The user picks a value from the first table and it loads the 2nd table. Selecting a value on the second table loads the Detail item for the detail view. Or it's supposed to. It's letting me set the property on the DetailViewController without an error, but it doesn't appear to be actually setting it. It's just null.
I'm following the SplitView template fairly closely. I'm only really getting off the beaten track by adding that 2nd TableViewController. My RootViewController loads the 2nd TableViewController on didSelectRowAtIndexPath, and that part's working. In my 2nd TableViewController.m I'm trying to set the detail item for the DetailView. In didSelectRowAtIndexPath I am able to get the object for my detail, but when I set it it doesn't set.
Here's the code:
First, in my DetailViewController, I set up a property for the object that will populate my fields (*thisRequest):
DetailViewController.h:
#interface DetailViewController : UIViewController <UIPopoverControllerDelegate, UISplitViewControllerDelegate> {
UIPopoverController *popoverController;
UIToolbar *toolbar;
Request *thisRequest;
}
DetailViewController.m:
#interface DetailViewController ()
#property (nonatomic, retain) UIPopoverController *popoverController;
- (void)configureView;
#end
#implementation DetailViewController
#synthesize toolbar, popoverController, detailItem, detailDescriptionLabel;
#synthesize thisRequest;
Then, in my 2nd TableViewController, I try to maintain a reference to the DetailViewController (I suspect this is where I'm messing up; I still don't fully understand how things persist and exactly how to reference them in this framework).
RequestsTableViewController.h:
#class TrackerSplitViewAppDelegate, RequestsTableViewController, DetailViewController;
#interface RequestsTableViewController : UITableViewController {
NSString* selectedDepartmentID;
NSMutableArray *requests;
DetailViewController *detailViewController;
}
#property (nonatomic, retain) NSString* selectedDepartmentID;
#property (retain, nonatomic) NSMutableArray *requests;
#property (nonatomic, retain) IBOutlet DetailViewController *detailViewController;
RequestsTableViewController.m:
#implementation RequestsTableViewController
#synthesize selectedDepartmentID;
#synthesize requests;
#synthesize detailViewController;
<snip>
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
TrackerSplitViewAppDelegate *appDelegate = (TrackerSplitViewAppDelegate *)[[UIApplication sharedApplication] delegate];
Request *aRequest = [appDelegate.requests objectAtIndex:indexPath.row];
detailViewController.thisRequest = aRequest;
NSLog(#"Request Loaded: %#", aRequest.Title);
NSLog(#"detailViewController.thisRequest: %#", detailViewController.thisRequest.Title);
[appDelegate release];
}
The aRequest object loads just fine, and the detailViewController.thisRequest = aRequest; line runs with no errors, but my log output looks like this:
2011-03-01 10:07:22.584 TrackerSplitView[8162:40b] Request Loaded: MyTitleString
2011-03-01 10:07:22.585 TrackerSplitView[8162:40b] detailViewController.thisRequest: (null)
Like I said, I still don't fully understand how everything gets passed around in the iOS framework, but the SplitNavigation template does something nearly identical to this with an id value and it works fine. The only differences I can see is the navigation layer isn't nested, as mine is, and also they don't use a pointer for the id since it's an integer instead of a custom object.
I'm guessing I'm not passing in the detailViewController to my RequestTableViewController properly, but I don't see how to do it.

EDIT: Figured it out. I was able to access the property through the appDelegate instance with appDelegate.detailViewController.thisRequest = aRequest;
On to the next bug.

Related

Double click on row in NSTableView doesn't display the new view

I have an os x app that uses core data.
I have 3 .xib files in my app, those are:
1. MainMenu.xib
2. MasterTableViewController.xib
3. DetailViewController.xib
When started , app displays a view that has NSTableView with couple of records in it.
I name that view MasterTableViewController
I want when user double click on the row, to hide the "master" view and to display my "detail" view. I named that view DetailViewController.
When double clicked on the row in the NSTableView in the "master" view,nothing happen, "master" view remains visible. What I want is "master" view to dissapear, and "detail" view to appear.
Here is the code that I have right now, and more explanations follows:
AppDelegate.h
#import <Cocoa/Cocoa.h>
#interface AppDelegate : NSObject <NSApplicationDelegate>
#property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
#property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
#property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
#property (nonatomic,strong) NSViewController *mainAppViewController;
#property (weak) IBOutlet NSView *mainAppView;
- (void)changeViewController:(NSInteger)tag;
#property (weak) IBOutlet NSTableView *websitesTableView;
- (void)tableViewDoubleClick:(id)nid;
#end
AppDelegate.m
#import "AppDelegate.h"
#import "MasterTableViewController.h"
#import "DetailViewController.h"
#interface AppDelegate ()
#property (weak) IBOutlet NSWindow *window;
- (IBAction)saveAction:(id)sender;
#end
#implementation AppDelegate
NSString *const masterTable = #"MasterTableViewController";
NSString *const detail = #"DetailViewController";
-(void)awakeFromNib {
[_websitesTableView setTarget:self];
[_websitesTableView setDoubleAction:#selector(tableViewDoubleClick:)];
}
- (void)tableViewDoubleClick:(id)nid {
NSInteger rowNumber = [_websitesTableView clickedRow];
NSTableColumn *column = [_websitesTableView tableColumnWithIdentifier:#"websiteUrl"];
NSCell *cell = [column dataCellForRow:rowNumber];
NSInteger tag = 2;
[self changeViewController:tag];
}
- (void)changeViewController:(NSInteger)tag {
[[_mainAppViewController view]removeFromSuperview];
switch (tag) {
case 1:
self.mainAppViewController = [[MasterTableViewController alloc]initWithNibName:masterTable bundle:nil];
break;
case 2:
self.mainAppViewController = [[DetailViewController alloc]initWithNibName:detail bundle:nil];
break;
}
[_mainAppView addSubview:[_mainAppViewController view]];
[[_mainAppViewController view] setFrame:[_mainAppView bounds]];
[[_mainAppViewController view] setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// automatically run the master table view controller
NSInteger tag = 1;
[self changeViewController:tag];
}
Now, some of you may wondering, where is the rest of the code. I have ommited the boiler plate code for the core data below in the AppDelegage.m, since it is unchanged. I used binding to make my NSTableView to work and to display my records, so MasterTableViewController.h and .m files are empty, and same is true for the DetailViewController.h and .m file.
Important note - What i can't understand here: If I change the tag in 2 in the applicationDidFinishLaunching method, detail view is displayed normally, but if i switch it back on 1, and then double click on the row, "master" view (with the NSTableView) remains visible, and nothing happen (views are not swapped)
Anyone can help me to find out what is wrong with my code?
Regards, John
You apparently had a second instance of your AppDelegate class instantiated in the MasterTableViewController.xib file. There should be only one AppDelegate instance and that's the one in MainMenu.xib. So, it shouldn't be in MasterTableViewController.xib.
One of the instances was receiving the double-click action method from the table, but the other one was the one with the outlet to the main window.
You need(ed) to get rid of the second instance and find another way to access the app delegate from the MasterTableViewController.

Properties are null outside of viewDidLoad

Two properties:
#property (retain, nonatomic) NSString *drinkType;
#property (retain, nonatomic) NSString *wheelType;
When accessed from viewDidLoad as self.drinkType, etc, they hold the value I expect. However, when accessed from a public method
-(void)updateSentenceWithSelectedAromas:(NSMutableArray *)selectedAromas;
they are null. What is happening here?
The "selectedAromas" array is passed from another controller to this method.
ViewController *aromaVC = [[ViewController alloc] init];
[aromaVC updateSentenceWithSelectedAromas:selectedAromas];
ViewController.h
-(void)updateSentenceWithSelectedAromas:(NSMutableArray *)selectedAromas;
#property (retain, nonatomic) NSString *drinkType;
#property (retain, nonatomic) NSString *wheelType;
ViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
// This is working
NSLog(#"The drink type is:%#", self.drinkType);
}
-(void)updateSentenceWithSelectedAromas:(NSMutableArray *)selectedAromas {
// This returns null
NSLog(#"The drink type is:%#", self.drinkType);
}
I think your missing quite a few things, which leads me to think that you're missing some basic understanding of variable scope in ObjectiveC, let's see if this helps you in some way:
First, your selectedAromas array has no relation whatsoever with drinkType and wheelType. So passing this array to the ViewController seems irrelevant.
Second, in your ViewController you're declaring your own drinkType and wheelType variables, so there's no way they will have the value of some other class or Controller.
You probably aren't setting your properties soon enough (init would be a good place). viewDidLoad is called much later in relation to the code you posted.
Okay, Michael Dautermann was absolutely right. The method updateSentenceWithSelectedAromas was in fact running in a separate instance of the view controller. To solve this problem I implemented a protocol listener with my method and set the delegate of the child controller to its parent using a segue.
Thank you everyone for all your help.
In case anyone stumbles upon this, here is what I did:
ViewController2.h
#protocol updateSentenceProtocol <NSObject>
//Send Data Back To ViewController
-(void)updateSentenceWithSelectedAromas:(NSMutableArray *)selectedAromas;
#end
#interface ViewController2 : UIViewController
// delegate so we can pass data to previous controller
#property(nonatomic,assign)id delegate;
#end
ViewController2.m
#synthesize delegate;
-(void)someMethod {
[delegate updateSentenceWithSelectedAromas:selectedAromas];
}
ViewController.m
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"viewController2Segue"])
{
// Get reference to the destination view controller
ViewController2 *vc = [segue destinationViewController];
vc.delegate = self;
}
}
-(void)updateSentenceWithSelectedAromas:(NSMutableArray *)selectedAromas {
// do stuff with array and properties as needed
}

Navigating from one UITableView to another inside appDelegate

The first UITableView is presented inside a Popover that is called from the RootViewController of the application.
I need to navigate to another UITableView inside the same popover. This is easy to do if you just instance an object of the second UITableView and push it from the first one.
In the next paragraph I write as taking for granted some facts, please correct me if I'm wrong.
The problem here is that this process should be done inside the appDelegate. This is because I'm implementing Dropbox API and I need the pushViewController to be done immediately after the login process is done, which means the navigation through UITableViews has to be done inside of the application:handleOpenURL. I asume that application:handleOpenURL has to be called right there and that's why I also asume the pushViewController has to be done there in order to have the navigation done after the Dropbox API validation window is presented, without having to make the user do anything else.
This is how my code looks like:
AppDelegate.h
#interface AppDelegate : NSObject <UIApplicationDelegate>{
UINavigationController *navigationController;
NSString *relinkUserId;
UIWindow *window;
TableViewControllerForStorageList *rootViewController;
ViewController *viewController;
}
#property (nonatomic, strong) IBOutlet UIWindow *window;
#property (nonatomic, strong) IBOutlet UINavigationController *navigationController;
#property (nonatomic, strong) IBOutlet TableViewControllerForStorageList *rootViewController;
#property (nonatomic, strong) IBOutlet ViewController *viewController;
AppDelegate.m
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
if ([[DBSession sharedSession] handleOpenURL:url]) {
if ([[DBSession sharedSession] isLinked]) {
[(TableViewControllerForStorageList *)self.window.rootViewController PushView];
}
return YES;
}
return NO;
}
TableViewControllerForStorageList.h
-(void)PushView;
TableViewControllerForStorageLost.m
-(void)PushView
{
TableViewControllerIpadStorage *tableViewControllerIpadStorage = [[TableViewControllerIpadStorage alloc]initWithNibName:#"TableViewControllerIpadStorage" bundle:Nil];
[self.navigationController pushViewController:tableViewControllerIpadStorage animated:YES];
}
Off course I got sure that Application:HandleOpenURL is running, but when calling PushView from there the error is [ViewController PushView]: unrecognized selector sent to instance
So, how can make the navigation be done from there? Which basics about objective c am I missing?
It is not clear from your question how your app is structured, so this answer may not be the best solution for your problem but hopefully it gives you some idea of how your view controller hierarchy is likely built up from your app delegate.
Lets say your first view controller class is named FirstViewController. Either your app delegate has a direct reference to an instance of this view controller, or it can access it through a parent view controller (perhaps via window.rootViewController).
Now lets say you have a method in FirstViewController named pushNextViewController that performs the task of pushing the second table view controller.
You can call that method from within the application:handleOpenURL: method of your app delegate.
This might look something like:
[self.window.rootViewController.firstViewController pushNextViewController];
There are other ways you could get a reference to your instance of FirstViewController and it would be cleaner if your rootViewController was a custom subclass so your could create a pushNextViewController method there and from that method tell your FirstViewController instance to pushNextViewController:
[self.window.rootViewController pushNextViewController];
Note that in both examples above, you will need to cast the rootViewController to whatever class it is actually an instance of or the compiler will warn you that it does not have the property firstViewController (example 1) or the method pushNextViewController (example 2).
EDIT: If your rootViewController is a UINavigationController, then your code might look more like:
UINavigationController* navController = (UINavigationController*)window.rootViewController;
FirstViewController* vc = navController.viewControllers[0];
[vc pushNextViewController];
EDIT 2: OK, It looks like the confusion here is that the window object has a rootViewController property (which appears to be pointing to your navigationController) and then you also have a rootViewController instance variable in your app delegate. These are two different objects, making your naming convention a bit confusing, but if I am right then the following should work:
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
if ([[DBSession sharedSession] handleOpenURL:url]) {
if ([[DBSession sharedSession] isLinked]) {
[rootViewController PushView];
}
return YES;
}
return NO;
}
You should consider changing the name of your app delegate's reference to your TableViewControllerForStorageList to something other than rootViewController to alleviate some confusion.

Data encapsulation, Instance variable cannot be accessed

I'm having some trouble understanding what classes can read what variables in other classes. I've read to many different things online and cant seem to find anything solid in here. I've literally wasted the past two days trying to get my program to work but no classes can read any other classes variables. Any help will be GREATLY appreciated.
This is my ViewController.h:
#interface ViewController : UIViewController
{
#public
NSString *nameOfLabel;
}
#property (strong, nonatomic) IBOutlet UILabel *firstLabel;
- (IBAction)Switch:(id)sender;
- (IBAction)changeLabel:(UIButton *)sender;
-(NSString *) nameOfLabel;
#end
nameOfLabel is a public variable and should be able to be accessed by an outside class, right?
ViewController.m:
#import "ViewController.h"
#import "NewView.h"
#interface ViewController ()
#end
#implementation ViewController
- (IBAction)Switch:(id)sender {
NewView * new = [[NewView alloc] initWithNibName:nil bundle:nil];
[self presentViewController: new animated:YES completion:NULL];
}
- (IBAction)changeLabel:(UIButton *)sender {
nameOfLabel = #"Test Name";
_firstLabel.text = nameOfLabel;
}
-(NSString *) nameOfLabel {
return nameOfLabel;
}
#end
changeLabel button changes *firstLabel.text to "Test name".
second class is NewView.h:
#import "ViewController.h"
#interface NewView : UIViewController
#property (strong, nonatomic) IBOutlet UILabel *secondLabel;
- (IBAction)changeSecondLabel:(UIButton *)sender;
#end
and NewView.m:
#import "NewView.h"
#interface NewView ()
#end
#implementation NewView
{
ViewController *view;
}
- (IBAction)changeSecondLabel:(UIButton *)sender {
view = [[ViewController alloc] init];
_secondLabel.text = view.nameOfLabel;
}
#end
changeSecondLabel should change secondLabel.text to nameOfLabel which is 'Test name', however, the label actually disappears which makes me think that nameOfLabel cannot be reached. Ive played around with nameOfLabel, making it a #property and then synthesising it, as well as trying putting it in { NSString *nameOfLabel; } under #implementation but I still get the same result.
This line: view = [[ViewController alloc] init]; creates a new ViewController which doesn't know anything about what you may have done to some other ViewController. In your case, it specifically doesn't know that changeLabel: was called on another ViewController before this new one ever existed.
When the second view controller (NewView) is presented, it has no reference to the first view controller (ViewController) and it's data.
Here are a couple of suggestions.
In modern Objective-C I'd recommend using properties instead of exposing a variable.
Look over the naming in general. "ViewController" is not a good name for example.
If the property is part of an internal state of the class, declare it in a class extension.
Before you present the second view controller, set a reference to the string from the first view controller.
Part of ViewController.m:
#interface ViewController ()
#property (copy,nonatomic) NSString *nameOfLabel;
#end
#implementation ViewController
- (IBAction)Switch:(id)sender {
NewView *new = [[NewView alloc] initWithNibName:nil bundle:nil];
new.secondLabel.text = self.nameOfLabel;
[self presentViewController: new animated:YES completion:NULL];
}
First of all please read about coding standards, it's not a good practice to:
Name variables like "new"
Name methods like "Switch"
Name UIViewController like "view" or "NewView"
Regarding logic:
This is all messed up here. What you actually do is you create viewController with nameOfLabel which is empty and is only changed on button press. I assume you press that button so it's changed. Then on switch action you create another viewController and present it. Then from inside that new viewController you create another new viewController which has empty nameOfLabel, get this empty value and put it inside secondLabel.
There are couple of ways you can do to change secondLabel:
Move nameOfLabel to model and read it from there when you want to change secondLabel,
Because your new viewController is child of viewController that keeps nameOfLabel you can access it by calling [[self presentingViewController] nameOfLabel] but make it property first,
Pass nameOfLabel through designated initializer.
Well, if you want a simple demonstration of access of a public ivar, the syntax is:
view->nameOfLabel;
^^
not dot-syntax:
view.nameOfLabel;
(dot-syntax just goes through accessor methods).
I've only seen a handful of warranted edge cases over the years; there's rarely, rarely ever a good reason to make an ivar public (also, protected is also rarely a good choice).

IBOutlet in ARC releases and sets to nil. How to avoid this? objective c

I'm new to ARC and Storyboarding. I've set IBOutlet to UITableView from my UIViewController.
After some time my IBOutlet sets to nil and I can't reload it from other classes.
Here is my dataTable IBOutlet:
#property (weak, nonatomic) IBOutlet UITableView *dataTable;
At the start dataTable is not nil, but not when I try to access it from another class (via appDelegate). How to solve this problem?
UPDATE
I call this method from my UIViewController
[appDelegate.myClass loginWithUserName:loginField.text andPassword:pwdField.text];
When it's done, and I have data to show, I call this code from loginWithUserName method:
MyViewController *controller = [[AppDelegate sharedStoryboard] instantiateViewControllerWithIdentifier:#"MyViewController"];
[controller audioLoaded];
And here is that method in my UIViewController, wich reloads data
-(void) audioLoaded
{
//it is nil here
[self.dataTable reloadData];
}
Set the property to strong retain the object:
#property (strong, nonatomic) IBOutlet UITableView *dataTable;
It's not good practice to access a UITableView from another view controller though..
EDIT:
You shoul reconsider the whole approach, by moving that logic from your appdelegate to a dedicated class that will perform the login. You can create a simple protocol that the UIViewController with the table can implement, then, when calling the login method, pass a reference to the current viewcontroller, something like
loginWithUserName:andPassword:andCaller:(id<LoginDelegate>)sender
Where LoginDelegate is something on this line:
#protocol LoginDelegate
- (void)audioLoaded;
#end
In this way you can just call
[sender audioLoaded];