Error when ViewController is implementing UITextFieldDelegate - objective-c

When implementing the UITextFieldDelegate in my ViewController class, the following error is thrown when entering the first character in the text field:
-[MyViewController respondsToSelector:]: message sent to deallocated instance...
So, I tried creating a separate class (inheriting only NSObject) and implementing UITextFieldDelegate. Guess what, it worked perfectly. However, that introduces some other problems as I have to do a lot of ugly cross-class-communication that I'd like to avoid. Here's the relevant parts of my app delegate code:
#interface RMSAppDelegate : NSObject <UIApplicationDelegate,
UITabBarControllerDelegate>
#property (nonatomic, retain) UIViewController* myViewController;
#end
#implementation MyAppDelegate
#synthesize myViewController;
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
myViewController = [[MyViewController alloc]
initWithNibName:#"MyView" bundle:nil];
[self.window setRootViewController:myViewController];
[self.window makeKeyAndVisible];
return YES;
}
#end
.. and here's what is being displayed:
#interface MyViewController : UIViewController <UITextFieldDelegate>
#property (nonatomic, retain) IBOutlet UITextField* pinTextField;
- (void)viewDidLoad;
#end
#implementation MyViewController
#synthesize pinTextField;
- (void)viewDidLoad {
// DOES NOT WORK (WHY?)
//[pinTextField setDelegate:self];
// WORKS, BUT I'D LIKE TO AVOID
[pinTextField setDelegate:[[[MyTextFieldDelegate alloc] init] autorelease];
[pinTextField becomeFirstResponder];
[super viewDidLoad];
}
#end
And please, if you see any code (even off topic) that I could be doing better, leave a comment.

Since you asked for off-topic code comments: You forget to call [super viewDidLoad]. You also don't need to redeclare the prototype in order to override it. And the #synthesize textFieldDelegate is not valid, as you have no property in the class named textFieldDelegate. And your dealloc method is releasing an ivar named tfd which doesn't seem to actually exist in the class.
Your real problem is that you are not properly retaining the view controller at whatever point you allocate it. It may be that the view controller is being instantiated in a nib and associated with an ivar rather than a property declared retain, or is not being associated with anything. Or it could be that you are allocating it in code, adding its view as a subview of something, and then releasing it without ever retaining the view controller itself. Or it could just be that you are just releasing it when you shouldn't.
Your other class works specifically because you are leaking the object, so it never gets deallocated. The better solution, were you to go with this method, would be to store the object in an ivar when you allocate it and then release it (and set the ivar to nil) in both dealloc and viewDidUnload.

Okay, I finally solved this on my own. I have not changed the code. My NIB (.xib) was the culprit!
I thought that nested UIViewControllers was OK, and I still think they are in some cases (and maybe using another programmatic method). Anyway, I was initializing my class MyViewController with a NIB that in the Objects panel had a UIViewController as the first object.
I solved this by having the UIView as the first object in the Objects panel, and setting the File's Owner to be the UIViewController instead.
Correct code, incorrect NIB. Thank you for your help.

Related

Navigating from one UITableView to another inside appDelegate

The first UITableView is presented inside a Popover that is called from the RootViewController of the application.
I need to navigate to another UITableView inside the same popover. This is easy to do if you just instance an object of the second UITableView and push it from the first one.
In the next paragraph I write as taking for granted some facts, please correct me if I'm wrong.
The problem here is that this process should be done inside the appDelegate. This is because I'm implementing Dropbox API and I need the pushViewController to be done immediately after the login process is done, which means the navigation through UITableViews has to be done inside of the application:handleOpenURL. I asume that application:handleOpenURL has to be called right there and that's why I also asume the pushViewController has to be done there in order to have the navigation done after the Dropbox API validation window is presented, without having to make the user do anything else.
This is how my code looks like:
AppDelegate.h
#interface AppDelegate : NSObject <UIApplicationDelegate>{
UINavigationController *navigationController;
NSString *relinkUserId;
UIWindow *window;
TableViewControllerForStorageList *rootViewController;
ViewController *viewController;
}
#property (nonatomic, strong) IBOutlet UIWindow *window;
#property (nonatomic, strong) IBOutlet UINavigationController *navigationController;
#property (nonatomic, strong) IBOutlet TableViewControllerForStorageList *rootViewController;
#property (nonatomic, strong) IBOutlet ViewController *viewController;
AppDelegate.m
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
if ([[DBSession sharedSession] handleOpenURL:url]) {
if ([[DBSession sharedSession] isLinked]) {
[(TableViewControllerForStorageList *)self.window.rootViewController PushView];
}
return YES;
}
return NO;
}
TableViewControllerForStorageList.h
-(void)PushView;
TableViewControllerForStorageLost.m
-(void)PushView
{
TableViewControllerIpadStorage *tableViewControllerIpadStorage = [[TableViewControllerIpadStorage alloc]initWithNibName:#"TableViewControllerIpadStorage" bundle:Nil];
[self.navigationController pushViewController:tableViewControllerIpadStorage animated:YES];
}
Off course I got sure that Application:HandleOpenURL is running, but when calling PushView from there the error is [ViewController PushView]: unrecognized selector sent to instance
So, how can make the navigation be done from there? Which basics about objective c am I missing?
It is not clear from your question how your app is structured, so this answer may not be the best solution for your problem but hopefully it gives you some idea of how your view controller hierarchy is likely built up from your app delegate.
Lets say your first view controller class is named FirstViewController. Either your app delegate has a direct reference to an instance of this view controller, or it can access it through a parent view controller (perhaps via window.rootViewController).
Now lets say you have a method in FirstViewController named pushNextViewController that performs the task of pushing the second table view controller.
You can call that method from within the application:handleOpenURL: method of your app delegate.
This might look something like:
[self.window.rootViewController.firstViewController pushNextViewController];
There are other ways you could get a reference to your instance of FirstViewController and it would be cleaner if your rootViewController was a custom subclass so your could create a pushNextViewController method there and from that method tell your FirstViewController instance to pushNextViewController:
[self.window.rootViewController pushNextViewController];
Note that in both examples above, you will need to cast the rootViewController to whatever class it is actually an instance of or the compiler will warn you that it does not have the property firstViewController (example 1) or the method pushNextViewController (example 2).
EDIT: If your rootViewController is a UINavigationController, then your code might look more like:
UINavigationController* navController = (UINavigationController*)window.rootViewController;
FirstViewController* vc = navController.viewControllers[0];
[vc pushNextViewController];
EDIT 2: OK, It looks like the confusion here is that the window object has a rootViewController property (which appears to be pointing to your navigationController) and then you also have a rootViewController instance variable in your app delegate. These are two different objects, making your naming convention a bit confusing, but if I am right then the following should work:
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
if ([[DBSession sharedSession] handleOpenURL:url]) {
if ([[DBSession sharedSession] isLinked]) {
[rootViewController PushView];
}
return YES;
}
return NO;
}
You should consider changing the name of your app delegate's reference to your TableViewControllerForStorageList to something other than rootViewController to alleviate some confusion.

Show NSWindow from separate nib (not modal)

How to do it? I simply want to load a window and show it in front of the main window.
NSWindowController* controller = [[NSWindowController alloc] initWithWindowNibName: #"MyWindow"];
NSWindow* myWindow = [controller window];
[myWindow makeKeyAndOrderFront: nil];
This code shows the window for one moment and then hides it. IMHO this is because I don't keep reference to the window (I use ARC). [NSApp runModalForWindow: myWindow]; works perfectly but I don't need to show it modally.
Yes, with ARC if you don't hold a reference to the window it will be torn down as soon you as you exit the routine you were in. You need to hold a strong reference to it in an ivar. [NSApp runModalForWindow: myWindow] is different because the NSApplication object holds a reference to the window as long as it is being run modally.
You should likely do something similar to the following, which creates a strong reference to the NSWindowController instance you create:
.h:
#class MDWindowController;
#interface MDAppDelegate : NSObject <NSApplicationDelegate> {
__weak IBOutlet NSWindow *window;
MDWindowController *windowController;
}
#property (weak) IBOutlet NSWindow *window;
#property (strong) MDWindowController *windowController;
- (IBAction)showSecondWindow:(id)sender;
#end
.m:
#import "MDAppDelegate.h"
#import "MDWindowController.h"
#implementation MDAppDelegate
#synthesize window;
#synthesize windowController;
- (IBAction)showSecondWindow:(id)sender {
if (windowController == nil) windowController =
[[MDWindowController alloc] init];
[windowController showWindow:nil];
}
#end
Note that rather than sending the makeKeyAndOrderFront: method directly to the NSWindowController's NSWindow, you can just use NSWindowController's built-in showWindow: method.
While the above code (and sample project below) use a custom subclass of NSWindowController, you also use a generic NSWindowController and create the instance using initWithWindowNibName: (just make sure the File's Owner of the nib file is set to NSWindowController rather than a custom subclass like MDWindowController).
Sample project:
http://www.markdouma.com/developer/MDWindowController.zip

Maintain a Link to Subviews in View Controller

Here's my situation: I have a UIViewController that manages a hierarchy of subviews, perhaps as shown below:
This view is built from a .xib. I would like to be able to maintain access to each subview of topView – that is, I want a pointer to each so that I can, for example, say something like:
[button1 setText:#"Hello!"];
Usually, to do this, I wire up each element to which I would like access using Interface Builder, resulting in a header that looks something like this:
#interface MyViewController : UIViewController
{
__weak IBOutlet UIView *view;
__weak IBOutlet UILabel *label;
__weak IBOutlet UIButton *button1;
__weak IBOutlet UIButton *button2;
}
#end
These instance variables are __weak, which is fine, since by the time my view controller "gets" them, they're already owned by my view controller's root view (which, confusingly, I labeled "topView" in my quick diagram). In fact, I want these references to be weak – when my root view is released, so too should all its subviews be released. Great.
But let's say I want to create a new element of the UI, maybe a custom button, entirely in code. I'll call this element CustomViewClass, which will be subclassed from UIView. The instance of CustomViewClass that I will create will be called customButton. As with the other subviews of my view, I would like "access" to customButton so that I can interact with it. I know, however, that like any other subview, customButton will be owned by its superview, and that's how it should be – again, I want it to be released whenever my view is released. This makes me think that I should declare this view as a __weak instance variable or property of my view controller. Let's do that:
#interface MyViewController : UIViewController
{
__weak IBOutlet UIView *view;
__weak IBOutlet UILabel *label;
__weak IBOutlet UIButton *button1;
__weak IBOutlet UIButton *button2;
__weak CustomViewClass *customButton;
}
#end
Then, in my implementation:
#implementation MyViewController
- (void)viewDidLoad
{
[super viewDidLoad];
customButton = [[CustomViewClass alloc] init];
[[self view] addSubview:customButton];
}
#end
As you've probably already realized, this won't work, and the compiler will throw a warning to boot. Something like:
Assigning retained object to weak variable; object
will be released after assignment
I currently dodge this sort of warning with some very poor style:
#implementation MyViewController
- (void)viewDidLoad
{
[super viewDidLoad];
CustomViewClass *customButtonLocal = [[CustomViewClass alloc] init];
[[self view] addSubview:customButtonLocal];
customButton = customButtonLocal;
}
#end
That way, I get what I want:
An instance of CustomViewClass on the screen...
...with exactly one owner, its superview...
...and no lingering variables (customButtonLocal is released immediately after the block ends).
But this can't be the "right" way to do this. So, finally, my question:
How should I be allocating and instantiating this programmatically-created __weak variable without using this middle-man workaround?
Make CustomViewClass *customButton a strong reference.
The reason why you usually declare variables for your subviews as __weak IBOutlet is that the existence of these links does not imply ownership. Subviews are owned by the object instantiated from a NIB/Storyboard. You own that object directly, and you also own its dependent objects indirectly.
The customButton is a different story: you create it programmatically, so your NIB/Storyboard does not own it. Therefore, you should make the reference to it __strong (which is the default when there are no ARC modifiers).

iOS Obj-C. Posing correct solution?

I am at a place in my application where essentially every ViewController has a local NSManagedObjectContext:
#property (strong, nonatomic) NSManagedObjectContext *managedObjectContext;
and every segue passes the managedObjectContext via the same setter
[segue.destinationViewController setManagedObjectContext:self.managedObjectContext];
Coming from Java, it would be easy to create an abstract class that each ViewController implementes. In Objective-c it doesnt seem like that is possible. What I am looking to do is have a base class that performs this passing, but basically anything that implements UIViewController will have this (including just a plain UIViewController as well as a UITableViewController). Would it be possible/correct to have create an "abstract" class that poses as UIViewController that does this?
Update:
UIViewController+ManagedObjectContext.h
#interface UIViewController (ManagedObjectContext)
#property (strong, nonatomic) NSManagedObjectContext *managedObjectContext;
#end
UIViewController+ManagedObjectContext.m
#import "UIViewController+ManagedObjectContext.h"
#implementation UIViewController (ManagedObjectContext){
NSManagedObjectContext *context; // This is not valid, cant have local variables
}
#synthesize managedObjectContext; // This is not valid, must be #dynamic
-(void)setManagedObjectContext:(NSManagedObjectContext *)context{
//How do you have a local NSManagedObjectContext?
}
#end
You can just make your own subclass of UIViewController, let's say MOCViewController, with the managedObjectContext property. Then make all of your other view controllers be subclasses of MOCViewController instead of directly subclassing UIViewController.
If you really want to do it with a category, your category can use objc_setAssociatedObject to attach the managed object context to the view controller.
If you only really have one managed object context and you're just passing it around everywhere, consider just putting the context in a property of your application delegate, or in a global variable.
You can get the managedObjectContext from a managed object rather than pass it separately.
Generally its more logical to pass the managed object.
For example:
Say you have a managed object called thing, you can get the managedObjectContext by calling
NSManagedObjectContext *moc=[thing managedObjectContext];
Alternatively you can get the managed object context from the application delegate:
AppDelegate *delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *moc = delegate.managedObjectContext;

creating a global NSMutableSet

In reference to the answer of this SO question: Keeping track of changes in a UIView
I need help setting up the global aspect of the NSMutableSet. In my appdelegate.h file I've got this:
#interface AppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
ViewController *viewController;
NSMutableSet *statesTouched;
}
and this in my appdelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
statesTouched = [[NSMutableSet alloc]init];
[window addSubview:viewController.view];
[window makeKeyAndVisible];
return YES;
}
In my viewcontroller.h file I'm adding the object like this:
[statesTouched addObject:touchedStateName];
but I'm getting an undeclared identifier for statesTouched. I've never tried putting something like this into my app delegate and I'm a little confused at how this should be working. Thanks!
This is because is an instance variable of your AppDelegate, not your view controller. If you move the declaration and the initialization to your view controller, the error will go away. It would not make it a global variable, though, which is good if it works for you.
If it does not work for you, make the variable a truly global one: move the declaration out of the app delegate, and add extern keyword, like this:
extern NSMutableSet *statesTouched;
Now add the definition in the .m file, like this:
NSMutableSet *statesTouched;
Make sure the definition is outside the #implementation block.