Delegation and Modal View Controllers - objective-c

According to the View Controller Programming Guide, delegation is the preferred method to dismiss a modal view.
Following Apple's own Recipe example, i have implemented the following, but keep getting warnings that the addNameController:didAddName method is not found...
NameDelegate.h
#protocol NameDelegate
- (void)addNameController:(AddName *)addNameController didAddName:(NSString *)name;
#end
AddName.h
#interface AddName : UIViewController {
UITextField *nameField;
id delegate;
}
- (IBAction)doneAction;
- (id)delegate;
- (void)setDelegate:(id)newDelegate;
#property (nonatomic, retain) IBOutlet UITextField *nameField;
#end
AddName.m
- (IBAction)doneAction {
[delegate addNameController:self didAddName:[nameField text]];
}
- (id)delegate {
return delegate;
}
- (void)setDelegate:(id)newDelegate {
delegate = newDelegate;
}
ItemViewController.h
#import "NameDelegate.h"
#interface ItemViewController : UITableViewController <NameDelegate>{
}
#end
ItemViewController.m
- (void)addItem:(id)sender {
AddName *addName = [[AddName alloc] init];
addName.delegate = self;
[self presentModalViewController:addName animated:YES];
}
- (void)addNameController:(AddName *)addNameController didAddName:(NSString *)name {
//Do other checks before dismiss...
[self dismissModalViewControllerAnimated:YES];
}
I think all the required elements are there and in the right place?
Thanks

You haven't specified that the delegate property of AddName has to conform to the NameDelegate protocol.
Use this code in AddName.h:
#import "NameDelegate.h"
#interface AddName : UIViewController {
UITextField *nameField;
id <NameDelegate> delegate;
}
#property(nonatomic, retain) IBOutlet UITextField *nameField;
#property(nonatomic, assign) id <NameDelegate> delegate;
- (IBAction)doneAction;
#end

Related

"missing setter or instance variable" log message occurs initializing NSWindowController with Objective-C in Xcode:

Here is an abstract I took from an app that already works in which a parent window and sheet are processed. The abstract here compiles, launches, and displays the parent window without having received the message I sent to its text field. The run produces 'missing setter or instance variable' log messages for the text field, the button, and the window itself. For some reason I have not gotten the parent window to be properly initialized, and I suspect I will have the same problem with the sheet window but I can't get past this problem to debug the rest of the app.
I believe I have omitted something fundamental in the process of connecting the window to its File's Owner, even though when looking at the connections inspector for the .xib it shows the custom class to be the ParentClass and the various connections are made. I can find no instructions in Apple's labyrinthine documentation nor here in StackOverflow to guide me through the connection process that would allow me to discover the part(s) I'm missing.
Anything you can offer will be gratefully studied.
ParentDelegate.h
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#class ParentClass;
#interface ParentDelegate : NSObject <NSApplicationDelegate>
#property (assign) IBOutlet NSWindow * window;
#property (weak, nonatomic) ParentClass * parentController;
#end
ParentDelegate.m
#import "ParentDelegate.h"
#implementation ParentDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { }
- (void)applicationDidBecomeActive:(NSNotification *)aNotification { }
- (void)applicationDidResignActive:(NSNotification *)aNotification { }
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication
{ return YES; }
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{ return NSTerminateNow; }
#end
ParentClass.h
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#interface ParentClass : NSWindowController {
}
#property (assign) IBOutlet NSWindow * window;
#property IBOutlet NSTextField * messageTextField;
#property IBOutlet NSButton * proceedButton;
#property (strong) NSMutableString * parentPropInfo;
- (IBAction) awakeFromNib;
- (IBAction) doProceed:(id)sender;
#end
ParentClass.m
#import "ParentClass.h"
#import "ParentDelegate.h"
#import "SheetClass.h"
#implementation ParentClass
ParentDelegate * parentDelegate;
SheetClass * sheetController;
- (IBAction)awakeFromNib {
parentDelegate = [NSApplication sharedApplication].delegate;
parentDelegate.parentController = self;
sheetController = [[SheetClass alloc] initWithWindowNibName: #"SheetClass"];
_messageTextField.stringValue = #"Click Proceed button";
}
- (IBAction)doProceed:(id)sender {
_parentPropInfo = #"Hello!".mutableCopy;
[NSApp runModalForWindow:sheetController.window];
// Sheet active now until it issues endModal, then:
_messageTextField.stringValue = sheetController.sheetPropInfo;
[NSApp endSheet: sheetController.window];
[sheetController.window orderOut:self];
}
#end
SheetClass.h
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#interface SheetClass : NSWindowController {
}
#property (assign) IBOutlet NSWindow * window;
#property (weak) IBOutlet NSTextField * propTextField;
#property (weak) IBOutlet NSButton * returnButton;
#property (strong) NSMutableString * sheetPropInfo;
- (IBAction)awakeFromNib;
- (IBAction)doReturn:(id)sender;
#end
SheetClass.m
#import "SheetClass.h"
#import "ParentClass.h"
#implementation SheetClass
ParentClass * parent;
- (IBAction)awakeFromNib {
parent.window = self.window.sheetParent;
_propTextField.stringValue = parent.parentPropInfo;
}
- (IBAction)doReturn:(id)sender {
_sheetPropInfo = #"Done!".mutableCopy;
[NSApp stopModal];
}
#end
Ultimately I would like to use this mini-app as a starting template for several other apps.

How to access view controller variables from the app delegate... and vice versa?

I would like the view controller to be able to access dog in the app delegate.
I would like the app delegate to be able to access mouse in the view controller.
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
{
int mouse; // <----------------
}
#end
- (void)viewDidLoad
{
[super viewDidLoad];
mouse = 12; // <-------------------
NSLog(#"viewDidLoad %d", dog); // <---------------
}
#import <UIKit/UIKit.h>
#class ViewController;
#interface AppDelegate : UIResponder <UIApplicationDelegate>
{
int dog; // <---------------
}
#property (strong, nonatomic) UIWindow *window;
#property (strong, nonatomic) ViewController *viewController;
#end
- (void)applicationWillResignActive:(UIApplication *)application
{
NSLog(#"applicationWillResignActive %d", mouse); // <--------------
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
dog = 77; // <---------------------
NSLog(#"applicationDidBecomeActive");
}
Part 1:
In the ViewController.h:
-(int)mouse; //add this before the #end
In the ViewController.m, add this method:
-(int)mouse
{
return mouse;
}
To access mouse from AppDelegate, use self.viewController.mouse
For example;
NSLog(#"ViewController mouse: %i", self.viewController.mouse);
Part2:
In the AppDelegate.h:
-(int)dog; //add this before the #end
In the AppDelegate.m, add this method:
-(int)dog
{
return dog;
}
In the ViewController.m:
#import "AppDelegate.h"
To access dog from ViewController, use this:
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSLog(#"dog from AppDelegate: %i", [appDelegate dog]); //etc.
In your view controller header file add mouse as a property:
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
{
NSInteger mouse; // <----------------
}
#property (nonatomic, assign) NSInteger mouse;
#end
Synthesize the property in your view controller implementation just below the #implementation line:
#synthesize mouse;
In your app delegate add dog as a property:
#interface AppDelegate : UIResponder <UIApplicationDelegate>
{
NSInteger dog; // <---------------
}
#property (strong, nonatomic) UIWindow *window;
#property (strong, nonatomic) ViewController *viewController;
#property (nonatomic, assign) NSInteger dog;
#end
Also synthesize dog in your app delegate implementation.
Now in your app delegate, assuming you have a reference to your view controller, you can access mouse like this:
viewController.mouse = 13;
You can do the same thing with your app delegate class, which can be accessed from any view controller using (assuming the name of your app delegate class is AppDelegate):
((AppDelegate *)([UIApplication sharedApplication].delegate)).dog = 13;
I would recommend that you use also use NSInteger instead of int.

UITextField: set text from a label

I'm trying to write my first iPad app using Xcode 4: it's based on "Tabbed Application" template with two views. On the first view, user selects a City (label displays selection) and on the second wiew there is a IBOutletCollection(UITextField) NSArray *playersData. I want to set the city selected on first view as a default city on second view. I have checked storyboard connections and they seem to be ok.
I get nothing. Any idea?
First view :
#import
#interface pruebaF1FirstViewController : UIViewController
- (IBAction)selectCity:(UIButton *)sender;
#property (weak, nonatomic) IBOutlet UILabel *citySelected;
#end
First view implementation :
#import "pruebaF1FirstViewController.h"
#import "pruebaF1SecondViewController.h"
#interface pruebaF1FirstViewController ()
#property pruebaF1SecondViewController *secondView;
#end
#implementation pruebaF1FirstViewController
#synthesize citySelected;
#synthesize secondView;
- (void)viewDidLoad
...
- (IBAction)selectCity:(UIButton *)sender {
NSString *currentCity =[sender currentTitle];
citySelected.text=#"";
citySelected.text =[citySelected.text stringByAppendingString:currentCity];
/*writes null*/
secondView.defaultCity.text=[NSString stringWithFormat:#"%#" ,currentCity];
NSLog(#"%#",secondView.defaultCity.text);
}
#end
Second view header
#import <UIKit/UIKit.h>
#interface pruebaF1SecondViewController : UIViewController <UITextFieldDelegate>
#property (strong, nonatomic) IBOutletCollection(UITextField) NSArray *playersData;
#property (retain, nonatomic) IBOutlet UITextField *defaultCity;
- (IBAction)eraseData:(UIButton *)sender;
- (IBAction)savePlayersData:(UIButton *)sender;
- (IBAction)termsPopUp:(UIButton *)sender;
/*- (void)writeDefaultCity:(NSString *)currentCity;*/
#end
Second view implementation
#import "pruebaF1SecondViewController.h"
#import "pruebaF1FirstViewController.h"
#interface pruebaF1SecondViewController ()
#property (nonatomic, strong)pruebaF1FirstViewController *prueba;
#end
#implementation pruebaF1SecondViewController
#synthesize playersData;
#synthesize defaultCity;
#synthesize prueba;
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)viewDidUnload
{
[self setPlayersData:nil];
[self setDefaultCity:nil];
[super viewDidUnload];
}
/* Return or Done button dismiss keyboard*/
-(BOOL)textFieldShouldReturn:(UITextField *)boxes
{
for (UITextField *boxes in playersData) {
[boxes resignFirstResponder];
}
return YES;
}
....
/*Trying to get city's name from first view in two ways*/
/*-(void)setDefaultCity
{
NSString *defecto=prueba.citySelected.text;
self.defaultCity.text = defecto;
}*/
- (IBAction)eraseData:(UIButton *)sender {
for (UITextField *boxes in playersData) {
boxes.text = #" ";
}
}
/*
-(void)writeDefaultCity:(NSString *)currentCity
{
defaultCity.text =[NSString stringWithFormat:#"%#" ,currentCity];
NSLog(#"Ciudad elegida: %#",currentCity);
}*/
....
#end
Views are generally not loaded until they are displayed on the device, and may be unloaded at any time when not visible to save memory. This means that when you're setting the 'text' property of the 'defaultCity', that label has not yet been created.
Instead you should add a NSString * property to pruebaF1SecondViewController and set the value of the defaultCity label in viewDidLoad:
In the header:
#property (nonatomic, weak) UILabel *defaultCityLabel;
#property (nonatomic, strong) NSString *defaultCity;
In the implementation
- (void)viewDidLoad
{
[super viewDidLoad];
defaultCityLabel.text = defaultCity;
}
And in the first view controller, assign the string value instead of the label value.

Delegate to 'Parent' ViewController best practice

The setup:
PickerView (spinSelector) and label (chosenItem) added to ViewController.
Created separate delegate class files (SpinDelegate m&h) for the PickerView delegate.
Created instance of the delegate (SpinDelegate *mySpinDelegate)
Assigned delegate property to delegate instance
ViewController.h
#interface ViewController : UIViewController
{
SpinDelegate *mySpinDelegate;
}
#property (nonatomic, weak) IBOutlet UILabel *chosenItem;
#property (nonatomic, strong) IBOutlet UIPickerView *spinSelector;
ViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
mySpinDelegate=[[SpinDelegate alloc]init];
self.spinSelector.delegate=mySpinDelegate;
self.spinSelector.dataSource=mySpinDelegate;
}
SpinDelegate.h
#interface SpinDelegate : UIViewController <UIPickerViewDataSource, UIPickerViewDelegate>
{
ChoiceData *choiceItems;
}
#end
SpinDelegate.m
#pragma mark - PickerView Delegate
- (NSString *)pickerView:(UIPickerView *)pickerView
titleForRow:(NSInteger)row
forComponent:(NSInteger)component{
return [choiceItems.choiceList objectAtIndex:row];
}
Next is to use the method:
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component;
This is part of the UIPickerViewDelegate protocol. Using this, to simply change the UILabel (chosenItem) in the ViewController to value of row. Something like "The picked item is %i",row
I've read and searched through a ton of posts and questions on UIViewController to UIViewController messaging and looked at protocol/delegate, singleton, NSNotificationCenter... it just seems to me that there is a syntax I am missing to address the parent/super? The ViewController created the instance of the delegate, doesn't the delegate have scope?
Please educate me on this. : )
You can do something like this:
ViewController.h
#import "SpinViewController.h"
#interface ViewController : UIViewController<SpinViewControllerDelegate>
{
}
#property (nonatomic, weak) IBOutlet UILabel *chosenItem;
#property (nonatomic, strong) IBOutlet UIPickerView *spinSelector;
ViewController.m
- (void)someFunction
{
mySpinViewController=[[SpinViewController alloc]init];
mySpinViewController.delegate=self;
// show or present mySpinViewController
}
//implement the followed protocol's method
-(void) optionSelected:(NSString*)cellValue{
}
SpinViewController.h
#protocol SpinViewControllerDelegate <NSObject>
#optional
-(void) optionSelected:(NSString*)cellValue;
#end
#interface SpinViewController : UIViewController <UIPickerViewDataSource, UIPickerViewDelegate>
{
ChoiceData *choiceItems;
}
#property (nonatomic,retain)id <SpinViewControllerDelegate> delegate;
#end
SpinViewController.m
-(void) pickerValueSelectedOrSimilarFn:(NSString*)cellValue{
// this is how you give a callback to classes following the protocol
[self.delegate optionSelected:cellValue];
}

Perform segue from delegate method

I have a segmented control which when clicked the user will be forced to enter a password.
After a successful check the modal screen should dismiss and in the controller with the segmented control a method is called and a segue should be performed to another controller.
The segue itself works but not after the login check. I think this is a problem with the "self"...
Code of delegate class:
#protocol LoginUserControllerDelegate
- (void)proceedAfterLogin:(BOOL)proceedToNextController;
#end
#interface LoginUserController : UIViewController {
IBOutlet UITextField *loginPWD;
id<LoginUserControllerDelegate> _delegate;
}
#property (nonatomic, retain) UITextField *loginPWD;
#property (nonatomic, assign) id<LoginUserControllerDelegate> delegate;
- (void)checkLogindata; // checks for correct password
#end
#implementation LoginUserController
#synthesize loginPWD;
#synthesize delegate = _delegate;
- (void)checkLogindata {
...
if ([tempDBPWD isEqualToString:tempLoginPWD]) {
if (_delegate != nil) {
[self dismissModalViewControllerAnimated:YES];
[_delegate proceedAfterLogin:YES];
}
}
...
}
#end
The segmented controller with the method:
#interface StartViewController : UIViewController <LoginUserControllerDelegate> {
UISegmentedControl *segmentedControl;
LoginUserController *_loginUserController;
}
#property (nonatomic,retain) IBOutlet UISegmentedControl *segmentedControl;
#property (nonatomic, retain) LoginUserController *loginUserController;
- (IBAction)segmentedControlIndexChanged:(id)sender;
#end
#implementation StartViewController
#synthesize segmentedControl;
#synthesize loginUserController = _loginUserController;
- (IBAction)segmentedControlIndexChanged:(id)sender
{
if (self.segmentedControl.selectedSegmentIndex == 0) {
// get delegate
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
UIStoryboard *storyboard = appDelegate.window.rootViewController.storyboard;
LoginUserController *loginUserControllerView = [storyboard instantiateViewControllerWithIdentifier:#"LoginUserViewID"];
loginUserControllerView.delegate = self;
// now show screen
[self presentModalViewController:loginUserControllerView animated:YES];
} else {
}
}
- (void)proceedAfterLogin:(BOOL)proceedToNextController
{
if (proceedToNextController) {
// yes, correct login now perform the segue to the controller
[[self segmentedControl] setSelectedSegmentIndex:UISegmentedControlNoSegment];
[self performSegueWithIdentifier:#"NextControllerSegueID" sender:self];
}
}
#end
I think the [self performSegueWithIdentifier:#"NextControllerSegueID" sender:self]; is the problem, because self is not correct. Is this where the error arises and how can I achieved my desired behaviour?
You're close, but you need to return to the delegate, and then call dismissModalViewControllerAnimated:YES. A modal controller can't dismiss itself, so you need to do your login, call back to the delegate, and have the delegate dismiss the modal controller.