I work on a project for iPad with Xcode 4.
I have a main view controller with many UITextField.
The TextFieldDelegate is a separate class in a separate file.
How can I refer, from TextFieldDelegate to a property (to a UITextField) of the main view controller (for example assign a value to a double)?
Thank you.
In most cases, if you want to use a separate delegate you should not need more information than what is passed to the delegate (the method's parameters). However, if you don't want to use your MainViewController as a delegate for your UITextField, you can initialize your TextFieldDelegate in your MainViewController instance and pass it the MainViewController instance.
For example you could have:
#import "MainViewController.h"
#interface TextFieldDelegate<UITextFieldDelegate> {
MainViewController* mainViewController;
}
#property(nonatomic,retain) MainViewController* mainViewController;
-(id)initWithController:(MainViewController*)controller;
#end
#implementation TextFieldDelegate
#synthesize mainViewController;
-(id)initWithController:(MainViewController*)controller {
if(self = [super init]) {
//some stuff
self.mainViewController = controller;
}
return self;
}
#end
Then in your MainViewController:
TextFieldDelegate tfd = [[TextFieldDelegate alloc] initWithController:self];
You just need to set the TextFields' delegate to tfd and you should be able to reference the MainViewController properties from the TextFieldDelegate instance. It's also possible to initiate it somewhere else, as long as you send the MainViewController instance to your TextFieldDelegate instance.
Edit: woups forgot a few '*'
Related
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).
I'm new to objective-c and, maybe I haven't grassped the concept of delegation very clearly yet, but i hope to do it by using it. I'm trying to implement a delegation in my app.
Idea is that i have class TableViewController which has NSMutableArray used for TableView initialization. I need to reinitialize this Array from my DropDown class. I'v tried to do that using delegation but failed to do it yet, maybe there is something wrong with it. I could pass TableViewController to DropDown class and edit the table via object. But i'd like to get it done using delegation.
Here is my TableViewController.h
#protocol TableViewControllerdelegate;
#interface TableViewController : UIViewController<UITableViewDataSource,UITableViewDelegate,MFMessageComposeViewControllerDelegate>
{
ControllerType controllerType;
}
#property (retain, nonatomic) IBOutlet UITableView *tableView;
#property (retain, nonatomic) NSMutableArray *dataArray;
#property (retain, nonatomic) NSArray *imageArray;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil andType:(ControllerType)type;
- (void)sendSMS: (NSString *) sms;
#end;
Here is my DropDown.h
#import "TableViewController.h"
#interface DropDownExample : UITableViewController <VPPDropDownDelegate, UIActionSheetDelegate> {
#private
VPPDropDown *_dropDownSelection;
VPPDropDown *_dropDownSelection1;
VPPDropDown *_dropDownSelection2;
VPPDropDown *_dropDownSelection3;
VPPDropDown *_dropDownSelection4;
VPPDropDown *_dropDownDisclosure;
VPPDropDown *_msg;
VPPDropDown *_dropDownCustom;
NSIndexPath *_ipToDeselect;
}
+ (bool) uncheck:(UITableViewCell *) cell andData:(NSString *) data;
- (void)reloadData;
#end
And this is how i try to edit my tableview object array
TableViewController *newControll = (TableViewController*)[UIApplication sharedApplication].delegate;
NSMutableArray *arrayWithInfo = [[NSMutableArray alloc] initWithObjects:AMLocalizedString(#"Status", nil),AMLocalizedString(#"Call", nil),AMLocalizedString(#"Location", nil),AMLocalizedString(#"Control", nil),AMLocalizedString(#"Sim", nil),AMLocalizedString(#"Object", nil),AMLocalizedString(#"Info", nil),nil];
newControll.dataArray = arrayWithInfo;
[arrayWithInfo release];
[newControll.tableView reloadData];
I get it running, but it get's '-[AppDelegate setDataArray:]: unrecognized selector sent to instance after reaching this code.
OK, I am not sure if I got this right but it finally clicked for me what delegation is and why I need it. Hopefully you'll understand too once you read through my scenario.
History
Previously, in my UITabBar app, I wanted to show a custom form view overlaid on top of my view controller to enter name and email.
Later I also needed to show the same custom overlay on top of another view controller on another tab.
At the time I didn't really know what delegation was for, so the first method I used to tackle this problem was NSNotificationCenter. I duplicated the same code to my second view controller and hooked it up to a button press event.
On pressing a button on the second view controller on another tab, it certainly showed my custom overlay, just like my first view controller.
However, this is where the problem starts.
The Problem
I needed to close my custom form view. So using NSNotificationCenter, I posted a notification and the listener callback method for the notification was told to close my custom view.
The problem was, using NSNotificationCenter, all listeners both in my first tab and my second tab responded to the posted notification and as a result, instead of closing just the custom form view overlaid on top of my second view controller, it closed ALL my custom view, regardless of where the custom view was opened from.
What I wanted was when I tap on the "X" button to close my custom form view, I only want it to close it for that single instance of the custom view, not all the other ones I had opened.
The Solution: Delegation
This is where it finally clicked for me - delegation.
With delegation, I tell each instance of my custom form view who the delegate was, and if I was to tap on the "X" button to close my custom view, it only close it for that single instance that was opened, all the other view controllers were untouched.
Some Code
Right, down to some code.
Not sure if this is the best way to do it (correct me if I am wrong) but this is how I do it:
// ------------------------------------------------------------
// Custom Form class .h file
// ------------------------------------------------------------
#protocol MyCustomFormDelegate <NSObject>
// if you don't put a #optional before any method, then they become required
// in other words, you must implement these methods
-(void)sendButtonPressed;
-(void)closeButtonPressed;
// example: these two methods here does not need to be implemented
#optional
-(void)optionalMethod1;
-(void)optioinalMethod2;
#end
#interface MyCustomFormView : UIView
{
...
id<MyCustomFormDelegate> delegate;
}
...
#property (nonatomic, retain) id<MyCustomFormDelegate> delegate;
#end
// ------------------------------------------------------------
// Custom Form class .m file
// ------------------------------------------------------------
...
#implementation TruckPickerView
#synthesize delegate;
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self)
{
...
[btnSend addTarget:self selector:#selector(sendEmail) forControlEvent:UIControlEventTouchUpInside];
...
[btnClose addTarget:self selector:#selector(closeForm) forControlEvent:UIControlEventTouchUpInside];
}
return self;
}
-(void)sendEmail
{
// code sends email
...
// ------------------------------------------------------------
// tell the delegate to execute the delegate callback method
//
// note: the implementation will be defined in the
// view controller (see below)
// ------------------------------------------------------------
[delegate sendButtonPressed];
}
-(void)closeForm
{
// ------------------------------------------------------------
// tell the delegate to execute the delgate callback method
//
// note: the implementation will be defined in the
// view controller (see below)
// ------------------------------------------------------------
[delegate closeButtonPressed];
}
// ------------------------------------------------------------
// view controller .h file
// ------------------------------------------------------------
#import "MyCustomFormView.h"
// conform to our delegate protocol
#interface MyViewController <MyCustomFormDelegate>
{
...
// create a single instance of our custom view
MyCustomFormView *customForm;
}
#property (nonatomic, retain) MyCustomFormView *customForm;
// ------------------------------------------------------------
// view controller .m file
// ------------------------------------------------------------
#synthesize customForm;
-(void)viewDidLoad
{
customForm = [[MyCustomFormView alloc] initWithFrame:....];
// tell our custom form this view controller is the delegate
customForm.delegate = self;
// only show the custom form when user tap on the designated button
customForm.hidden = YES;
[self.view addSubview:customForm];
}
-(void)dealloc
{
...
[customForm release];
[super dealloc];
}
// helper method to show and hide the custom form
-(void)showForm
{
customForm.hidden = NO;
}
-(void)hideForm
{
customForm.hidden = YES;
}
// ------------------------------------------------------------
// implement the two defined required delegate methods
// ------------------------------------------------------------
-(void)sendButtonPressed
{
...
// email has been sent, do something then close
// the custom form view afterwards
...
[self hideForm];
}
-(void)closeButtonPressed
{
// Don't send email, just close the custom form view
[self hideForm];
}
You get that error, because (as the error says) you're sending a setDataArray: message to your app delegate (the AppDelegate class).
[UIApplication sharedApplication].delegate;
This will return the delegate of you app. There are a couple of ways to find out which class is your app's delegate, but usually it's called AppDelegate (as in your case) and it's implementing the UIApplicationDelegate protocol too.
You can't simply cast that to a completely different class. If your app delegate has an ivar or property of type TableViewController you have to use accessors to get it. If it's a property, you can use the dot notation. If it's an ivar, you can either implement a getter method that returns the ivar, or make it a property instead.
// assuming your app delegate has a TableViewController property called myTableViewController.
AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
TableViewController *tableViewController = appDelegate.myTableViewController;
This will fix the error, but your use of the delegate pattern is wrong too. I don't see where you're using any custom delegates. You forward declare a TableViewControllerdelegate protocol, but I don't see any declaration of it, or I don't see where you're trying to use it.
I have a Subclass of NSObject in which I want to call IMMEDIATELY (a sort of -(void)viewDidLoad) a method (in this case to load a MkMapView): what's the better way to do this? I think I cant use viewDidLoad, so can I use performSelector?
SubClass.h
#interface Mysubclass : NSObject <MKMapViewDelegate> {
}
SubClass.m (1st alternative)
-(id)init{
self = [super init];
if ( self != nil ) {
// THE CODE TO INITIALIZE MKMAPVIEW
}
return self
}
OR
SubClass.m (2nd alternative)
-(id)init{
[self performSelector:#selector(myMethod)];
return self;
}
-myMethod{
// THE CODE TO INITIALIZE MKMAPVIEW
}
What's the better (or correct) alternative? Its possible to avoid -(id)init? Or everytime I add a subclass, to call a method I have to write it into -(id)init? Thank you!
There is no reason to use -performSelector: in this context. If you want to add a method that initializes the MKMapView when your object is created, call the method from within the if (self) block:
- (id)init
{
self = [super init];
if (self) {
[self setupMapView];
}
return self;
}
- (void)setupMapView
{
// THE CODE TO INITIALIZE MKMAPVIEW
}
It is a matter of personal preference/style whether to have a second method -setupMapView or to simply leave the code for setting up the MKMapView in the if (self) block of the -init method or to break the code off into a second method -setupMapView called from -init.
That being said, it sounds like other things may be off with your setup. Your MKMapView should [most likely] be within a UIViewController subclass (which will probably have an associated XIB), so you will be have access to -viewDidLoad. Note that your UIViewController subclass will serve as the delegate to your MKMapView.
Update 1
In your UIViewController subclass instance (I'll assume you called it ViewController, you should have an IBOutlet to an MKMapView object. Do this in ViewController.h either by (1) adding an instance variable
#interface ViewController : UIViewController
{
IBOutlet MKMapView *myMap;
}
#end
or by (2) adding a property
#interface ViewController : UIViewController
#property (nonatomic, strong, readwrite) IBOutlet MKMapView *myMap;
#end
Now open ViewController.xib in Interface Builder. You should have an MKMapView inside the view. If you don't already, add one from the Object Library. Right click on File's Owner. Locate the row with the item myMap. Drag from the circle on the right end of the row to the MKMapView in the visible view.
Your ViewController class now has an outlet to the MKMapView. You will be able to send messages to the MKMapView subview of your view controllers view after it has been loaded.
You should have a property or an instance variable for your SubClass instance so that it doesn't get destroyed as soon as -viewDidLoad returns. Do this again by either adding an instance variable to ViewController.h
#interface ViewController : UIViewController
{
IBOutlet MKMapView *myMap;
SubClass *istance;
}
#end
or by adding a property
#interface ViewController : UIViewController
#property (nonatomic, strong, readwrite) IBOutlet MKMapView *myMap;
#property (nonatomic, strong, readwrite) SubClass *istance;
#end
Now, in ViewController.m, you need to define -viewDidLoad so that self.istance is set as the delegate of self.myMap. In the comments, I had suggested creating your own initializer -initWithMapView:. If you plan on having SubClass do some extensive set-up of your MKMapView, that makes sense. If you just want SubClass to be the delegate of the MKMapView, there's no need for such a method.
Let's consider both cases:
(1) using a method -[SubClass initWithMapView:]:
In ViewController.m you'll have (within the #implementation of ViewController)
- (void)viewDidLoad
{
self.istance = [[SubClass alloc] initWithMapView:self.myMap];
}
In SubClass.h you'll have (within the #interface of SubClass)
- (id)initWithMapView:(MKMapView *)mapView;
#property (nonatomic, weak, readwrite) MKMapView *mapView;
In SubClass.m you'll have (within the #implementation of SubClass)
- (id)initWithMapView:(MKMapView *)mapView
{
self = [super init];
if (self) {
self.mapView = mapView;
self.mapView.delegate = self;
//more setup of mapView.
}
return self;
}
(2) using -[SubClass init]:
Instead, in ViewController.m you'll have
- (void)viewDidLoad
{
self.istance = [[SubClass alloc] init];
self.myMap.delegate = self.istance;
}
I have two classes:
A UIViewController and a class that's subclassing NSObject that acts as a downloading helper class called OfficesParser. OfficesParser is using ASIHTTPRequest and I set the delegate for the download requests to be my UIViewController.
EDIT: Interface for the UIViewController:
#interface OfficesViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, ASIHTTPRequestDelegate> {
OfficesParser *officesParser;
}
#property (nonatomic, retain) OfficesParser *officesParser;
#end
In the UIViewController implementation I set up the OfficesParser like so:
- (void)viewDidLoad {
[super viewDidLoad];
self.officesParser = [[[OfficesParser alloc] init] autorelease]; // self.officesParser is retained
}
Then before the view appears I call my my OfficesParser object to download some data for me:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.officesParser doNetworkOperations];
}
Also in my UIViewController I have setup the appropriate delegate methods to deal with the data after it has been downloaded. In particular I'm interested in this delegate method that will run after all the data has been processed in my download queue. I can see that the delegate method is running from the log. But for some reason self.officesParser in here is nil.
- (void)queueFinished:(ASINetworkQueue *)queue {
DLog(#"queueFinished running");
[self.officesParser test]; // test will not get called because self.officesParser is nil
}
I have a little problem with the application I currenty work on. I create a simpliest project to illustrate my problem.
So, I create a "Navigate-Base Application". I add an other UITableViewController named TableViewController (the one which is created with the project is named RootViewController). I create an instance of TableViewController when I touch a line in the RootViewController.
I create a custom class named "MyCustomClass".
MyCustomClass.h (full code) :
#import <Foundation/Foundation.h>
#interface MyCustomClass : NSObject {
NSString *name;
}
#property (nonatomic, retain) NSString * name;
#end
MyCustomClass.m (full code) :
#import "MyCustomClass.h"
#implementation MyCustomClass
#dynamic name;
#end
I had a MyCustomClass attibute in TableViewController class.
TableViewController.h (full code) :
#import <UIKit/UIKit.h>
#import "MyCustomClass.h"
#interface TableViewController : UITableViewController {
MyCustomClass *aCustomObject;
}
#property (nonatomic, retain) MyCustomClass *aCustomObject;
#end
At the load of TableViewController, I try to display aCustomObject's content.
TableViewController.m (top of the file and what I modify in the template's file) :
#import "TableViewController.h"
#implementation TableViewController
#synthesize aCustomObject;
#pragma mark -
#pragma mark View lifecycle
- (void)viewDidLoad {
[super viewDidLoad];
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
NSLog(#"Name : %#",self.aCustomObject.name);
}
Before, I create and give a value to aCustomObject.name in RootViewController :
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
TableViewController *detailViewController = [[TableViewController alloc] initWithNibName:#"TableViewController" bundle:nil];
detailViewController.aCustomObject.name = #"The Name";
[self.navigationController pushViewController:detailViewController animated:YES];
}
Console said :
2011-06-22 07:21:11.087
MyTestApp[12822:207] Name : (null)
I think it's a stupid thing but I don't find myself after hours of try.
Thanks a lot and excuse me for my english mistakes,
You forget to initialize your custom object in the tableViewController's viewDidLoad Method.
Try this.
- (void)viewDidLoad {
[super viewDidLoad];
if(aCustomObject == nil){
self.aCustomObject = [[[MyCustomClass alloc] init] autoRelease];
}
self.aCustomObject.name = #"";
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
//this will show empty here.
NSLog(#"Name : %#",self.aCustomObject.name);
}
You use the #dynamic keyword to tell
the compiler that you will fulfill the
API contract implied by a property
either by providing method
implementations directly or at runtime
using other mechanisms such as dynamic
loading of code or dynamic method
resolution. It suppresses the warnings
that the compiler would otherwise
generate if it can’t find suitable
implementations. You should use it
only if you know that the methods will
be available at runtime.
from Apple Documentation
You are claiming in the question that you included full source for MyCustomClass.m. Where did you implement the getter and setter for the property? If you want the compiler to generate the methods for you, you should use
#synthesize name;