I am writing an application that needs to interfaces with different backend systems. I decided to use a Protocol in order to abstract my backend Classes. I created a nib called LoginViewController that contains an "External Object" reference of type "NSObject", and wired it to the systemDelegate outlet in my LoginViewController.
#interface LoginViewController : UIViewController {
}
#property (nonatomic, retain) IBOutlet UITextField *usernameTextView;
#property (nonatomic, retain) IBOutlet UIImageView *captchaImageView;
#property (nonatomic, retain) IBOutlet UITextField *captchaTextView;
#property (nonatomic, retain) IBOutlet NSObject <BackEndSystemDelegate> *systemDelegate;
- (IBAction) submitCaptcha:(id) sender;
- (IBAction)dismissKeyboard: (id)sender;
- (IBAction) animateViewUp: (id) sender;
- (IBAction) animateViewDown: (id) sender;
- (void) animateViewOnYAxis: (int) offset;
- (void) loadCaptchaImage;
#end
I instanciate the LoginViewController in my application delegate, then try to load the nib with the external object reference. My code calls the loadNibNamed and crashes without a stack trace. I do not reach the NSLog statements after the invocation:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSObject <BackEndSystemDelegate> *systemDelegate = [[ACMEBackEndSystemDelegate alloc] init];
// Init LoginView, and load nib with systemDelegate
self.viewController = [[LoginViewController alloc] init];
NSDictionary *proxies = [NSDictionary dictionaryWithObject:systemDelegate forKey:#"systemDelegate"];
NSDictionary *options = [NSDictionary dictionaryWithObject:proxies forKey:UINibExternalObjects];
NSArray *toplevelobjects = [[NSBundle mainBundle] loadNibNamed:#"LoginViewController"
owner:self.viewController
options:options];
if (toplevelobjects) {
NSLog(#"toplevelobjects is nil");
} else {
NSLog(#"toplevelobjects count %d", [toplevelobjects count]);
}
NSLog(#"Controller: %#, View: %#", viewController, viewController.view);
// Add the view controller's view to the window and display.
[window addSubview:viewController.view];
[window makeKeyAndVisible];
return YES;
}
I am at a loss trying to figure this out. Any help would be appreciated.
Thanks,
J Garcia
A few comments:
Generally you refer to a delegate object using the pattern id<SomeProtocolName> and not NSObject<SomeProtocolName>
You alloc your systemDelegate variable but never release it. This is a memory leak.
If the ACMEBackEndSystemDelegate class implements the BackEndSystemDelegate protocol, it is sufficient to allocate it like ACMEBackEndSystemDelegate* systemDelegate = [[ACMEBackEndSystemDelegate alloc] init];
Now, as for your crash, you said there's a crash on this line:
NSArray *toplevelobjects = [[NSBundle mainBundle] loadNibNamed:#"LoginViewController"
owner:self.viewController
options:options];
I assume you have a .xib called LoginViewController.xib. Open it up. That is the class type set for "File's Owner"? Is it LoginViewController? If not, set it. Now check the view outlet property. Is it set to a UIView in the .xib (perhaps a top-level UIView object)? If not, set it.
Related
I am not getting an error now but my delegate is not working. I am making a custom keyboard so i have the UIViewController and a UIView. I want the UIView to call the sendKeyboardShortCut method in the UIViewController. The sendKeyboardShortCut method is not being called. Thank You
//ViewController .h
#import "KeyboardExtension.h"
#interface PageViewController : UIViewController <UITextViewDelegate,sendKeyboardShortCutDelegate> {
KeyboardExtension *inputAccView;
}
-(void)sendKeyboardShortCut:(NSString*)shortCut;
#property (nonatomic, assign) IBOutlet UITextView *tv;
#end
//ViewController .m
#implementation PageViewController
#synthesize tv;
- (void)viewWillAppear:(BOOL)animated
{
tv.delegate = self;
}
-(void)createInputAccessoryView{
inputAccView = [[[KeyboardExtension alloc] init]autorelease];
inputAccView.delegate = self;
NSArray *nibObjects = [[NSBundle mainBundle] loadNibNamed:#"KeyboardExtension" owner:self options:nil];
inputAccView = [nibObjects objectAtIndex:0];
}
-(void)textViewDidBeginEditing:(UITextView *)textView{
[self createInputAccessoryView];
[textView setInputAccessoryView:inputAccView];
}
-(void)sendKeyboardShortCut:(NSString*)shortCut{
if ([shortCut isEqualToString:#"dash"] ) {
NSRange range = tv.selectedRange;
if((range.location+range.length)<=tv.text.length)
{
NSString * before = [tv.text substringToIndex:range.location];
NSString * after = [tv.text substringFromIndex:range.location+range.length];
tv.text = [NSString stringWithFormat:#"%#-%#",before,after];
}
}
}
#end
//keyboard view .h
#import <UIKit/UIKit.h>
#protocol sendKeyboardShortCutDelegate <NSObject>
-(void)sendKeyboardShortCut:(NSString*)shortCut;
#end
#interface KeyboardExtension : UIView
-(IBAction)dash:(id)sender;
#property(nonatomic,assign) id<sendKeyboardShortCutDelegate>delegate;
#end
//keyboard view .m
#import "KeyboardExtension.h"
#implementation KeyboardExtension
#synthesize delegate;
-(IBAction)dash:(id)sender{[delegate sendKeyboardShortCut:#"dash"];}
#end
I suspect that:
1) your button's target is the nib's file owner and not KeyboardExtension
2) your action is setup not to send the sender, so UIKit calls -comma instead of -comma:
Also, the following code is highly suspect:
inputAccView = [[[KeyboardExtension alloc] init]autorelease];
inputAccView.delegate = self;
NSArray *nibObjects = [[NSBundle mainBundle] loadNibNamed:#"KeyboardExtension" owner:self options:nil];
inputAccView = [nibObjects objectAtIndex:0];
A) you allocate an object that you barely use as you replace it immediately with the nib's object
B) the way to extract the object from the nib ([nibObjects objectAtIndex:0]) is really not robust. You should create an IBOutlet in PageViewController and link it in the nib
I think you should revisit the way you use nibs here.
A final (unrelated) point: why are you using [NSString stringWithFormat:#"..."] instead of just #"..."?
I'm having some trouble understanding how variable values are passed from one view to another. I have a UITextField in the firstview that the user enters a number into. When the user taps a button, that number is multiplied by 2 and the result is displayed on a UILabel in the second view. This is what I have thus far
FirstViewController.h
#interface FirstViewController : UIViewController{
UITextField *numberTextField;
NSNumber *aNumber;
}
#property (nonatomic, retain) IBOutlet UITextField *numberTextField;
#property (nonatomic) NSNumber *aNumber;
-(IBAction)calculate;
#end
FirstViewController.m
#implementation FirstViewController
#synthesize numberTextField, aNumber;
-(double)doubleNumber{
double number = [numberTextField.text doubleValue] * 2;
return number;
}
-(IBAction)calculate{
self.aNumber = [NSNumber numberWithDouble:[self doubleNumber]];
}
//more default code continues below
SecondViewController.h
#import "FirstViewController.h"
#interface SecondViewController : FirstViewController{
UILabel *numberLabel;
}
#property (nonatomic, retain) IBOutlet UILabel *numberLabel;
#end
SecondViewController.m
#implementation SecondViewController
#synthesize numberLabel;
- (void)viewDidLoad
{
[super viewDidLoad];
numberLabel.text = [NSString stringWithFormat:#"%#",aNumber];
}
Best and Easy Way to store value globally
set you object with your keyword
[[NSUserDefaults standardUserDefaults] setObject:#"Ajay" forKey:#"name"];
than get that object any where in you project
NSString *name = [[NSUserDefaults standardUserDefaults] objectForKey:#"name"];
You should accomplish what you want by using a segue. Create a segue in your storyboard and then call it in your firstviewcontroller with - (void)performSegueWithIdentifier:(NSString *)identifier sender:(id)sender. Then to pass data, import your secondviewcontroller.h file with a property for the value you want to pass and setup - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender.
In that method you can pass things by using [segue.destinationViewController ###call the setter for the property in your secondViewController.h file###];
If this isn't clear, or you need more help just let me know.
I'm assuming that the FirstViewController is instantiating the SecondViewController. If that is so, then you just pass aNumber to the SecondViewController using an additional property:
// add an additional property to the SecondViewController
#property (nonatomic, retain) NSNumber *aNumber;
When you instantiate the SecondViewController inside the FirstViewController, you just pass that value to the SecondViewController before you load it:
// inside FirstViewController
SecondViewController *secondViewController = [[SecondViewController alloc] initWithNibName:#"SecondViewController" bundle:nil];
secondViewController.aNumber = aNumber;
// inside SecondViewController
- (void)viewDidLoad
{
[super viewDidLoad];
numberLabel.text = self.aNumber;
}
Mmm... Pay attention to not confuse views and viewControllers!!!
A viewController can manage more than a view. For example in your code you have a UITextField, a UILabel and probably a UIButton. These are all views that are managed by two viewsController (FirstViewController and SecondViewController).
As long as you have so few views to work with you can use just one viewController and pass the value you want to your UILabel directly:
- (void)calculateAndPassValue
{
aNumber = [NSNumber numberWithDouble:[self doubleNumber]];
numberLabel.text = [NSString stringWithFormat:#"%#",aNumber];
}
Otherwise, if your goal is passing variable values from one viewController to another. Well... There are many ways you can obtain that, for example:
Creating an ivar.
Using a singleton.
Saving your data in NSUserDefault.
Creating a database on disk.
First and second cases are good if you need to manage your data while your app is running. Third and fourth if you want to memorize your data for future use and retrieve them at next start up.
Try to search keys like ivar, singleton, NSUserDefault and you'll find many discussions and lines of sample code.
I have a project on iPhone with iOS4.
A instance variable of app delegate is a dictionary with global readonly data loaded from a plist when app starts.
CalculatorAppDelegate.h
#import <UIKit/UIKit.h>
#class MainViewController;
#interface CalculatorAppDelegate : NSObject <UIApplicationDelegate> {
NSDictionary *RGBSpacesDictionary;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain, readonly) NSDictionary *RGBSpacesDictionary;
#property (nonatomic, retain) IBOutlet MainViewController *mainViewController;
#end
CalculatorAppDelegate.m
#import "CalculatorAppDelegate.h"
#import "MainViewController.h"
#implementation CalculatorAppDelegate
#synthesize mainViewController=_mainViewController;
#synthesize RGBSpacesDictionary;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// load plist
NSString* plistPath1 = [[NSBundle mainBundle] pathForResource:#"RGBSpaces" ofType:#"plist"];
RGBSpacesDictionary = [NSDictionary dictionaryWithContentsOfFile:plistPath1];
etc.
}
Then in MainViewController i am able to successfully reading the dictionary in viewDidLoad
MainViewController.h
#class CalculatorAppDelegate;
#interface MainViewController : UIViewController <FlipsideViewControllerDelegate> {
CalculatorAppDelegate *appDelegate;
}
#property (nonatomic, retain) CalculatorAppDelegate *appDelegate;
etc.
}
MainViewCOntroller.m
#import "CalculatorAppDelegate.h"
#implementation MainViewController
#synthesize appDelegate;
- (void)viewDidLoad
{
[super viewDidLoad];
appDelegate = [[UIApplication sharedApplication] delegate];
RGBSpacesCount = (int) [appDelegate.RGBSpacesDictionary count];
}
In viewDidLoad it is all OK, I can read my dictionary as appDelegate.REGSpaceDictionary.
The problem is with another method of MainVievController called when a button is pressed
- (IBAction) RGBSpaceButtonPressed {
NSLog(#"appDelegate.RGBSpacesDictionary %#", appDelegate.RGBSpacesDictionary);
etc.
}
At this time calling the dictionary (for example with a NSLog) return in a crash.
Can someone help me? Thank you.
In this line
RGBSpacesDictionary = [NSDictionary dictionaryWithContentsOfFile:plistPath1];
you are assigning an autoreleased object straight to the ivar so there is no guarantee how long it will stay around for. You should be assigning a non autoreleased object or going through the setter
// Going through the setter
self.RGBSpacesDictionary = [NSDictionary dictionaryWithContentsOfFile:plistPath1];
// OR
// Non assigning a non autoreleased object
RGBSpacesDictionary = [[NSDictionary alloc] initWithContentsOfFile:plistPath1];
To use the setter you would have to redeclare the property in an extension at the top of the app delegate's .m file like this
#interface CalculatorAppDelegate ()
#property (nonatomic, retain, readwrite) NSDictionary *RGBSpacesDictionary;
#end
...
The rest of your implementation
Try to retain the dictionary in app delegate. It must be deallocated at some point, because you get an autoreleased one and you didn't use the property to set it.
// here you must retain the dictionary
[[NSDictionary dictionaryWithContentsOfFile:plistPath1] retain];
Of course don't forget to release it later in dealloc.
I have this code working, however I don't quite understand how it is managing to set the data source for the UITableViewController? Would this have to be occurring via Interface Builder settings somehow?
That is if you see the line "tableData = [[NSMutableArray alloc] initWithObjects: etc", and the fact that I don't see where my "tableData" instance variable here is actually assigned to be the data for the UITableView....
#interface RootViewController : UITableViewController <NewItemControllerDelegate> {
NSMutableArray *tableData;
}
#end
#implementation RootViewController
- (void)viewDidLoad {
[super viewDidLoad];
tableData = [[NSMutableArray alloc] initWithObjects:#"My Standard View", #"A Different View", nil]; // <== HOW IS THIS MANAGING TO SET THE VIEW WITH THE DATA
}
and for reference
#interface myProjectAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
UINavigationController *navigationController;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) IBOutlet UINavigationController *navigationController;
#end
#implementation myProjectAppDelegate
#synthesize window;
#synthesize navigationController;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
// Add the navigation controller's view to the window and display.
[self.window addSubview:navigationController.view];
[self.window makeKeyAndVisible];
return YES;
}
PS (edit) So what I can't quite understand is the linkage between my "NSMutableArray *tableData" variable I declared in the RootViewController header file, and the actual UITableViewController's datasource so to speak? Is there a default "tableData" in a UITableViewController perhaps that is what I'm really setting or something, so I'm not really allocating new NSMutableArray to my variable I created but another one? (hope this makes sense)>
By default, UITableViewController sets itself as the delegate and datasource of its table view. Since your class is a subclass of UITableViewController, it does the same. Of course, this assumes that you have implemented all the UITableViewDataSource methods to actually use the tableData array (which you aren't showing us here).
I created a new Class named
CustomToolbar
Then i created an empty nib, added a
toolbar to it, and set the toolbar
class to "CustomToolbar".
What is the proper way of initializing CustomToolbar In code so that my class uses the nib file?
I already have written the code to do that but i know it's not the correct way and it has a leak.
#interface CustomToolbar : UIToolbar {
}
#property (nonatomic, retain) IBOutlet UIBarButtonItem *button;
#end
#implementation CustomToolbar
- (id)initWithDelegate
{
NSArray *objects = [[NSBundle mainBundle]
loadNibNamed:#"CustomToolbar"
owner:nil
options:nil];
if (self = (CustomToolbar*) [objects objectAtIndex:0])
{
//do some work here
}
return self;
}
#end
The NIB will call initWithCoder: on your custom class, like this:
- (id)initWithCoder:(NSDecoder*)aDecoder {
self = [super initWithCoder:aDecoder];
if( self ) {
// Do something
}
return self;
}
If you really want to load it the way you do now, you need to retain the object returned from loadNibNamed.