injecting UINibExternalObjects to an self initialzied ViewController - iphone

I want to navigate from one ViewController to another. As part of this I want to
pass the ViewController I want to navigate to some information. I encapsulated the
information into an Object that I want to hook up as an external object with the target
viewController.
I created the external Object inside IB gave it the identifier I referenced in the NSDictionary that is passed to the NibLoading method.
NSArray* topLevelObjs = nil;
NSMutableDictionary* options = [[NSMutableDictionary alloc] initWithCapacity:1];
NSMutableDictionary* config = [[NSMutableDictionary alloc] initWithCapacity:1];
id detailImageVC = [[SelectedImageModalViewController alloc] init];
SelectedImageModalModel* selectImageModalModel = [[SelectedImageModalModel alloc] init];
selectImageModalModel.imageName = #"img#2x.png";
[config setValue:selectImageModalModel forKey:#"SelectImageModalModel"];
[options setValue:config forKey:UINibExternalObjects];
topLevelObjs = [[NSBundle mainBundle] loadNibNamed:#"SelectedImageModalViewController" owner:detailImageVC options:options];
if ([topLevelObjs count] == 0)
{
NSLog(#"Warning! Could not substitute proxy objects in xib file.\n");
return;
}
[appDelegate.navigationController presentModalViewController:detailImageVC animated:YES];
[options release];
[config release];
[selectImageModalModel release];
[detailImageVC release];
What I expected was, that after I called presentModalViewController:animated: I would receive a call to viewDidLoad on the very same detailImageVC with my external objects
connected.
Instead neither happens. viewWillApear: will get called, but the detailImageVC won't hold my external reference.
Any idea, hind or comment is appreciated. Thanks!

viewDidLoad will be called if only ViewController loaded the view itself.
For example; initWithNibName does not load the view, it just sets the nib name. When ViewController needs its view in some future point and if there is no view in ViewController.view then ViewController will load view like you did AND THEN INVOKE viewDidLoad itself.
Your code loads the view of ViewController itself. So you should call the viewDidLoad method in your code like this:
topLevelObjs = [[NSBundle mainBundle] loadNibNamed:#"SelectedImageModalViewController" owner:detailImageVC options:options];
if (topLevelObjs.count == 0) {
NSLog(#"Warning! Could not substitute proxy objects in xib file.\n");
return;
} else {
[detailImageVC viewDidLoad];
}
If your detailImageVC does not hold your external object then you should check your nib file for IBOutlet bindings and your SelectedImageModalViewController for corresponding #property. If property is not strong like #property(nonatomic, strong) on ARC, or, is not reatin on Non-ARC like #property(nonatomic, retain) then it will not hold your object after awaking from nib.

Related

iOS strange delegate behavior?

I have a really strange delegate behavior in iOS. I'm setting a custom delegate in a subclassed UIViewController like this:
- (void)viewDidLoad
{
[super viewDidLoad];
self.baseNavigationBar = [[BaseNavigationBar alloc] initWithNibName:nil bundle:nil];
self.baseNavigationBar.delegate = self;
self.baseNavigationBar.navigationController = self.navigationController;
[self.navigationController.navigationBar addSubview:self.baseNavigationBar];
}
The initWithNibName can use nil, when using the default nib, because internally it will check fora nil in the nibName.
if (!nibNameOrNil) {
nibNameOrNil = NSStringFromClass([self class]);
}
The delegate declaration from the baseNavigationBar object looks the following:
#property (nonatomic, retain) id<BaseNavigationBarDelegate> delegate;
/*same thing with assign, which I you should use*/
/*and in the .m of course #synthesize*/
And now 2 screenshots from the running application.
The first one show's the debugger values from the BaseListViewController, which is a subclass of BaseCoreViewController, which is a subclass of UIViewController.
The screenshot is taken, when the viewDidLoad method is called.
The second one show's the values from the BaseNavigationBar, which is a UIView subclass.
The screenshot is taken at a time, when the user clicks the "next" button
- (IBAction)nextAction:(id)sender {
if (self.delegate) {
[self.delegate navigationBarDidClickNextButton:self];
}
}
So why is this a problem? By clicking a button in the BaseNavigationBar my delegate is always nil, so the program is stuck. But when looking at the values from the BaseCoreViewController at the same time the delegate is not nil. Very strange.
Edit
The BaseNavigationBar is loaded from a nib file using the UINib loadNibNamed:owner:options: function.
Edit 2
So that's pretty much all of the code.
Edit 3
Finally we got the source of the error in the last pieces of the code... setting self = something totally not allowed...
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (!nibNameOrNil) {
nibNameOrNil = NSStringFromClass([self class]);
}
if (!nibBundleOrNil) {
nibBundleOrNil = [NSBundle mainBundle];
}
NSArray *topLevelObjects = [nibBundleOrNil loadNibNamed:nibNameOrNil owner:self options:nil];
/*Here is the bad part*/
self = [topLevelObjects objectAtIndex:0];
state = BaseNavigationBarButtonStateNext;
if (self) {
self.title = #"";
}
return self;
}
Solved using a Class method instead of defining a custom init.
If you are using nib file, you should declare the nib class while initialising your class object.
You have initialised initWithNibName:nil
self.baseNavigationBar = [[BaseNavigationBar alloc] initWithNibName:nil bundle:nil];
but it should be like initWithNibName:#"BaseNavigationBar"
self.baseNavigationBar = [[BaseNavigationBar alloc] initWithNibName:#"BaseNavigationBar" bundle:nil];
Not sure what the problem is. Require some more code to analyze the problem. But what I can see is address of BaseNavigationBar in both the screen shots is different. It means the BaseNavigationBar object present in the controller is not the one which got the nextAction event as in second screen shot.

UITabBarController as a delegate of its sub view controllers

I currently have a Storyboard with a subclass of UITabBarController linked to 3 view controllers.
Those view controllers are custom subclasses of UIViewController, and I added a "delegate" property, as I need to access a property (settings) from the main view controller (retrieved from a .plist file).
It's a Settings class that handle reading and writing from/to this file.
Anyway, I haven't found how to set their delegate property from IB, so I added this in the code :
- (void)viewDidLoad
{
[super viewDidLoad];
/* Fetch settings */
NSString* settingsPath = [[NSBundle mainBundle] pathForResource:#"Settings" ofType:#"plist"]; // Load settings from file
settings = [[Settings alloc] initWithContentOfFile:settingsPath];
for (UIViewController<HasDelegate> *c in [self viewControllers]) {
c.delegate = self;
}
}
But only the 1st view controller (the one that is displayed when starting the app) can access it.
The other ones has a nil pointer for the delegate property.
Is it the right way to do it ?
What am I missing ?
Nevermind, I was accessing the UINavigationController, instead of its rootViewController. I added :
if([c isKindOfClass:[UINavigationController class]]) {
NSLog(#"UINavigationController");
UINavigationController *navC = (UINavigationController*)c;
[[[navC viewControllers] objectAtIndex:0] setDelegate:self];
}
and it works like a charm !

Loading CustomView From nib file

I have a code that load the custom view from a nib file but there is a problem with the variable of that custom view.
- (id)initWithFrame:(CGRect)frame
{ self = [super initWithFrame:frame];
if (self) {
// Initialization code
HomeMainView* views = (HomeMainView*)[[[NSBundle mainBundle] loadNibNamed:#"HomeMainView" owner:self options:nil] objectAtIndex:0];
[self release];
NSArray* permission = [NSArray arrayWithObjects:#"user_photos",#"publish_stream", nil];
FBLoginView* fbLogin = [[[FBLoginView alloc] initWithPublishPermissions:permission defaultAudience:FBSessionDefaultAudienceFriends] autorelease];
[views.FBLogin addSubview:fbLogin];
[views.homeButton setTitle:#"asdf" forState:UIControlStateNormal];
self = views;
}
return self;
}
I am trying to add the fbLogin to the views object generated by the Nib file. The problem is the views.FBLogin generate the errors saying unrecognized selectors.
Does anyone know what is wrong here?
I had the same problem but it was because I needed to add this to my app delegate didFinishLaunchingWithOptions :
[FBLoginView class]
Without this the UIView was a UIView not an FBLoginView, hence the unrecognized selector.
Make sure that you have set the custom class in interface builder for the view. Select the view and press cmd+option+3 and set the custom class.

why I can't transfer variables between two view controllers this way?

I have one view who calculates location and reverser geocode to get the zip code. Then it calls another view where I want to display weather results based on that zip code.
In the first view controller I do this once user clicks on the button to turn the page:
- (IBAction) showMyWeather : (id)sender {
WeatherApp *weather = [[WeatherApp alloc] initWithNibName:nil bundle:nil];
weather.zipcode = placemarkZip; //this one seems not to be doing the job
weather.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:weather animated:YES];
}
And at the WeatherApp I would like to read now zipcode, which is declared in this view controller .h:
#interface WeatherApp : UIViewController{
IBOutlet UIButton *done;
MKPlacemark *zipcode;
}
#property (nonatomic, retain) MKPlacemark *zipcode;
How can I use this code to transfer this zipcode to the WeatherApp? Thanks!
Yes, this is a fine way to pass information into your new object.
Alternatively, you could create a custom initializer for WeatherApp like
- (id)initWithZipCode:(NSString *)zip;
and then in the implementation file, it could be like this:
- (id)initWithZipCode:(NSString *)zip
{
self = [super init];
[self setZipcode:zip];
return self;
}
Finally, you could instantiate the class like so:
- (IBAction)showMyWeather:(id)sender
{
WeatherApp *weather = [[WeatherApp alloc] initWithZipCode:placemarkZip];
[weather setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];
[self presentModalViewController:weather animated:YES];
[weather release]; // No longer needed with ARC... just sayin'
}
Finally, if you're going to continue to pass in information as you do above, I'd question why you're using initWithNibName:bundle:. If you're just going to pass nil to both, why not just use [[WeatherApp alloc] init]?

Memory management on the array initialized for UITabBarController

I am still learning the ropes of Objective C and iPhone development. My weak point is memory management - any help will be much appreciated.
In the code below, where can I release the NSMutableArray listOfViewControllers? Keep in mind the function createTabs can be called within the app multiple times and the tabs are recreated dynamically based on user input. This function is within a ViewController.
If i do [listofViewControllers release] just before exiting the function, the app crashes when I have to call createTabs again
If I use a convenience method like below:
NSMutableArray *listOfViewControllers = [NSMutableArray arrayWithCapacity:1]
instead of:
NSMutableArray *listOfViewControllers = [[NSMutableArray alloc] init]
it still crashes when createTabs is called again
-(void) createTabs
{
//TODO - memory management - where do you release this?
NSMutableArray *listOfViewControllers = [[NSMutableArray alloc] init];
if ([briefingsArray count] > 0)
{
//add briefing(s) tab(s)
for (Briefing *briefing in briefingsArray)
{
WebViewController *briefingViewController = [[WebViewController alloc] initWithBriefing: briefing];
[listOfViewControllers addObject:briefingViewController];
[briefingViewController release];
}
[listOfViewControllers addObject:alertViewController];
//add archives tab
NSString *archiveURL = [NSString stringWithFormat: ARCHIVEURL, DeviceID()];
UIViewController *archiveViewController = [[WebViewController alloc] initWithURL:ARCHIVEURL andTitle:#"Archives" andImage:#"archive_icon.png"];
[listOfViewControllers addObject:archiveViewController];
[archiveViewController release];
}
NSArray *oldlistOfViewControllers = [self.tabBarController viewControllers];
UIViewController *vcOld = [oldlistOfViewControllers objectAtIndex:[oldlistOfViewControllers count] -1];
[listOfViewControllers addObject:vcOld];
[self.tabBarController setViewControllers:listOfViewControllers
animated:YES];
}
My best guess is that it has nothing to do with tab bar controller. When you did not release the array, the controllers in the array would never be dealloc and there was no problem at all. So it's likely that the problem might come from deallocation of your WebViewController.
It looks like what you are creating here - listOfViewControllers - should be an instance variable of whatever object you are making here. Then you should alloc/init it inside the object's -init method, and release it in dealloc.
It is good practice (and usually necessary) to make an instance variable for anything you expect to exist after the end (or before the start) of a function call.
After [self.tabBarController setViewControllers:listOfViewControllers
animated:YES];, you can release your listOfViewControllers. Because tabBarController will retain this listOfViewControllers by copy policy.
You can see the UITabBarController reference about viewControllers property. This property adopts the copy policy.
#property(nonatomic, copy) NSArray *viewControllers