I'm new to Objective-C and I'm struggling to find the proper way to pass NSArray values from one NSView to another NSView. My code looks like this:
NSViewOne:
NSLog(#"%#", arrayValues); // 10, 20, 30
NSViewTwo *displayValues = [[NSViewTwo alloc] init];
[displayValues showValuesFromOne:arrayValues];
NSViewTwo:
- (void)showValuesFromOne:(NSArray *)sender {
NSArray *arrayValues = sender;
NSLog(#"%#", arrayValues);
}
In NSViewOne the array values show, in NSViewTwo I get (null).
There is a misconception:
First of all action methods are an entry point from the GUI to your code, not an exit point. The passed value is always the sender. You can use action methods from your code, but this is rarely useful. So -showValuesFromOne: makes no sense at all.
Second, it is unusual to pass a value from a view to another view. View communicates with controllers and vice versa. Therefore the code in view 1 should be in the controller and be executed by the view 1. Then the invoked controller code pushes the values to view 2. Do not use a action method for this, but a simple method.
The controller loads both nibs and therefore has outlets to both views. Therefore for the controller it is easy to communicate with both views. The threads converge in his hand.
Related
I have an Objective C app and I am trying to follow the MVC guidelines laid out.
There is a point in my Model where user input is required before any further calculations can proceed.
Is there an elegant way I can request input from the controller without breaking MVC?
The only way I can think of doing this right now is just returning a value a nonzero value from the method in the model to the controller, and then having the control call a separate method to send the input to the model.
I was hoping there is some other way to pause the execution of the code in the model while it waits for input from the controller.
Here is some applicable code. I simplified most of it for readability.
Relevant ViewController function:
#implementation GameViewController
-(void)tap:(UITapGestureRecognizer *)gesture
{
if(gesture.state == UIGestureRecognizerStateEnded) {
id view = gesture.view;
if ([view isKindOfClass:[GameBoardTileView class]]) {
GameBoardTileView *gameTileView = (GameBoardTileView *) view;
int row = gameTileView.row;
int col = gameTileView.col;
[self.game chooseTileAtRow:row column:col];
// Updates states of all game tiles after tapping one tile
[self updateTiles];
}
}
}
#end
Relevant Model function:
-(void)chooseTileAtRow:(int)row column:(int)col
{
// Retrieves the game tile at the spot just selected
GameBoardTile *tile = [self retrieveTileAtRow:row column:col];
// Makes sure the spot is empty
if (tile.companyType == -1) {
// If no company type is found generate a random company type
if (!tile.companyType) {
//NSLog(#"changing company type");
if ([self.chainsInPlay count]) {
/////////// Would like index to be given by the Controller instead of randomly generated
int index = [self generateRandomNumber:0 end:[self.chainsInPlay count]-1];
tile.companyType = [[self.chainsInPlay objectAtIndex:index] intValue];
[self.chainsInPlay removeObjectAtIndex:index];
}
}
}
}
What the view controller does is it keeps track of a grid of tiles that when an individual tile is tapped, it sends that row and column over to the Model using chooseTileAtRow:column:. The model retrieves a GameBoardTile at that row and column and gets the companyType. If the companyType does not exist (== 0), I would like it to be set. Currently it just generates a random number for testing purposes. Ideally, I would like this number to be passed in from the Controller and selected via user input.
You cannot "pause execution" or "wait"; don't even think that way.
It is perfectly reasonable for the model to have a property needsMoreInfo or to return false or nil from some data request or other method in order to say that it can't proceed without further information.
The controller could thus ask the model whether it needs more info, and supply it, before requesting the real calculation.
I think you have to split your model's method, so you can call different parts in your VC. It depends on your MVC logic. You can also use NotificationCenter or delegate pattern.
You should most likely be using the delegation pattern.
Essentially, a base class can define itself as a view controller's delegate, while instantiating the view controller. You use a protocol to ensure that the base class implements all the necessary methods. Your view controller can then blindly call methods on the delegate (often passing arguments).
I have a class BrowserWindowController that extends NSWindowController. My app delegate has a BrowserWindowController that it allocates, initializes, and points an instance variable at when the app is launched. Then, it displays its window. The goal is to have a window from a NIB show up.
However, the code I am using ends up allocating TWO BrowserWindowControllers and initializes both. I have used the debugger to track down when BWC's initWithWindow method is called:
browser = [[BrowserWindowController alloc] initWithWindowNibName:#"BrowserWindow"]; //this calls initWithWindow as expected
[browser showWindow:nil]; //this allocates ANOTHER BWC and calls initWithWindow on it!
showWindow is making a new BrowserWindowController. I don't know what points to the new object it makes. It's a huge problem for me. Any way to get around this or make the window show up using a different method? Or could I at least get a pointer to the controller that showWindow creates for whatever reason?
Did you check the condition like this and try?
if !(browser)
{
browser = [[BrowserWindowController alloc] initWithWindowNibName:#"BrowserWindow"]; //this calls initWithWindow as expected
[browser showWindow:nil];
}
Worst solution ever. The problem was that I had a property in my controller called "owner" that was an NSString. NSWindowController already has an "owner" property, and I overlooked that. Somehow, that caused the NIB loader to make a second controller with no accessible pointer to it and do some other weird things.
So I renamed it, and it works now. Thank goodness... I was tearing my hair out with this problem.
I did quite a bit of research on this, but I am having a mental block about my problem. I am working on Objective-C for an iOS app
Here's my set up:
The view controller gets a text from the view (user input), and passes that text to the MethodA of the model.
The MethodA in model works on the input text and gets an output (e.g. searches google for that text). It does the search using dispatch_async method which calls the selector to MethodB within model.
MethodB parses the output and puts all the results into a NSMutableArray
My Question Here: how do I pass that NSMutableArray back to view controller so I can show it on the view?
Sorry, if the answer to my question is very simple/obvious. I am new to Objective-C
Any time I want to do async processing and that stuff needs to get back into the UI somewhere, I do one of two things:
1. Use NSNotification to tell anyone who cares that the work is complete
2. Use a delegate property on the worker and a #protocol
1 NSNotification
The model object should document in it's .h file that it fires notifications when certain things happen; such as when a portion of the model has been updated. When the ViewController initializes the model object, have it set itself up as an observer of the documented notification, and implement a callback which updates the UI.
2 delegation and #protocol
Create a #protocol such as #protocol FooModelUpdateDelegate with a method properly named such as fooObjectDidUpdate:(Foo *)object;, and then the model class has a delegate property as id<FooModelUpdateDelegate> updateDelegate and the ViewController sets itself as that delegate, and I'm sure you can figure out the rest.
I guess passing along a delegate-object that respoons to a selector-method and calling this method with the processed data will be a good way to achieve the loosley coupled structure your program deserves. Are you familiar with this concept, or shall I dig up some code-samples for you?
UPDATE: Code samples
So, I would probably use the calling class, say MyViewController, to implement the callbackMethod, myCallbackMethod as follows:
-(void) myCallbakcMethod: NSMutableArray* array {
//DoWhatever with the returned data
}
The point is to get the result passed back to this method when the computation is finished.
So in your MyViewController where you call MethodA you pass along a reference to the delegate to handle the result, namly self.
//From this:
[MyModel methodA:param1]
//To:
[MyModel methodA:param1:self]
MyModels methodA and methodB would need to add a parameter (id)delegate and pass that along between the calls.
In methodB where the data myArray is ready, do the following call:
if([delegate respondsToSelector:#selector(myCallbackMethod:)]])
[observer performSelector:#selector(myCallbackMethod:) withObject:myArray];
In your view controller:
// your controller code
...
[myModel getSearchResult:searchKeyword receiver:self action:#selector(responseReceived:)];
}
- (void)responseReceived:(MyModel *)model
{
NSArray *searchResult = model.searchResult;
[model release], model = nil;
// some view actions, for instance update your table view
...
}
In your model:
...
- (id)getSearchResult:(NSString *)searchKeyword receiver:(id)aReceiver action:(SEL)receiverAction {
self.receiver = aReceiver;
self.action = receiverAction;
// some async actions
}
In async response receiver:
...
[self.receiver performSelector:self.action withObject:self];
}
Unless I'm misunderstanding your description it sounds like your "model" class is doing more than it should. In this case it's doing at least some of the work of your controller. My suggestion would be to fold methodA and methodB into the view controller (or another controller class). Method B could still set the NSMutableArray property of "model" instance, if that's essential (or skip that step if it's not).
-(void)methodA {
NSMutableArray *someArray = ...
[self methodB:someArray];
}
-(void)methodB:(NSMutableArray*)array {
NSLog(#"%#", array);
// do something with the array, like update the view
}
But if both are methods inside the view controller why not just update the view inside the method instead of passing it to another method?
I'm going through the Stanford CS193P course on iTunesU and am a little puzzled on how to do the recently viewed photos portion on assignment 4.
In the assignment we are to have a tab bar controller with two tabs.
1st tab is a navigation controller that will show a table of places, which will push a table of photo names, which will push a scroll view with a photo
2nd tab is a navigation controller that will show a table of recently viewed photos, which will push a scroll view with a photo.
I have the first tab working, and now when I push the scroll view with the image, I also want to add that photo to an array of recent photos, which MVC should own this recent photos array?
The Tab View Controller (if so the docs say that this class is not intended for sub classing)
The root Table View Controller of the 2nd Tab (how do I pass the current photo to the instance is in another tab) (and quite frankly should the first tab know about the second tab)
The root Table View Controller of the 1st Tab (then how does the second tab pull this data from the first tab?)
Something else
I guess I'm still hazy about MVC's, protocols, delegates and data sources. If you have your solution to this task that I could look through I would greatly appreciate it.
I ended up pushing and pulling the data from user defaults.
Although I'm curious why the tab bar controller is not intended for sub classing. That seems like the most logical place for data to be owned when it is needed by multiple tabs.
I've done something similar and if I don't missundestood your question completely, you could create a Singelton whichcould act like some kind of shared database. It will never be initialized in a normal fashion, just created when you use it the first time. This singelton could contain your array and you could then call it from everywhere by writing just:
[SingeltonType main].sharedPhotos
The following example is from my own code where I have a "User" which is the owner of the app. There I store a database with info that will be available from anywhere during runtime.
header:
#interface User : NSObject {
Database *_storage;
}
#property (nonatomic, retain) Database *storage;
+(User*)owner;
main file:
#import "User.h"
#implementation User
#synthesize password = storage = _storage;
static User* _owner = nil;
+(User*)owner {
#synchronized([User class]) {
if(!_owner) [[self alloc] init];
return _owner;
}
return nil;
}
+(id)alloc {
#synchronized([User class]) {
NSAssert(_owner == nil, #"Attempted to allocate a second instance of a singleton.");
_owner = [super alloc];
return _owner;
}
return nil;
}
-(id)init {
self = [super init];
if(self != nil) {
self.storage = [[[Database alloc] init] autorelease];
}
return self;
}
Then I just call it like this:
[User owner].storage // which gives me access to it
Hope that helps! Really useful if you need to access data from different places :)
Note: You will only have ONE instance of this object and cannot create more.
After a bunch of additional searching, I didn't find any one consistent way to pass data from tab to tab.
Since we are only storing a relatively small amount of data, I decided to make a class, with class methods (for convenience) to push and pull the data into user defaults.
I have messed around with that question a bit by using protocol. I created the protocol in the class displaying the image (and UIScrollView). I then adopted the protocol in the "viewed photos" tableController class and implemented that protocol method that passes the viewed image. The problem I have is how do you define the "viewed Photos" tableController class as the delegate, given that 1) it has not been loaded yet and might not be loaded until after viewing pictures 2) how do you work your way though the nav controllers and tab controller to point to the class declaring the protocol. Would love to hear from experts here on whether protocol or class method is the right way here from a programming methodology?
Thanks
KB
I've already killed a day on this subject and still got no idea on how could this be done in a correct way.
I'm using NSOutlineView to display filesystem hierarchy. For each row in the first column I need to display checkbox, associated icon and name of the file or directory. Since there's no standard way to make this, I've subclassed NSTextFieldCell using both SourceView and PhotoSearch examples, binding value in IB to name property of my tree item class though NSTreeController. I'm using drawWithFrame:inView: override to paint checkbox and image, forwarding text drawing to super. I'm also using trackMouse:inRect:ofView:untilMouseUp: override to handle checkbox interaction.
Everything was fine up until I noticed that once I press mouse button down inside my custom cell, cell object is being copied with copyWithZone: and this temporary object is then being sent a trackMouse:inRect:ofView:untilMouseUp: message, making it impossible to modify check state of the original cell residing in the view.
Since the question subject is about binding, I thought this might be the answer, but I totally don't get how should I connect all this mess to function as expected. Tried this:
[[[treeView outlineTableColumn] dataCell] bind:#"state"
toObject:treeController
withKeyPath:#"selection.state"
options:nil];
but didn't succeed at all. Seems like I'm not getting it.
May this be a completely wrong way I've taken? Could you suggest a better alternative or any links for further reading?
UPD 1/21/11: I've also tried this:
[[[treeView outlineTableColumn] dataCell] bind:#"state"
toObject:treeController
withKeyPath:#"arrangedObjects.state"
options:nil];
but kept getting errors like "[<_NSControllerTreeProxy 0x...> valueForUndefinedKey:]: this class is not key value coding-compliant for the key state." and similar.
You bind a table (or outline) column's value, not an individual data cell's state. The data cell's object value is set to the current row/col's value then drawn so you don't have potentially thousands (or millions?) of cells created for no good reason.
Further, you want the tree or array controller's arrangedObjects, not its selection.
Bind the column's value to the tree controller's arrangedObjects as the controller key, and "state" as the model key path in IB; or #"arrangedObjects.state" in code as above.
Okay, I've managed to do what I needed by binding columns's value to arrangedObject's self (in IB) and overriding cell's setObjectValue: so that it looks like:
- (void) setObjectValue:(id)value
{
if ([value isMemberOfClass:[MyNodeClass class]])
{
[super setObjectValue:[value name]];
[self setIcon:[value icon]];
[self setState:[value state]];
}
else
{
if (!value)
{
[self setIcon:nil];
[self setState:NSOffState];
}
[super setObjectValue:value];
}
}
Actual state change is performed within another class, connecting its method to cell's selector (in IB) which I call using
[NSApp sendAction:[self action] to:[self target] from:[self controlView]];
from cell's trackMouse:inRect:ofView:untilMouseUp:. This another class'es method looks like this:
- (IBAction) itemChecked:(id)sender
{
MyNodeClass* node = [[sender itemAtRow:[sender clickedRow]] representedObject];
if (node)
{
[node setState:[node state] == NSOnState ? NSOffState : NSOnState];
}
}