Objective-C, using protocol to access datasource - objective-c

I'm using the MVC model but I cannot get the data needed for the View, I am trying to use a data source since a View should never own its data. Typically, a protocol is used to create a data source.
I have a MVC: CalculatorBrain.[hm] - CalculatorViewController.[hm] - (view is .xib)
I also have MVC: GraphingView - GraphingViewController - (model is the data I can't get)
The goal is: when I press a button on the calculator, it draws the function (e.g.: x+5=) that is currently in it's display. The calculator part takes care of the expression/function, the display, etc while the graphing part should draw. CalculatorViewController should be the GraphingView's source, but the data always stays null.
This is GraphingView.h with the declaration of the protocol :
#class GraphingView;
#protocol GraphingViewSource
- (float)getDataPoints:(GraphingView *)requestor;
#end
#interface GraphingView : UIView {
id <GraphingViewSource> source;
}
#property (assign) id <GraphingViewSource> source;
#end
CalculatorViewController.m implements the protocol by implementing the getDataPoints: method. Header of CalculationViewController.h :
#interface CalculatorViewController : UIViewController <GraphingViewSource>
Pressing the button which should set up the data source :
- (IBAction)graphPressed:(UIButton *)sender
{
GraphingViewController *graphingVC = [[GraphingViewController alloc] init];
graphingVC.graphingView.source = self;
[self.navigationController pushViewController:graphingVC animated:YES];
[graphingVC release];
}
Pressing the button brings up the new view nicely etc, but following line of code only returns null (inside GraphingView.m) :
float result = [self.source getDataPoints:self];
It seems, I cannot access my data source...

From your comments it seems that graphingView is nil at the time you are trying to set its source.
If there is a xib for the view controller, you should be doing this:
GraphingViewController *graphingVC = [[GraphingViewController alloc] initWithNibName:#"GraphingViewController" bundle:nil];
Instead of a plain init.
This will create and instantiate all of your view objects so they are available to have properties set before you present the view controller.

Related

Passing data back from child view controller

I am a beginner, and I have a project in which I need to pass data back from a childviewcontroller. Specifically, I have a picker view in my container and I want to be able to use it to select an option (say to simplify just a color to chose from an array of ten colours). Then I would like to be able to access the chosen color in my parent view controller and do something with it. I have researched this for a couple of days now and the closest answer to what I am looking for I found in a related question on S.O. Here it is:
"Regarding passing value to the containerView controller, you can make a property in the ChildViewController of the value you want it to be passed. Then in your ParentViewController do something like the following:
self.ChildViewController.yourProperty = yourValue
The opposite can be done in 4 ways:
You can make a delegate protocol to communicate the data between your controllers.
You can post a notification in your ChildViewController and add the parent controller as an observer.
You can use KVO.
And the easiest way out, you can make a property in your parentviewController and access it like the following:"
((YourParentViewControllerClassType *)self.parentViewController).yourParentProperty = TheValueYouWant;
Now, I would like to try the fourth option first as delegation, kvo and so on are options I have read about but not ready to tackle yet. What I would need help with is the last option.
Say I had a property in my child view controller where I store the chosen color. Something like:
#interface ViewController ()
#property NSString *ChosenColorInChildVC;
#end
And then, later on:
self.ChosenColorInChildVC = [self pickerView:myPickerView titleForRow:[myPickerView selectedRowInComponent:1] forComponent:1]];
how would I pass that value using the proposed:
((YourParentViewControllerClassType *)self.parentViewController).yourParentProperty = TheValueYouWant;
Can someone dumb it down a little further for me? Thanks
I'm going to explain you with an example how delegation works.
Edit your ChildViewController.h like this:
#protocol ChildViewControllerDelegate;
#interface ChildViewController : UIViewController
#property (weak)id <ChildViewControllerDelegate> delegate;
#end
#protocol ChildViewControllerDelegate <NSObject >
- (void) passValue:(UIColor *) theValue;
#end
On your ChildViewController.m when you want to pass something back to the ParentViewController , do like this:
- (void) myMethod
{
[delegate passValue:[UIColor redColor]]; // you pass the value here
}
On your ParentViewController.h
#import "ChildViewController.h"
#interface ParentViewController : UIViewController <ChildViewControllerDelegate > // you adopt the protocol
{
}
On your ParentViewController.m:
- (void) passValue:(UIColor *) theValue
{
//here you receive the color passed from ChildViewController
}
Now be careful. Everything will work only if you set the delegate.
So when you declare ChildViewController inside your ParentViewController class, do like this:
ChildViewController * controller = [[ChildViewController alloc]initWithiNibName:#"ChildViewController"];
controller.delegate = self; //without this line the code won't work!
#metronic is right; use delegation.
Also typically you will include the sender in your delegate methods:
-(void) childViewController:(ChildViewController*)viewController passValue:(UIColor*) theValue
Noted: this is very important.
Write protocol declaration code above the #import lines e.g.
#protocol -----
#end
import ----
#interface classname ---

Basic MVC: set variable to NSTextField input in different classes

The main idea:
Model: Set the value from the textfield to a variable which I can call/log.
View: Just a NSTextField hooked up to the Model class.
Controller: NSButton hooked up to the ViewController.
As you will notice, it logs the basic string from NSLog, also the predefined beginvalue. but when I ask for the txtBegin value it returns NULL
I know the TextField and the Button are hooked up in the connections inspector.
Screenshot:
Downloadable project:
ViewController.h
#import <Cocoa/Cocoa.h>
#import "Model.h"
#interface ViewController : NSView
- (IBAction)logTheVariable:(id)sender;
#end
ViewController.m
- (IBAction)logTheVariable:(id)sender
{
Model *myModel = [[Model alloc]init];
[myModel doSomething];
}
Model.h
#import <Foundation/Foundation.h>
#interface Model : NSObject{
//iVars
int begin;
}
//properties
#property (weak) IBOutlet NSTextField *txtBegin;
//methods
-(void)doSomething;
#end
Model.m
#import "Model.h"
#implementation Model
-(void)doSomething{
NSLog(#"I'm in the Model Class"); //logs like a charm!
begin = 5; //just a test to see if it logs anything (which works)
NSLog(#"%d",begin);// logs like a charm!
//->Problem is here <-
NSLog(#"%#",_txtBegin.stringValue); //returns a "NULL" value.
//->Problem is here <-
}
#end
Simple solution just declare the outlet of textfield in your viewcontroller and then modify below method in your model class and implement it:-
Model.h
-(void)doSomething: (NSString*)yourstringvalue;
Model.m
-(void)doSomething: (NSString*)yourstringvalue
{
NSLog(#"%#",yourstringvalue);
}
Viecontroller.m
- (IBAction)logTheVariable:(id)sender
{
Model *myModel = [[Model alloc]init];
NSString * str=self.begintext.stringValue;
[myModel doSomething:str];
}
The Model class instance you use in logTheVariable: is logging a null value because it is a new instance you created in the action of the ViewController, not an instance of Model interface builder is aware of.
- (IBAction)logTheVariable:(id)sender
{
Model *myModel = [[Model alloc]init];
//This is a new instance. The IBOutlet for txtBegin is null.
[myModel doSomething];
}
What you have implemented is not how MVC is intended. Apple provides a thorough roadmap of the user interface, framework and programming concepts you need to know to develop for OSX that will help you understand how Apple expects you to use their frameworks. https://developer.apple.com/library/mac/referencelibrary/GettingStarted/RoadMapOSX/chapters/01_Introduction.html
Models generally don't know anything about the user interface. They just store data and communicate when data is changed.
Your Model class should expose a property for any data it wants to expose.
Your Model class should not have any reference to the NSTextField.
So now in your model, you can log when your property is changed
-(void)doSomething:(NSString *)value //method name should be setBegin assuming you name your property 'begin'
{
NSLog(#"I'm in the Model Class"); //logs like a charm!
begin = 5; //just a test to see if it logs anything (which works)
NSLog(#"%d",begin);// logs like a charm!
//->Problem is here <-
NSLog(#"%#",value); //will log like a charm
}
Views generally doesn't know anything about the model. It just displays data in a way the user can interact with and possibly edit.
Controllers tie the Model and View together. It receives notification from the model when data changes and updates the View. Conversely, it also receives notification from the View when the data is edited to update the Model.
Your ViewController class should have a reference to the NSTextField (using an outlet)
Your ViewController class should have an instance of Model which it creates internally.
Now your logTheVariable can be implemented to tie the Model and View together:
- (IBAction)logTheVariable:(id)sender
{
//Use ViewController's model instance
Model *myModel = [self myModel];
NSString * value = [[self txtBegin] stringValue];
[myModel doSomething:value];
}

How to pass values between 2 View Controllers without protocol?

I have two view controllers, call them viewA and ViewB
All the action happens in main view - ViewA
A menu button is hit, brings up ViewB, all is well and the menu comes up
Now, the user touches one IBAction button, which programmatically just needs to:
change the value of a BOOL, call it myBOOL to YES
dismiss ViewB
pass the myBOOL variables current state of YES back to ViewA
I have declared the same BOOL, set property, synthesized on both Views, but per my NSLog upon dismissal of ViewB and loading back up ViewA, it reverts back to NO
So I know I'm going off on a tangent, I just want to know if you can send the value of a BOOL between two controllers and if so, please show me an example... as searches have found Protocols and Delegate examples with NSString's, and when I attempt with a BOOL I get stuck in an import loop, however I've read that its possible to make a global BOOL, as bad design as it is, I just need to get over this block for now.
A question on this topic should really be focused more on NSNotificationCenter rather than NSUserDefaults, taking note that both are singletons.
NSUserDefaults:
The purpose of this class is NOT to pass variables between classes. It's purpose is, well, to store user's defaults. (ie preferences, settings, ... etc).
NSNotificationCenter:
This class is very handy, and has many different uses, one of which is to broadcast a variable for any class to receive. The receiving class is called the observer. This pattern is known as the Observer Pattern.
NOTE: The NSUserDefaults approach has the advantage of allowing you to set the variable before the other class is initialized, and can be retrieved at anytime. However, that's really sloppy (IMHO) and considered bad practice.
Quick and Dirty code sample on NSNotificationCenter:
// upon initializing the class that wants to observe the changes, we add it as an observer.
// So, somewhere in the A.m, upon being initialized (init, maybe?).
- (id)init {
if (self = [super init]) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(calledUponNotif:)
name:#"MyObserveKey"
object:nil];
}
return self;
}
// the selector should look something like this:
- (void)calledUponNotif:(NSNotification *)notif {
id sentVar = [notif object];
}
// Somewhere in the B.m
[[NSNotificationCenter defaultCenter] postNotificationName:#"MyObserveKey"
object:varToSend];
Another note: After calling the postNotification method, the registered selector in the other class will be called synchronously, so you don't have to worry about that.
This is not a good encapsulation answer but without being able to use protocols or delegates I don't believe it will have good encapsulation.
You can also create a global variable that you can set in one view controller and access in another.
ViewControllerOne.h
extern NSString *globalVariable;
#interface ViewControllerOne
#end
ViewControllerOne.m
#import "ViewControllerOne.h"
#implementation ViewControllerOne
NSString *globalVariables = #"Some String in the variable to access in second controller";
#end
ViewControllerTwo.m
#import "ViewControllerTwo.h"
#import "ViewControllerOne.h"
#implemetation ViewControllerTwo
- (void)viewDidLoad
{
NSLog("%#", globalVariables);
}
#end
This will print out into the console
****CONSOLE****
Some String in the variable to access in second controller
There is View-independent value keeping tool. You can use:
[[NSUserDefaults standardUserDefaults]setObject:<#(id)#> forKey:<#(NSString *)#>]
For example, you inputs strings or datas in A view, you can store them in above variables. And then, in B view, you can use them by below code:
[[NSUserDefaults standardUserDefaults]objectOrKey:<#(NSString *)#>]
These are a example of NSUserDefaults data using:
View A:
- (void)textFieldDidEndEditing:(UITextField *)sender
{
if (sender == homepage) {
[[NSUserDefaults standardUserDefaults]
setURL:[NSURL URLWithString:homepage.text] forKey:Ever5secHomepagePrefKey];
if( [homepage canResignFirstResponder] ) {
[homepage resignFirstResponder];
}
} else if (sender == userId) {
[[NSUserDefaults standardUserDefaults]
setObject:userId.text forKey:Ever5secUserIdPrefKey];
objectForKey:Ever5secUserIdPrefKey]);
if( [userId canResignFirstResponder] ) {
[userId resignFirstResponder];
}
} else if (sender == password) {
[[NSUserDefaults standardUserDefaults]
setObject:password.text forKey:Ever5secPasswordPrefKey];
if( [password canResignFirstResponder] ) {
[password resignFirstResponder];
}
}
}
View B:
userId.text = [[NSUserDefaults standardUserDefaults]
objectForKey:Ever5secUserIdPrefKey];
password.text = [[NSUserDefaults standardUserDefaults]
objectForKey:Ever5secPasswordPrefKey];
homepage.text = [[[NSUserDefaults standardUserDefaults]
URLForKey:Ever5secHomepagePrefKey]
description];
You don't need to use NSNotificationCenter, NSUserDefaults or global variables.
As long as the view controllers are related (and looking at the OP's question, they certainly seem to be) you can simply set the view controllers up to hold a reference to each another (with one of the references being weak of course in order to avoid a "retain", or "strong reference", cycle). Then each view controller can set the property on the other view controller as needed. Example follows...
NB: This concept is valid for any two related view controllers. However, the following code assumes that:
The view controllers in question are related via a navigation controller and the second view controller is attached to the first via a push segue.
iOS 5.0 or above is in use (as it makes use of storyboards).
FirstViewController.h
#interface FirstViewController : UIViewController
/* Hold the boolean value (or whatever value should be
set by the second view controller) in a publicly
visible property */
#property (nonatomic, assign) BOOL someBooleanValue;
/* Provide a method for the second view controller to
request the first view controller to dismiss it */
- (void)dismissSecondViewController;
#end
FirstViewController.m
#import "FirstViewController.h"
#import "SecondViewController.h"
#implementation FirstViewController
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
/* Get the reference to the second view controller and set
the appropriate property so that the secondViewController
now has a way of talking to the firstViewController */
SecondViewController *vc = [segue destinationViewController];
vc.firstViewController = self;
}
- (void)dismissSecondViewController
{
// Hide the secondViewController and print out the boolean value
[self.navigationController popViewControllerAnimated:YES];
NSLog(#"The value of self.someBooleanValue is %s", self.someBooleanValue ? "YES" : "NO");
}
#end
SecondViewController.h
#import "FirstViewController.h"
#interface SecondViewController : UIViewController
// Create a 'weak' property to hold a reference to the firstViewController
#property (nonatomic, weak) FirstViewController *firstViewController;
#end
SecondViewController.m
#implementation SecondViewController
/* When required (in this case, when a button is pressed),
set the property in the first view controller and ask the
firstViewController to dismiss the secondViewController */
- (IBAction)buttonPressed:(id)sender {
self.firstViewController.someBooleanValue = YES;
[self.firstViewController dismissSecondViewController];
}
#end
Of course, the most correct way to handle this sort of inter-viewController communication is to use protocols/delegates/data sources so that the SecondViewController doesn't need to know the specifics of its parent/owner object. However, sometimes it is quicker/simpler to build a solution like this just to prove the concept. Then if all is well and the code is worth keeping, refactor to use protocol(s).
In the case where view controllers don't - and shouldn't - know about each other, it may be necessary to use NSNotificationCenter. Don't use global variables or NSUserDefaults for communication between view controllers.
There are two options available storing and retrieving data in different view controllers.
1)NSUserDefaults is best option for storing data and accessing in any other view controllers.
The NSUserDefaults class provides convenience methods for accessing common types such as float, double, integer, Boolean.
A default object must be a property list, that is, an instance of (or for collections a combination of instances of): NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary.
This is very easy and best method for storing and retrieving data.
if you want to read about NSUserDefaults, here I am sharing document.
NsuserDefaults Document.
2) You would create properties when you want them to be accessible outside the class or other view controllers.
Create property in this way. #property (nonatomic, retain) NSArray *arrayData; and then you can use this array value in other view controllers also.
Properties replace the accessor methods for objects.
You can see my answer here. Pass value from one view controller to another
There are two options available storing and retrieving data in different view controllers.
1)NSUserDefaults is best option for storing data and accessing in any other view controllers.
The NSUserDefaults class provides convenience methods for accessing common types such as float, double, integer, Boolean.
A default object must be a property list, that is, an instance of (or for collections a combination of instances of): NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary.
This is very easy and best method for storing and retrieving data.
if you want to read about NSUserDefaults, here I am sharing document.
[NsuserDefaults Document.][1]
2) You would create properties when you want them to be accessible outside the class or other view controllers.
Create property in this way. #property (nonatomic, retain) NSArray *arrayData; and then you can use this array value in other view controllers also.
Properties replace the accessor methods for objects.
I think best way to use powerful features of blocks in below ways.
In ViewB.h
typedef void (^CompletionHandler)(BOOL myBool);
#interface ViewB : UIViewController {
CompletionHandler completionHandler;
}
- (void)dismissHandler:(CompletionHandler)handler;
In ViewB.m
- (void)dismissHandler:(CompletionHandler)handler {
completionHandler = handler;
}
- (IBAction)dismiss:(id)sender {
completionHandler (YES); // your yes no logic here
}
In ViewA.m
- (IBAction)showPopup:(id)sender {
ViewB *vc = [[ViewB alloc] init];
[self.view addSubview:vc.view];
[vc dismissHandler:^(BOOL myBool) {
if (myBool) {
//Do your work;
}
}];
}

Access class variable from another class

I have a UITabBarController that manages two ViewControllers. The first is a UIViewController that allows the user to change game settings. The second is a GLKViewController that runs the game simulation.
I'm trying to enable the Game ViewController to fetch the settings from the Settings ViewController. I have a Slider on the Settings View that represents "Speed".
I have a reference to the other controller, but I'm unable to expose the variable that backs my Slider properly.
SecondViewController.h
#interface SecondViewController : UIViewController{
IBOutlet UISlider * mySlider;
}
property (nonatomic,retain) IBOutlet UISlider * mySlider;
#end
SecondViewController.m
- (IBAction) mySliderWasMoved:(id)sender;
#implementation SecondViewController
#synthesize mySlider;
- (IBAction) mySliderWasMoved:(id)sender{
};
ThirdViewController.m
NSArray *tmpVCs = self.parentViewController.tabBarController.viewControllers;
UIViewController *tmpVC = [tmpVCs objectAtIndex:1]; //obtain handle to SecondViewController
//NSNumber *mySpeed = tmpVC.mySlider; //doesn't see mySlider
NSNumber *mySpeed = [tmpVC mySlider]; //doesn't see mySlider
I'm new to this, and there are many aspects of my project to learn - so I'm not trying to learn how to manage data at this time. I just need to know how to access an instance variable
As mention on the comments,
Use NSDefault to save the value on slider changed. On the very first time of loading your application, you will want to set a default value.
Use Singleton Object to store value.
We understand that, quoting from you " not trying to learn data persistence at this time. Nor do I need architecture direction.", but the rule of thumb here is that you probably will be able to access the instance variable in some way or the other but i think having the best approach will benefit you greatly.
Just my 2 cent.
Fort the benefit of others: I grabbed a handle to the other class, but I hadn't declared the return type as the correct type of class.
Replace:
UIViewController *tmpVC = [tmpVCs objectAtIndex:1];
With:
SecondViewController *tmpVC = [tmpVCs objectAtIndex:1];
Now I have access to the properties that are specific to the SecondViewController.

How to generate a generic table view controller?

I've created a custom TablePickerViewController which is a subclass of UITableViewController. I'm using this class to display a list of object of a custom type TablePickerItem.
I'm using TablePickerViewController multiple times in my iOS application to show different kinds of lists where the user has to pick an item -- and then another view controller MainViewController should react on this selection and do something.
I've created this protocol and created a delegate property in the TablePickerViewController:
#protocol TablePickerViewControllerDelegate <NSObject>
- (void)tablePickerViewController:(TablePickerViewController *)controller
didSelectItem:(TablePickerItem*)item;
#end
When I setup a new TablePickerViewController in MainViewController it is also set as delegate -- than it will be notified when the user taps an cell in the table view.
The problem is that my MainViewController will setup multiple TablePickerViewController with different data (TablePickerItem). How should I setup my MainViewController to handle these multiple TablePickerViewController? Events from each of them will results in calling to the same protocol-method in my MainViewController.
Further I need to get the element which the TablePickerItem represents, as I need to know for instance the elements ID when acting in the tablePickerViewController:didSelectItem method. Should I just handle this by adding something like #property (nonatomic) id element to the TablePickerItem and set the original object into this property then creating it?
Maybe someone can give me an example on how to create an generic table view controller, if my solutions seems being done in the wrong way.
I'm not entirely sure of your set up, but if you have multiple pickers that feedback to the main controller then you could just have a reference to the picker e.g.
// MainViewController.m
#interface MainViewController ()
#property (nonatomic, strong) TablePickerViewController *picker1;
#property (nonatomic, strong) TablePickerViewController *picker2;
// ... and so on. Obviously you know your problem domain so you can change
// the terrible naming above to something appropriate
#end
#implementation MainViewController
// ...
- (void)theMethodWhereYouSetUpYourPickers;
{
TablePickerViewController *picker1 = [[TablePickerViewController alloc] init];
picker1.delegate = self;
self.picker1 = picker1;
// ...
}
- (void)tablePickerViewController:(TablePickerViewController *)controller
didSelectItem:(TablePickerItem*)item;
{
if (controller == self.picker1) {
NSLog(#"Something was picked in picker 1 %#", item);
} else if (controller == self.picker2) {
NSLog(#"Something was picked in picker 2 %#", item);
}
}
// ...
#end