The Problem
An IBOutlet is released before I have a chance to use it.
What I Want
I want to access a navigation controller from my app delegate so I can reload a table view.
My Setup
I have:
A Main.xib that's set as my main interface in target settings
An IBOutlet to the navigation controller as an ivar on my app delegate
This IBOutlet hooked up to the correct navigation controller in Main.xib
App Delegate is instantiated in the xib but not set as File's Owner
I'm using ARC, Xcode 4.3.2 and iOS5.1
What I've Tried
Changing deployment target
Putting a break point on dealloc for the navigation controller, app delegate - they're never called
Reading everything I can find on ARC and IBOutlets - nothing seems to contradict what I'm doing
Creating a fresh project with just a the minimum classes required - I see exactly the same problem
Code
KPAppDelegate.h
#interface KPAppDelegate : UIResponder <UIApplicationDelegate> {
IBOutlet KPBrowseExpensesNavigationController *nc;
}
#property (strong) IBOutlet KPBrowseExpensesNavigationController *nc;
KPAppDelegate.m
#implementation KPAppDelegate
#synthesize nc;
-(void)setNc:(KPBrowseExpensesNavigationController *)nc_ {
nc = nc_; // This gets called on view load and nc gets set.
}
...snip...
// This is called about 5 seconds after app startup
-(void)objectLoader:(RKObjectLoader *)objectLoader didLoadObjects:(NSArray *)objects {
// By the time we get here, nc is nil.
UITableViewController *tvc = [[nc viewControllers] objectAtIndex:0];
[[tvc tableView] reloadData];
}
#end
UPDATE
I must be doing something really silly here. Even an incredibly simple project still shows this problem. See link below.
Download a simple test project that shows the problem.
In Window nib, set the FilesOwner Class as UIApplication and then point it's delegate from Outlets to the AppDelegate object. This is what is wrong in your project example.
is your outlet from the Interface Builder set as an KPBrowseExpensesNavigationController type?
If not it is not going to create the connection between your nib and ViewController.
You should set its Custom Class as KPBrowseExpensesNavigationController in the Identity Inspector
I am not sure why you declare it as a property & a non-property. I should do something like this:
#interface KPAppDelegate : UIResponder <UIApplicationDelegate>
#property (nonatomic, strong) IBOutlet KPBrowseExpensesNavigationController *nc;
And in your implementation:
#implementation KPAppDelegate
#synthesize nc = _nc; // So you don't accidentally use nc
...snip...
// This is called about 5 seconds after app startup
-(void)objectLoader:(RKObjectLoader *)objectLoader didLoadObjects:(NSArray *)objects {
// By the time we get here, nc is nil.
UITableViewController *tvc = [[**self.nc** viewControllers] objectAtIndex:0];
[[tvc tableView] reloadData];
}
#end
Hope this helps!
I didn't see where you alloc your nav controller. Just declaring the property won't assign any value to it, so it would be nil. In you -didFinishLaunchingWithOptions in the app delegate, set your alloc/init statement. Everything else looks fine.
KPBrowseExpensesNavigationController *nc = [[KPBrowseExpensesNavigationController alloc] init];
If you have a custom init, you can use that too, but just make sure to set it up before you try and use it.
Related
I'm completely new to Objective-C, XCode, and iOS development and I'm trying to figure out how to run certain code at startup, after all UI views and controls have been instantiated. I have a generic NSObject that I've added through interface builder by dragging it into my view controller scene. It's defined as follows:
#import <Foundation/Foundation.h>
#interface Controller : NSObject {
IBOutlet UISlider *slider;
IBOutlet UILabel *label;
}
-(IBAction)sliderChanged:(id)sender;
#end
I need to run sliderChanged on initialization. I've tried the following way:
#import "Controller.h"
#implementation Controller
-(id)init {
self = [super init];
if (self){
[self sliderChanged:nil];
}
return self;
}
// More code here
But both my slider and label are nil when this is called. I understand there's a viewDidLoad method within the ViewController class which may be what I need, but I'm not sure how to access the instance of my Controller class (which seems to be instantiated somewhere behind the scenes by the interface builder) from within that method. Should all of this code simply be moved to the ViewController itself? That would seem to make sense, but the design above is what we've been instructed in class, so I'm not really sure how to go about doing this.
After the XIB/Storyboard loader finishes loading all the objects and wiring them up, it sends awakeFromNib to every object that was instantiated from the XIB. So try adding this to your Controller class:
- (void)awakeFromNib {
[super awakeFromNib];
[self sliderChanged:nil];
}
You can find more information in the NSObject UIKit Additions Reference and “The Nib Object Life Cycle” in the Resource Programming Guide.
HOWEVER, if you created Controller as a top-level object, and you didn't connect any outlets to it, then nothing references it after the XIB loader finishes with it, so the system will deallocate it again. That's probably not what you want, so you should connect an outlet in your view controller to the Controller. If you do that (and let's say the outlet is named controller), then you can access it in viewDidLoad in your view controller class:
#import <UIKit/UIKit.h>
#import "Controller.h"
#interface ViewController : UIViewController {
IBOutlet Controller *controller;
}
#end
Implementation:
- (void)viewDidLoad {
[super viewDidLoad];
[self.controller sliderChanged:self];
}
I want to change the value of a label in my default view controller from a different class.
So I start a simple 'Single View Application' iOS project (Xcode5)
This automatically generates a ViewController for me (which I
understand is the root view controller)
I now add a label in my View and connect it to the ViewController (via IBOutlet mechanism)
I call this outlet 'gameStateLabel', so it looks like this in the ViewController.h file
#property (weak, nonatomic) IBOutlet UILabel *gameStateLabel;
Next, I have a completely separate class which has the logic for my code, and based on a condition in the logic I want to change the UIlabel.
So I try to do this from my other class:
Get an instance of the root view controller like this:
UIViewController * uvc = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
I think I now have an instance of the rootviewcontroller in uvc and should be able to reach in and change gameStateLabel.
BUT: I CANNOT do this uvc.gameStateLabel simply does not show up as a property even though it is clearly declared as a property and I've added the #synthesize for it also.
Any help will be greatly appreciated - I've been going nuts over this.
(For ref. I'm used to doing something similar on the Mac side where I'd declare a label as a property of the AppDelegate, get the instance of the Appdelegate and simply refer to the label property and change its text]
Here's the ViewController. Note that gameStateLable is a property
#import "ViewController.h"
#interface ViewController ()
#property (weak, nonatomic) IBOutlet UILabel *gameStateLabel;
#end
#implementation ViewController
#synthesize gameStateLabel;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
And here is my class cls1 (which inherits from NSObject)
#import "cls1.h"
#import "ViewController.h"
#implementation cls1
-(void) dummy{
UIViewController * uvc = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
// uvc does NOT show gameStateLabel in intellisense, i.e. uvc.gameStateLabel does NOT work
}
Add #import "mainRootVC.h" in you CustomClass.m file
And create object of mainRootVC such like,
mainRootVC *obj = [[mainRootVC alloc] init];
// Now you can access your label by
obj.gameStateLabel...
Do like this...
YourViewController *rootController =[(YourViewController*)[(YourAppDelegate*)
[[UIApplication sharedApplication]delegate] window] rootViewController];
Try the following too:
ViewController *controller = (ViewController*)self.window.rootViewController;
It will return the initial view controller of the main storyboard.
For sending information from a viewController to other viewController you have:
Delegates:Definiton and examples
NSNotificationCenter: Apple Documentation
NSString *label = #"label text";
[[NSNotificationCenter defaultCenter] postNotificationName:NAVIGATETO object:label userInfo:nil];
You can found tons of examples about those two. I recommend you use one of those.
Hope this helps a bit.
OK. I found two issues.
I had copied the code over from my Mac project and modified it. Something seems to have gone wrong.
I retyped the entire function and it solved most of the problem
uvc should be of the type ViewController* - not UIViewController* as I was doing
Thanks everyone for your replies - much appreciated.
I'm trying to set the delegate for my custom protocol that has one required method allowing me to pass an array of objects back in the hierarchy of two UITableViewControllers. My delegate continues to return nil. Due to this, my required method is never called.
I'm wondering if the datasource and delegate implementations with my UITableViewControllers is causing a conflict. Also, perhaps ARC is getting in the way when declaring the delegate?
It should be noted that both UITableViewControllers were built using Storyboard and are navigated using segues within a UINavigationController (not sure if this may be causing issues or not).
The nav is --> AlarmViewController --> AlarmDetailsViewController. I create an Alarm object in my AlarmDetailsViewController that contains all the details for an alarm, place it into an array and I want to pass that array back to my AlarmViewController to be displayed in a custom cell in the table.
NOTE: I want to use the Delegate pattern here. I'm not interested in solutions that invoke NSNotifications or use my AppDelegate class.
AlarmDetailsViewController.h
#import "Alarm.h"
#protocol PassAlarmArray <NSObject>
#required
-(void) passAlarmsArray:(NSMutableArray *)theAlarmsArray;
#end
#interface AlarmDetailsViewController : UITableViewController <UITableViewDataSource, UITableViewDelegate>
{
//.....
id <PassAlarmArray> passAlarmsArrayDelegate;
}
#property (nonatomic, retain) id <PassAlarmArray> passAlarmsArrayDelegate;
#end
AlarmDetailsViewController.m
#import "AlarmDetailsViewController.h"
#interface AlarmDetailsViewController ()
#end
#implementation AlarmDetailsViewController
#synthesize passAlarmsArrayDelegate;
-(void) viewWillDisappear:(BOOL)animated
{
NSLog(#"delegate = %#", self.passAlarmsArrayDelegate); // This prints nil
[[self passAlarmsArrayDelegate] passAlarmsArray:alarmsArray];
}
//....
#end
AlarmViewController.h
#interface AlarmViewController : UITableViewController <UITableViewDataSource, UITableViewDelegate, PassAlarmArray>
{
//...
AlarmDetailsViewController *alarmDetailsViewController;
}
#property (nonatomic, retain) AlarmDetailsViewController *alarmDetailsViewController;
#end
AlarmViewController.m
#import "AlarmViewController.h"
#import "AlarmDetailsViewController.h"
#import "AlarmTableViewCell.h"
#import "Alarm.h"
#interface AlarmViewController ()
#end
#implementation AlarmViewController
#synthesize alarmDetailsViewController;
- (void)viewDidLoad
{
[super viewDidLoad];
// This is where I'm attempting to set the delegate
alarmDetailsViewController = [[AlarmDetailsViewController alloc]init];
[alarmDetailsViewController setPassAlarmsArrayDelegate:self];
}
//....
//My #required protocol method which never gets called since my delegate is nil
-(void) passAlarmsArray:(NSMutableArray *)theAlarmsArray
{
alarmsTableArray = theAlarmsArray;
NSLog(#"alarmsTableArray contains: %#", alarmsTableArray); // Never gets called due to delegate being nil
NSLog(#"theAlarmsArray contains: %#", theAlarmsArray); // Never gets called due to delegate being nil
}
#end
I've attempted to set the delegate in a method that fires when a button is pressed in AlarmViewController (as opposed to the viewDidLoad method) but that does not work either.
I'm assuming I've got a logic flow error somewhere here . . . but nearly 2 days of hunting and rebuilds haven't uncovered it. Ugh.
You're setting your delegate in the wrong place, and on a different instance of the controller than the one you will get when you do the segue. You should set the delegate in the prepareForSegue method if you're pushing AlarmDetailsViewController from AlarmViewController
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
AlarmDetailsViewController *alarm = segue.destinationViewController;
alarm.passAlarmsArrayDelegate = self;
}
You really need to understand the life cycle of view controllers, how and when they're instantiated, and when they go away. This is the very heart of iOS programming, and Apple has extensive documentation on it. Reading up on segues would also be very useful. A segue (other then an unwind segue) always instantiates a new instance of the destination controller. So, when your segue is performed, whether directly from a button, or in code, a new (different from the one you alloc init'd directly) details controller is instantiated. Before that segue is performed, prepareForSegue: is called, and that's when you have access to the one about to be created. That's the place to set a delegate or pass any information on to the destination view controller.
Did you try replace (nonatomic, retain) with (nonatomic, strong) since you are using ARC?
Auto-synthesized properties like your alarmDetailsViewController property have backing ivars prefixed with underscores, e.g. _alarmDetailsViewController. Your alarmDetailsViewController ivar (the alarmDetailsViewController declared inside the #interface ... {} block in AlarmViewController.h) is different from the backing ivar of your alarmDetailsViewController property.
Just delete your alarmDetailsViewController ivar and use the #property, preferably through self.alarmDetailsViewController.
I´m having problems declarating my own delegate. Well...thats not exactly true: i have it declarated and, when i build the project, the compiler reports no issues. I declarated it in this way:
I made a file (enviarDatos.h) for declare the protocol:
#protocol enviarDatos <NSObject>
- (void)addItemViewController:(NSMutableArray *)item;
#end
In the Vista2.h (ViewController) file I imported the file enviarDatos.h and declared a property:
#property (nonatomic, weak) id <enviarDatos> delegare;
In the Vista2.m (ViewController) file I use the protocol method:
#interface ViewController : UIViewController <enviarDatos> {
And, finally, in the ViewController.m file I implement the delegates method:
- (void)addItemViewController:(NSMutableArray *)ar {
origen = ar;
}
Does anyone see something wrong? the code of the last function its never executing.
Thanks for your help.
EDIT:
What i need is to change an array in ViewController from Vista2 (another viewcontroller)
Then create delegate property in next view(child view) & set it to self in parent view while pushing or showing child view.
ParentView.m
1.Implement protocol methods
- (void)addItemViewController:(NSMutableArray *)ar
{
origen = ar;
}
2.While showing child view
ChildViewController *child = [[ChildViewController alloc] init];
child.delegate = self;
//present child view
ChildView.h
#property (nonatomic, weak) id <enviarDatos> delegare;
ChildView.m
-(void) anyMethod
{
if([self.delegate respondsToSelector:#selector(addItemViewController:)])
{
[self.delegate addItemViewController:mutableArray];
}
}
Ah, it looks like you are declaring the delegate property in the wrong place.
You should declare the property delegate in enviarDatos.h.
#property (nonatomic, weak) id <enviarDatos> delegate;
Then in Vista2.m you will do something like this...
EnviarDatos *myObject = [[EnviarDatos alloc] init];
myObject.delegate = self;
This then sets up the EnviarDatos object and assigns the Vista2 object as the delegate.
Now, in EnviarDatos.m you can run...
[self.delegate addItemViewController:someObjectArray];
And this will then run that code in the Vista2 object.
Delegates are used for calling back to objects that create them (or some other objects). If you create an object and then want to run a method in it then you won't need a delegate.
Can you say at what condition addItemViewController is invoked?
You seem to be on the right track, but are you sure you are setting the delegate as
[yourObject setDelegate: self];
Have you tried debugging it? Does the debugger pause at addItemViewController if you set a breakpoint there? Can you confirm the delegate is not null inside the method? I may post some code but your seems to be right except for the assigning of delegate, I think you should check it.
This is what I got so far.
mainview.m
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender(id)sender {
secondView *secView = [segue destinationViewController]
secView.ext = #".com";
}
secondView.h
#interface secondView : UIViewController {
NSString *ext;
}
#proper (nonatomic, retain) NSString *ext;
#end
secondView.m
-(void)viewDidLoad {
NSString *url = [#"www.google" stringByAppendingFormat:ext]
}
And its returning a error saying ext is null... what am I doing wrong?
Did you try to turn ext into a property instead? My understanding is that the "dot" notation essentially turns your code into
[secView setExt:#".com"]
So turn SecondView.h into
#interface secondView : UIViewController
#property (nonatomic, copy) NSString *ext;
#end
And don't forget to #synthesize it in your .m file
Check the followings:
Make sure #synthesize ext; is in SecondView.m
In Storyboard, have you linked the segue correctly?
In Stodyboard, is the viewController that represetns SecondView defined as a class of SecondView?
Make sure that you are calling the SecondView via prepareForSegue:sender method (i.e. SecondView doesn't get called by pushViewController:animated somewhere else in your code).
Put a breakpoint at the line:
secView.ext = #".com";
and make sure that the ext ivar is properly set after the assignment. If it's not, you might be specifying that the accessors use a different ivar in your #synthesize directive for ext, or you might have provided a setter -setExt: that doesn't properly set the ivar.
If ext is set properly after the assignment, perhaps you've got two different instances of secondView. That used to happen a lot when people used .xib files -- they'd create one view controller in code and have another instance of the same class in their .xib file. I wouldn't expect that to be the case here since you're getting secView straight from the segue, but it's worth thinking about.
There are two views: view1 and view2.
Add view2.h file in view1.h
init object of view2 and set their variable.