CS193P UITabBarController MVC Help on Assignment 4 - objective-c

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

Related

Objective-C passing values from NSView

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.

How do I override -windowTitleForDocumentDisplayName?

In the Mac Developer Reference for windowTitleForDocumentDisplayName, here, it suggests that a window controller can override this method,
to customize the window title. For example, a CAD application could append β€œ-Top” or β€œ-Side,” depending on the view displayed by the window.
But I can't find any example code showing exactly how to do this. When I override this method in my custom window controller class, it doesn't seem to get called, when I create a new instance of my window controller class. I've been searching the web for a couple of days to find information about this method, but there is hardly any info out there. Much of it is really old - my other question is one of the only recent pages linked to by Google.
Help please!
This method appears to be called only when the NSWindowController's document is actually set to an NSDocument instance.
in your NSWindowController subclass:
- (NSString *)windowTitleForDocumentDisplayName:(NSString *)displayName
{
NSString *newName = [NSString stringWithFormat:#"%# Test", displayName];
return newName;
}
NOTE: this is called from NSDocumentController's openUntitledDocumentAndDisplay: method, so the input displayName will be some variant of "Untitled".

Request input from the View Controller in a Model without breaking MVC in Objective C

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).

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.

iOS MVC - How to pass data from model to controller?

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?