Trouble transferring data between view controllers contained in TabBarController - objective-c

This is my first question and I am very new in programming field. I have a tab bar controller and I want to transfer data from FVC(1st view controller) to SVC(second View Controller). In FVC ,I am taking controllers contained in tab bar controller in an array (VCArray) and assigning the second object of that array to instance of SVC and setting properties of SVC with appropriate data of FVC but those properties appears nil in SVC.
and 1 more interesting thing is that when I check the SVC instance which was assigned as 2nd object of the VCArray with [isOFKindClass SVC] and [isOFKindClass FVC] both come true..How is it possible? An object can have two classes? and if I check [isOfKIndClass NSArray] it comes false..it means theres nothing wrong in implementation.
Sorry about my bad english..:p

The answer to part1 is that you need to expose properties on SVC, so that another class can access them, and the FVC needs to import the interface file (the .h) of svc. Thus you almost always need two things to do this: a way to find the class you want to make changes to, and the interface of that class. The property will look like this:
SVC.h:
#property (nonatomic, strong) NSString *title;
- (void)doSomething;
FVC:
#import "SVC.h"
SVC *svc = ...; // get a reference to it
svc.title = #"Howdie!";
[svc doSomething]; // tell the class to use the title you just set, for example
To answer your second question, there are two types of these "is.." methods, isKindOfClass and isMemberOfClass. The first says is the current object a type of the provided class, or ANY superclass. The second only passes if the class is in fact an exact member. For example:
NSMutableData *data;
[data isKindOfClass:[NSData class]] == YES
[data isKindOfClass:[NSMutableData class]] == YES
[data isMemeberOfClass:[NSData class]] == NO
[data isMemeberOfClass:[NSMutableData class]] == YES
EDIT: So the data never makes it into SVC. Well try this - a property is just a shortcut to have an ivar, a getter, (and usually) a setter. You can actually provide your own setter. So you say that (using my example above), that in SVC title is always nil, even though its set by SVC. There are only three reasons this can happen:
FVC has a reference to another object, but in fact you called it SVC so when the value is set, its set to another class not SVC
SVC was a nil object when FVC set the value (ObjectiveC handles messages to nil just fine, so you will not see any errors on the console)
SVC has reset the value to nil unbeknownst to you in say viewWillAppear
So the way you can find this out is override the variable setter (again using my example):
- (void)setTitle:(NSString *)val
{
title = val; // ARC way
NSLog(#"SVC - just set title to %#", title);
}
Add this to SVC and see what happens.

Related

Cocoa Outlets acting wierd, won't recognize selector

I'm getting some weird behavior, I Set a Label in Interface Builder, then I connect the label to a file as an Referencing Outlet.
#property (weak) IBOutlet NSTextField *TitleLabel;
When I access that label in the file (cell.TitleLabel.stringValue = title) and run the application, it doesn't recognize it.I get this:
-[NSApplication TitleLabel]: unrecognized selector sent to instance 0x608000101680
The weird thing is that it doesn't always do this, sometimes it works and displays correctly, other times it doesn't.
I've just started messing with IB so I'm probably missing something. Any help?
Is the property really on your NSApplication subclass? or is it on you application delegate class? It's not impossible for it to be on the application object, but it would be a pretty uncommon (and arguably ill-advised) pattern.
In short, I suspect you're probably connecting it to the wrong object.
EDIT: Ah. I see. You're trying to access things via the topLevelObjects array, but in practice, you can't count on the order of topLevelObjects. What you need to rely on the owner's outlets getting populated, but you're passing nil for the owner. topLevelObjects only exists to give the caller "ownership" (in the reference counting sense) of the top level objects in the xib for memory-mangement purposes, it's not really meant to be "used" directly like you're doing here. (In fairness, I can imagine situations where you might need to introspect that array, but this hardly rises to that level.)
The canonical way to do this would be to use an NSViewController subclass as the owner. In Xcode, if you add a subclass of NSViewController to your project, it will give you the option to create a xib file at the same time that will have everything hooked up. Then you just initialize the NSViewController subclass at runtime and the view outlet property of that class will be filled with the root view. You can obviously add more outlets and plug in whatever you like.
This post appears to cover the basics, if your looking for more details. Apple's docs on xib files and how they work are here.
The problem was that the View would sometimes get assigned to NSApplication. I'm not sure if the way that I am initiating the view is the common way of doing it but the problem was within this block of code:
NSArray * views;
[[NSBundle mainBundle] loadNibNamed:#"CollapseClickViewController" owner:nil topLevelObjects:&views];
CollapseClickCell * cell = [[CollapseClickCell alloc] initWithFrame:CGRectMake(0,0,320,50)];
cell = [views objectAtIndex:0];
the problem was that [views objectAtIndex:0] would sometimes return NSApplication. To fix it I just checked the class against itself and returned that object via:
-(CollapseClickCell*)assignCell:(CollapseClickCell*)cell withArray:(NSArray*)array{
for ( int i = 0; i< [array count]; i++) {
if ([[array objectAtIndex:i] class] == [CollapseClickCell class]) {
return [array objectAtIndex:i];
}
}
return nil;
}
I then assign that to the object:
cell = [cell assignCell:cell withArray:views];
It may not be the conventional way of doing it but it works. If there is a better technique or a more common approach please enlighten me! :)

Objective-C protocol syntax

Here are 2 lines of code from Apple's own SimpleStocks sample code (APLSimpleStockView.m)
NSInteger dataCount = [self.dataSource graphViewDailyTradeInfoCount:self];
NSArray *sortedMonths = [self.dataSource graphViewSortedMonths:self];
The first code line above looks like "dataSource" is the recipient of message graphViewDailyTradeInfoCount:self (which returns an NSInteger).
The second line of code above looks like "dataSource" is now the recipient of message graphViewSortedMonths:self (which returns an NSArray *).
The only reference to dataSource I can find (in APLSimpleStockView.h) has it being a property, not an object/class instance? How come I can send a property a message? I thought I can only get and set a property's value?
The end result of the code is that after line 1, dataCount contains a number, and after line 2, sortedMonths contains an array of sorted month names. But where does this behaviour come from, since I cant find any place in the sample where dataSource causes anything to be returned when sent a message.
Is self.dataSource acting as both a getter and a setter here?
I thought I can only get and set a property's value?
That's correct, but what's the value of a property? In this case it's an object, and you can definitely send a message to an object.
The code is equivalent to using the getter for the property, assigning the result to a variable, and then sending the message:
WhateverClassTheDataSourceIs * dS = self.dataSource;
NSInteger dataCount = [dS graphViewDailyTradeInfoCount:self];
The additional assignment just isn't necessary.
(Your code could also be written
[[self dataSource] graphViewDailyTradeInfoCount:self];
If that makes it clearer for you.)
How come I can send a property a message?
A property is just a promise to provide accessor methods for a given name. If the property is foo, the accessors are typically -foo and -setFoo:. So, in this case, self.dataSource returns an object that receives the message.
If you look at the APLSimpleStockView interface, you'll see the property declared as a pointer to an object:
#property (nonatomic, weak) IBOutlet id<APLSimpleStockViewDataSource> dataSource;
That means that dataSource is an id (that is, a pointer to an object) that implements the APLSimpleStockViewDataSource protocol. Also, it's marked as an outlet so that you can set it in Interface Builder. Accordingly, self.dataSource returns an id (again, an object pointer) that refers to the view's data source.
Is "self.dataSource acting as both a getter and a setter here?
No, it's just a getter returning the data source object.

Setting/getting global variables in objective-C

I am writing an app which is a sort of dictionary - it presents the user with a list of terms, and when clicked on, pops up a dialog box containing the definition. The definition itself may also contain terms, which in turn the user can click on to launch another definition popup.
My main app is stored in 'myViewController.m'. It calls a custom UIView class, 'CustomUIView.m' to display the definition (this is the dialog box that pops up). This all works fine.
The text links from the CustomUIView then should be able to launch more definitions. When text is tapped in my CustomUIView, it launches another CustomUIView. The problem is, that this new CustomUIView doesn't have access to the hash map which contains all my dictionary's terms and definitions; this is only available to my main app, 'myViewController.m'.
Somehow, I need to make my hash map, dictionaryHashMap, visible to every instance of the CustomUIView class. dictionaryHashMap is created in myViewController.m when the app opens and doesn't change thereafter.
I don't wish to limit the number of CustomUIViews that can be opened at the same time (I have my reasons for doing this!), so it would be a little resource intensive to send a copy of the dictionaryHashMap to every instance of the CustomUIView. Presumably, the solution is to make dictionaryHashMap a global variable.
Some of my code:
From myViewController.m:
- (void)viewDidLoad
{
self.dictionaryHashMap = [[NSMutableDictionary alloc] init]; // initialise the dictionary hash map
//... {Code to populate dictionaryHashMap}
}
// Method to pop up a definition dialog
- (void)displayDefinition:(NSString *) term
{
NSArray* definition = [self.dictionaryHashMap objectForKey:term]; // get the definition that corresponds to the term
CustomUIView* definitionPopup = [[[CustomUIView alloc] init] autorelease]; // initialise a custom popup
[definitionPopup setTitle: term];
[definitionPopup setMessage: definition];
[definitionPopup show];
}
// Delegation for sending URL presses in CustomUIView to popupDefinition
#pragma mark - CustomUIViewDelegate
+ (void)termTextClickedOn:(CustomUIView *)customView didSelectTerm:(NSString *)term
{
myViewController *t = [[myViewController alloc] init]; // TODO: This instance has no idea what the NSDictionary is
[t displayDefinition:term];
}
From CustomUIView.m:
// Intercept clicks on links in UIWebView object
- (BOOL)webView: (UIWebView*)webView shouldStartLoadWithRequest: (NSURLRequest*)request navigationType: (UIWebViewNavigationType)navigationType {
if ( navigationType == UIWebViewNavigationTypeLinkClicked ) {
[myViewController termTextClickedOn:self didSelectTerm:request];
return NO;
}
return YES;
}
Any tips on how to make the dictionaryHashMap visible to CustomUIView would be much appreciated.
I have tried making the dictionaryHashMap global by doing the following:
Changing all instances of 'self.dictionaryHashMap' to 'dictionaryHashMap'
Adding the line 'extern NSMutableDictionary *dictionaryHashMap;' to CustomUIView.h
Adding the following outside of my implementation in myViewController.m: 'NSMutableDictionary *dictionaryHashMap = nil;'
However, the dictionaryHashMap remains invisible to CustomUIView. As far as I can tell, it actually remains a variable which is local to myViewController...
It's not resource-intensive to pass around the reference (pointer) to dictionaryHashMap. A pointer to an object is only 4 bytes. You could just pass it from your view controller to your view.
But I don't know why you even need to do that. Your view is sending a message (termTextClickedOn:didSelectTerm:) to the view controller when a term is clicked. And the view controller already has a reference to the dictionary, so it can handle the lookup. Why does the view also need a reference to the dictionary?
Anyway, if you want to make the dictionary a global, it would be more appropriate to initialize it in your app delegate, in application:didFinishLaunchingWithOptions:. You could even make the dictionary be a property of your app delegate and initialize it lazily.
UPDATE
I didn't notice until your comment that termTextClickedOn:didSelectTerm: is a class method. I assumed it was an instance method because myViewController starts with a lower-case letter, and the convention in iOS programming is that classes start with capital letters. (You make it easier to get good help when you follow the conventions!)
Here's what I'd recommend. First, rename myViewController to MyViewController (or better, DefinitionViewController).
Give it a property that references the dictionary. Whatever code creates a new instance of MyViewController is responsible for setting this property.
Give CustomUIView properties for a target and an action:
#property (nonatomic, weak) id target;
#property (nonatomic) SEL action;
Set those properties when you create the view:
- (void)displayDefinition:(NSString *)term {
NSArray* definition = [self.dictionaryHashMap objectForKey:term];
CustomUIView* definitionPopup = [[[CustomUIView alloc] init] autorelease]; // initialise a custom popup
definitionPopup.target = self;
definitionPopup.action = #selector(termWasClicked:);
...
In the view's webView:shouldStartLoadWithRequest: method, extract the term from the URL request and send it to the target/action:
- (BOOL)webView: (UIWebView*)webView shouldStartLoadWithRequest: (NSURLRequest*)request navigationType: (UIWebViewNavigationType)navigationType {
if ( navigationType == UIWebViewNavigationTypeLinkClicked ) {
NSString *term = termForURLRequest(request);
[self.target performSelector:self.action withObject:term];
return NO;
}
return YES;
}
In the view controller's termWasClicked: method, create the new view controller and set its dictionary property:
- (void)termWasClicked:(NSString *)term {
MyViewController *t = [[MyViewController alloc] init];
t.dictionary = self.dictionary;
[t displayDefinition:term];
}
Create a class that will be used as singleton. Example.
You Should always keep your data in separate class as the mvc pattern suggest and that could be achieved by using a singleton class for all your dictionary terms and accesing them from every custom view when needed.

NSWindowController and isWindowLoaded

I have an NSWindowController and I initialize it like this;
+ (MyWindowController *) sharedController
{
static MyWindowController *singleton = nil;
if (!singleton) singleton = [[self alloc] initWithWindowNibName: #"myWindow"];
return singleton;
}
and I show windows like this;
[[MyWindowController sharedController] showWindow: nil];
Now the problem is that I need information from some controls on that window. But I do not want to load the window if it's not yet loaded because then I can just go with the defaults. Should I use isWindowLoaded? #property to access the singleton? or what is recommended here? (If #property, then please give me the readonly, nonatomic attributes too.)
Don't store model data in views. Have the controller (probably not MyWindowController, but the one that needs the data) own the real data (if any) and fill in any defaults.
Any values you fill in in Interface Builder should be for nothing more than sizing.
For example, if I know a field must hold a number whose value is ±50000, I'll enter “-50000” and size the field accordingly, and leave the “-50000” there. The actual default is more likely to be 0 or something, and I will have that provided by the controller that owns the value (or, if the field shows a property of a model object, I'll have the default provided by each new model object).

Exposing model object using bindings in custom NSCell of NSTableView

I am struggling trying to perform what I would think would be a relatively common task. I have an NSTableView that is bound to it's array via an NSArrayController. The array controller has it's content set to an NSMutableArray that contains one or more NSObject instances of a model class. What I don't know how to do is expose the model inside the NSCell subclass in a way that is bindings friendly.
For the purpose of illustration, we'll say that the object model is a person consisting of a first name, last name, age and gender. Thus the model would appear something like this:
#interface PersonModel : NSObject {
NSString * firstName;
NSString * lastName;
NSString * gender;
int * age;
}
Obviously the appropriate setters, getters init etc for the class.
In my controller class I define an NSTableView, NSMutableArray and an NSArrayController:
#interface ControllerClass : NSObject {
IBOutlet NSTableView * myTableView;
NSMutableArray * myPersonArray;
IBOutlet NSArrayController * myPersonArrayController;
}
Using Interface Builder I can easily bind the model to the appropriate columns:
myPersonArray --> myPersonArrayController --> table column binding
This works fine. So I remove the extra columns, leaving one column hidden that is bound to the NSArrayController (this creates and keeps the association between each row and the NSArrayController) so that I am down to one visible column in my NSTableView and one hidden column. I create an NSCell subclass and put the appropriate drawing method to create the cell. In my awakeFromNib I establish the custom NSCell subclass:
MyCustomCell * aCustomCell = [[[MyCustomCell alloc] init] autorelease];
[[myTableView tableColumnWithIdentifier:#"customCellColumn"]
setDataCell:aCustomCell];
This, too, works fine from a drawing perspective. I get my custom cell showing up in the column and it repeats for every managed object in my array controller. If I add an object or remove an object from the array controller the table updates accordingly.
However... I was under the impression that my PersonModel object would be available from within my NSCell subclass. But I don't know how to get to it. I don't want to set each NSCell using setters and getters because then I'm breaking the whole model concept by storing data in the NSCell instead of referencing it from the array controller.
And yes I do need to have a custom NSCell, so having multiple columns is not an option. Where to from here?
In addition to the Google and StackOverflow search, I've done the obligatory walk through on Apple's docs and don't seem to have found the answer. I have found a lot of references that beat around the bush but nothing involving an NSArrayController. The controller makes life very easy when binding to other elements of the model entity (such as a master/detail scenario). I have also found a lot of references (although no answers) when using Core Data, but Im not using Core Data.
As per the norm, I'm very grateful for any assistance that can be offered!
Finally figured this one out. Man that took some doing. So here is what I needed to do...
First of all I needed to create an array of my model's key values in my model object and return those key values in an NSDictionary from within the model.
Thus my model got two new methods as follows (based on my simplified example above):
+(NSArray *)personKeys
{
static NSArray * personKeys = nil;
if (personKeys == nil)
personKeys = [[NSArray alloc] initWithObjects:#"firstName", #"lastName", #"gender", #"age", nil];
return personKeys;
}
-(NSDictionary *)personDictionary
{
return [self dictionaryWithValuesForKeys:[[self class] personKeys]];
}
Once implemented, I assign my bound value in my table to
arrayController --> arrangeObjects --> personDictionary.
The last step is to reference the object in the NSCell drawWithFrame and use as needed as follows:
NSDictionary * thisCellObject = [self objectValue];
NSString * objectFirstName = [thisCellObject valueForkey:#"firstName"];
NSString * objectLastName = [thisCellObject valueForKey:#"lastName"];
And as I hoped, any update to the model object reflects in the custom NSCell.
There is another approach that does not require the dictionary. However, it does require the implementation of the - (id)copyWithZone:(NSZone *)zone method within your data class. For example:
- (id)copyWithZone:(NSZone *)zone {
Activity *copy = [[self class] allocWithZone: zone];
copy.activityDate = self.activityDate;
copy.sport = self.sport;
copy.sportIcon = self.sportIcon;
copy.laps = self.laps;
return copy; }
Now in IB, you point the Table Column's value at Array Controller --> arrangedObjects
The drawWithFrame method will now return your actual object from the objectValue.
Activity *rowActivity = [self objectValue];
Changing the class requires updating the copyWithZone method and then accessing the data directly in your drawWithFrame method.
I'm very grateful for this post, Hooligancat, because my project was stalled on this problem, and as hard as I looked at Tim Isted's identical solution at http://www.timisted.net/blog/archive/custom-cells-and-core-data/
I couldn't figure it out.
It was only when I read your excellent simplification of the problem and your version of the solution that the penny dropped - I'm very grateful!