uninitialized property in prepareForSegue - objective-c

I'm having some difficulty managing a segue. My view controller hierarchy is as follows:
BHGSplitViewController (subclass of UISplitViewController)
UINavigationController
MasterViewController (subclass of UITableViewController)
UINavigationController
DetailViewController (subclass of UIViewController)
These have the following properties:
BHGSplitViewController:
#interface BHGSplitViewController : UISplitViewController
#property (nonatomic, strong) MasterViewController* masterVC;
#end
MasterViewController:
#interface MasterViewController : UITableViewController
#property (strong, nonatomic) MenuDataSource *menuDataSource;
#property (strong, nonatomic) DetailViewController *detailViewController;
#end
DetailViewController:
#interface DetailViewController : UIViewController
#property (strong, nonatomic) NSURL* URL;
#property (strong, nonatomic) IBOutlet UIWebView *webView;
#end
These are related in my main storyboard through relationship segues.
After the initial app launch, I modally display a login view controller. After login, I segue back to my BHGSplitViewController, but I need to set some data. In the loginViewController's prepareForSegue method I attempt the following:
BHGSplitViewController *splitVC = [segue destinationViewController];
splitVC.masterVC.menuDataSource.var = someValue;
But, when debugging, splitVC.masterVC = nil, so obviously attempting to set a value on it is not going to work. How do I set-up and retain those relationships?
I inherited a version of this app that was built with storyboards. I'm guessing that I need to start intializing these properties. What's the best way to do that with storyboards? Do I need to override initWithCoder:? Should I be setting these properties in viewDidLoad?
Edit: Explanation of segue
So, I'm loading up my BHGSplitViewController in my AppDelegate and setting it up to handle collapses, etc. After that, I need to immediately display a modal view for login. I need this to animate in like a push (but Apple won't allow that), so I'm trying to hack my way around that by using a custom segue (which is hacky and I hate it):
Present Segue:
- (void)perform {
UIViewController *srcViewController = (UIViewController *) self.sourceViewController;
UIViewController *destViewController = (UIViewController *) self.destinationViewController;
UIView *prevView = srcViewController.view;
UIView *destView = destViewController.view;
UIWindow *window = [[[UIApplication sharedApplication] delegate] window];
[window insertSubview:destView aboveSubview:prevView];
[destView enterLeft:0.1 then:^{
[srcViewController presentViewController:destViewController animated:NO completion:nil];
}];
}
Dismiss Segue:
- (void)perform {
UIViewController *srcViewController = (UIViewController *) self.sourceViewController;
UIViewController *destViewController = self.destinationViewController;
UIView *prevView = srcViewController.view;
UIView *destView = destViewController.view;
UIWindow *window = [[[UIApplication sharedApplication] delegate] window];
[window insertSubview:destView aboveSubview:prevView];
[destView enterRight:0.1 then:^{
[srcViewController presentViewController:destViewController animated:NO completion:nil];
}];
}

You can set the properties in viewDidLoad. initWithCoder is definitely too early since the split view controller is instantiated first, and then the other controllers it houses. So, you can do this in viewDidLoad of BHGSplitViewController,
-(void)viewDidLoad {
[super viewDidLoad];
UINavigationController *nav = (UINavigationController *)self.viewControllers.firstObject;
self.masterVC = (MasterViewController *)nav.topViewController;
}

Related

Using protocol to trigger a segue from a UIView inside a UIViewcontroller

CONFIGURATION:
-UIviewController embedded in Navigationcontroller.
-UIviewController has a UIscrollview as subview
-UIscrollview has some views where charts are created: each view containing a chart has its own .h and .m file and from this file I want trigger a segue to a tableview controller.
-A Tableviewcontroller was added in xcode and a segue from the UIviewController to the TableViewcontroller was created as well (Xcode)
-created a protocol in the UIView to have the segue pushed from there.
PROBLEM:
delegate always nil, segue method will never be called
UIVIEW .h file
#protocol UItoUIvcDelegate <NSObject>
-(void)triggerSegue;
#end
#interface CFfirstGraph : UIView <CPTPlotDataSource , CPTPieChartDelegate,UIActionSheetDelegate>
#property(weak, nonatomic) id <UItoUIvcDelegate> delegate;
#end
UIVIEW .m file (snippet)
-(void)pieChart:(CPTPieChart *)pieChart sliceWasSelectedAtRecordIndex:(NSUInteger)index
{
if (self.delegate == nil)
{
NSLog(#"nil");
}
[self.delegate triggerSegue];
}
UIVIEWCONTROLLER .h file
#import "CFfirstGraph.h"
#interface CFMainViewController : UIViewController <UItoUIvcDelegate, UITableViewDelegate, UITableViewDataSource>
#end
UIVIEWCONTROLLER .m file (snippet)
- (void)viewDidLoad
{
[super viewDidLoad];
self.scrollView.contentSize = CGSizeMake(320, 1000);
[self.view addSubview:self.scrollView];
CFfirstGraph *click =[[CFfirstGraph alloc]init];
click.delegate = self ;
}
-(void)triggerSegue
{
[self performSegueWithIdentifier:#"detailedData" sender:self];
NSLog(#"estoy aqui");
}
What am I doing wrong ? why the delegate is always nil ? I tried to add the method setDelegate but still no luck.
Thanks,
dom
Make CFfirstGraph as a strong property.
#property (strong, nonatomic) CFfirstGraph * click;
- (void)viewDidLoad
{
[super viewDidLoad];
self.click =[[CFfirstGraph alloc]init];
self.click.delegate = self ;
self.scrollView.contentSize = CGSizeMake(320, 1000);
[self.view addSubview:self.scrollView];
}
ok, after many hours of sweat I found the issue.
First...delegate = nil was not the main problem.
The real issue was the protocol method triggering the segue was never called.
If i create and initialize a CFfirstGraph object (or even property) it won't be related to the view created already in x-code, and this is the main issue.
On the other hand...if I "CTRL-drag" an outlet from the UIview to the CFMainViewController i will have a property that is exactly the one i need:
#interface CFMainViewController () <UIScrollViewDelegate>
#property (weak, nonatomic) IBOutlet CFfirstGraph *FirstGraph;
Then i assign the delegate to self (CFMainViewController) in the viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
self.FirstGraph.delegate = self ;
}
and the delegate method "triggerSegue" will be executed being called from the UIVIEW.
Best Regards,
dom

Adding navigation controller on a view controller subclass

How do you add a navigation controller on a newly created view controller? i've search everywhere but all the tutorials are from creating a navigation controller project.
Anyone can lead mo to a tutorial that creates a navigation controller using a view controller subclass?
What i'm doing so far:
I created a UIViewController Project, and i have something like this to go to another view controller, with a navigation controller.
NavController *view=[[NavController alloc] initWithNibName:nil bundle:nil];
view.modalTransitionStyle=UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:view animated:YES];
[view release];
Added a new view controller subclass.
Add > New File > UIViewController subclass with nib
on NavController.h
#import <UIKit/UIKit.h>
#interface NavController : UIViewController {
IBOutlet UIWindow *window;
IBOutlet UINavigationController *navCon;
}
#property (nonatomic, retain) UIWindow *window;
#property (nonatomic, retain) UINavigationController *navCon;
#end
on NavController.m
#import "NavController.h"
#implementation NavController
#synthesize window,navCon;
- (void)viewDidUnload {
[super viewDidUnload];
}
- (void)dealloc {
[window release];
[navCon release];
[super dealloc];
}
#end
i already dragged a Navigation Conrtoller and a Window on my IB, and connected window to window and the Navigation Controller to navcon outlets, but whats next?
If you're using the storyboards select your view controller then in top menu choose "editor" / "embed in" / "navigation controller".
Normally, you have to create an Navigationcontroller object inside your Appdelegate.h (like the existing window object). After that you import the h.File of a ViewController into the Appdelegate.m and init it like in the following example the menuviewcontroller.
stackoverflow
To call another view use following lines of code, so the navigationcontroller will handle everything for you.
#import Viewcontroller
ViewControllerName controllerVarName = [ViewControllerName alloc] init];
[self.navigationcontroller pushViewController:_ViewControllerName animated:YES];
Inside your specific ViewController use this line to set the title the Navigationcontroller will use:
self.title = #"titleName";

Can't set property in parentViewController

I have a view controller, MainViewController in which I have a UIButton as an IBOutlet that is connected up in Interface Builder
In the header I have
#interface MainViewController : UIViewController
{
SettingsViewController *settingsViewController;
UIButton *leftTextButton;
}
#property (nonatomic, retain) IBOutlet UIButton *leftTextButton;
in the .m I have synthesised the property
#synthesize leftTextButton;
I then add a settings subview as follows
SettingsViewController *settingsViewController = [[SettingsViewController alloc] initWithNibName:#"SettingsViewController"
bundle:nil];
[self.view.superview addSubview:settingsViewController.view];
Within the SettingsViewController I then try to update the title of the UIButton *leftTextButton in the parent view
[ ((MainViewController*) self.parentViewController).leftTextButton setTitle:#"Ahhhhh !!" forState:UIControlStateNormal];
Although this doesn't crash it doesn't work - the title of the button does not change.
Any help would be greatly appreciated
The parentViewController property is nil unless the view controller is presented modally, or is under a UINavigationController or a UITabBarController.
Give your SettingsViewController a mainViewController property:
#interface SettingsViewController
...
#property (assign) MainViewController *mainViewController;
Set it when you create the SettingsViewController:
SettingsViewController *settingsViewController = [[SettingsViewController alloc] initWithNibName:#"SettingsViewController" bundle:nil];
settingsViewController.mainViewController = self;
Then you can use it to update the button title:
[self.mainViewController.leftTextButton setTitle:#"Ahhhhh !!" forState:UIControlStateNormal];
If you just add the view controller's view in the parent view, the view controller will have no idea of its parent view controller, thus the parentViewController property is nil. You also have to add your SettingsViewController to your MainViewController via addChildViewController: and then notify the SettingsViewController via didMoveToParentViewController: that you have created this hierarchy. Then you should be able to access the parentViewController.
(Note: this is for iOS 5 only, if you're still on iOS 4 see rob mayoff's reply)

SplitViewController UI layout

I'm trying to create a UI whereby I have a SplitView with the Details area containing a TabBarController. The TabBarController will show 3 different types of detail for the item selected in the RootViewController of the SplitView.
So far, I've got the TabBar showing the SPlitView by doing the following;
1) Created a new SplitView based app.
2) Created a new TabBar based app
3) Copy the .xib, .h and .m files for the FirstView and SecondView controllers from the TabBar app into the SplitView app.
4) Added the following to my application delegate header file;
#class RootViewController;
#class DetailViewController;
#class FirstViewController;
#class SecondViewController;
#interface SplitViewTemplateAppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
UISplitViewController *splitViewController;
UITabBarController *tabBarController;
RootViewController *rootViewController;
DetailViewController *detailViewController;
FirstViewController *firstViewController;
SecondViewController *secondViewController;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) IBOutlet UISplitViewController *splitViewController;
#property (nonatomic, retain) IBOutlet RootViewController *rootViewController;
#property (nonatomic, retain) IBOutlet DetailViewController *detailViewController;
#property (nonatomic, retain) IBOutlet UITabBarController *tabBarController;
#property (nonatomic, retain) IBOutlet FirstViewController *firstViewController;
#property (nonatomic, retain) IBOutlet SecondViewController *secondViewController;
5) Opened up MainWindow.xib in IB and changed the class on the DetailsView to UITabController
6) Added the following code to my application delegate module file;
#import "FirstViewController.h"
#synthesize window, splitViewController, rootViewController, detailViewController, tabBarController, firstViewController, secondViewController;
-(void) makeTabBarController {
NSMutableArray *controllers = [NSMutableArray arrayWithArray:splitViewController.viewControllers];
int index = 0;
for (UIViewController *controller in splitViewController.viewControllers) {
if (index == 1) {
//NSLog(#"Index is: %#", index);
//NSLog(#"Controller name is: %#", controller.title);
UINavigationController *localNavController;
tabBarController = [[UITabBarController alloc] init];
NSMutableArray *localViewControllersArray = [[NSMutableArray alloc] initWithCapacity:2];
firstViewController = [[FirstViewController alloc] initWithNibName:#"FirstView" bundle:nil];
localNavController = [[UINavigationController alloc] initWithRootViewController:firstViewController];
localNavController.tabBarItem.title = #"First Tab";
[firstViewController release];
[localViewControllersArray addObject:localNavController];
[localNavController release]; // Retained by above array
secondViewController = [[SecondViewController alloc] initWithNibName:#"SecondView" bundle:nil];
localNavController = [[UINavigationController alloc] initWithRootViewController:secondViewController];
localNavController.tabBarItem.title = #"Second Tab";
[secondViewController release];
[localViewControllersArray addObject:localNavController];
[localNavController release]; // Retained by above array
tabBarController.viewControllers = localViewControllersArray;
[localViewControllersArray release]; // Retained thru above setter
//tabBarController.delegate = splitViewController;
[controllers replaceObjectAtIndex:index withObject:tabBarController];
}
index++;
}
splitViewController.viewControllers = controllers;
}
7) Added the following to the didFinishLaunchingWithOptions method;
[self makeTabBarController];
So now I get an out of the box SplitView with a tab bar controller on the right with two tabs in it. The tabs work for switching between the views.
A couple of things that I am now struggling with are;
The button to fire the Popover is missing, do I need to add this to each tab view?
How do I hook the RootViewController with the TabBarController so that details for the selected item is shown?
not sure if you're still looking for an answer for this. I ran into a very similar issue with the question about multiple detail views. In the end, I found and used this solution from the apple developer site:
SubstitutableDetailViewController
Their solution is very straightforward to implement and very understandable.
With regards to the second part of your question, you could have the TabBarController Delegate inform the view on the left hand side of the split view who the current detail view is. It could then use that information to provide updates to the proper view.

Programmatically changing a UILabel from the App Controller in a Navigation Based iOS App

I'm having a lot of trouble with what seems like a very simple thing. I cannot update a UILabel programmatically from a Navigation-based iOS App. I don't want to use a button as this label is designed to report the status of an external system, and should update on launch. There is no need to make the user go though the extra step on touching the button if I don't have to.
The following is a somewhat exhaustive list of the steps I've taken. I'm sorry if some of this seems unnecessary, but in my experience even the smallest forgotten step can be the cause of the issue.
From a fresh Navigation-based App in Xcode here are the steps I'm taking:
Replace UITableView with a generic UIView class
Re-wire File's Owner's view outlet to the new UIView
Add a UILabel to the center of the UIView, make the text centered, and leave the default text.
Save and Exit Interface Builder
RootViewController.h
#import <UIKit>
#interface RootViewController : UIViewController {
UILabel *myLabel;
}
#property (nonatomic, retain) IBOutlet UILabel *myLabel;
#end
RootViewController.m
#import "RootViewController.h"
#implementation RootViewController
#synthesize myLabel;
...
Removed TableView stuff from RootViewController.m
Wire IBOutlet myLabel to the Label in RootViewController.xib
Save and Exit Interface Builder
tempNavAppAppDelegate.m
...
- (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];
RootViewController *rootViewCont = navigationController.visibleViewController;
rootViewCont.myLabel.text = #"test";
NSLog(#"Label Text: %#", rootViewCont.myLabel.text);
return YES;
}
...
Build/Run
The Label shows as "Label" not "test". And the log reports:tempNavApp[94186:207] Label Text: (null)
I've tried a number of different ways to get this done, but any help would be appreciated.
The Journey
After discovering that my rootViewCont.myLabel was also nil, thanks to the help of mprudhom, I decided to test and see if I could assign myLabel.text a value in RootViewController.m's - (void)viewDidLoad method.
It worked, I was able to change the text directly from the RootViewController. But while this proved my View Controller wasn't broken, it did not solve my initial desire to change the UILabel from tempNavAppAppDelegate.m.
Elliot H. then suggested that navigationController.visibleViewController wasn't actually returning a view controller. I had tested for the value of rootViewCont and it came back as a RootViewController, but Elliot's suggestion got me thinking about the app's lifecycle and when the different parts of my code was actually loaded up.
So I started printing an NSLog at each step of the launch process (application:didFinishLaunchingWithOptions:, applicationDidBecomeActive:, viewDidLoad, viewDidAppear:), and discovered to my surprise that [self.window makeKeyAndVisible]; does not mean that the view will load before application:didFinishLaunchingWithOptions: is complete.
With that knowledge in hand I knew where the problem was. The solution (or at least my solution) seems to be NSNotificationCenter. I have now registered for notifications in tempNavAppAppDelegate and I am broadcasting a notification in RootViewController's viewDidAppear: method.
The Pertinent Code
RootViewController.h:
#interface RootViewController : UIViewController {
IBOutlet UILabel *myLabel;
}
#property (nonatomic, retain) UILabel *myLabel;
#end
RootViewController.m:
#implementation RootViewController
#synthesize myLabel;
- (void)viewDidLoad {
[super viewDidLoad];
NSParameterAssert(self.myLabel);
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[[NSNotificationCenter defaultCenter] postNotificationName:#"viewDidAppear" object:self];
}
tempNavAppAppDelegate.h:
#interface tempNavAppAppDelegate : NSObject {
UIWindow *window;
UINavigationController *navigationController;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) IBOutlet UINavigationController *navigationController;
- (void)viewDidAppearNotification:(id)notification;
#end
tempNavAppAppDelegate.m:
#implementation tempNavAppAppDelegate
#synthesize window;
#synthesize navigationController;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self.window addSubview:navigationController.view];
[self.window makeKeyAndVisible];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(viewDidAppearNotification:) name:#"viewDidAppear" object:nil];
return YES;
}
- (void)viewDidAppearNotification:(id)notification
{
NSString *noteClass = [NSString stringWithFormat:#"%#", [[notification object] class]];
if ([noteClass isEqualToString:#"RootViewController"]) {
RootViewController *noteObject = [notification object];
noteObject.myLabel.text = #"Success!";
}
}
If this code is printing nil:
rootViewCont.myLabel.text = #"test";
NSLog(#"Label Text: %#", rootViewCont.myLabel.text);
Then almost certainly it is because rootViewCont.myLabel itself is nil. Try logging the value of rootViewCont.myLabel as well and you'll see.
Are you sure you wired up the label to your UILabel IBOutput declaration in Interface Builder? That's most commonly the problem.
I personally always assert all my expected outlets in viewDidLoad so that I catch early on when the outlets have been (accidentally or not) been decoupled in Interface Builder. E.g.:
- (void)viewDidLoad {
[super viewDidLoad];
NSParameterAssert(rootViewCont.myLabel);
}
your interface should look like this
#import <UIKit>
#interface RootViewController : UIViewController {
// IBOutlet here...
IBOutlet UILabel *myLabel;
}
#property (nonatomic, retain) UILabel *myLabel;
#end
Is visibleViewController actually returning the view controller? My guess is since application:didFinishLaunchingWithOptions: hasn't returned yet, it's possible UINavigationController hasn't properly configured that property to return yet, even though you've added the navigation controller's subview to the view hierarchy, it's probably that visibleViewController isn't valid until after viewDidAppear: is called on the view controller in question.
Try having an IBOutlet to the RootViewController directly, or create it programmatically, and then assign the label text.
Just a general reminder: If an object is nil (in this case visibleViewController would be returning nil), and you send it a message, you won't crash, because messages to nil are valid and won't do anything. When you call the myLabel accessor on the rootViewCont object, if rootViewCont is nil, myLabel will return nil always.