Custom UINavigationController Implementation - objective-c

I've got a TabBarController in my app and in it I've got a few NavigationControllers. I've got all this stuff simply made in interface builder.
Now I want to implement my custom navigationcontroller so I created a class:
#import <UIKit/UIKit.h>
#interface DetailNavigationController : UINavigationController
#end
#implementation DetailNavigationController
- (id)initWithRootViewController:(UIViewController *)rootViewController
{
self = [super initWithRootViewController:rootViewController];
if (self) {
NSLog(#"I work!");
}
return self;
}
- (void) dealloc {
[super dealloc];
}
#end
And in Interface builder I added this class as Custom Class to the navigation controller I want. Now when I start the app and select the tab with this navigation controller it works but the initWithRootController is not called. I guess it's completely ignoring the class and runs as default navigation controller.
Do I need to specify anything more in interface builder or do I need to specify this controller programatically somewhere in tabbar delegate?
Thanks.

If you're creating the navigation controller in interface builder, you will need to override initWithCoder:, not initWithRootViewController:. The xib contains an instantiated version of your object, with the root view controller already set.

Related

Interface Builder: How to load view from nib file

I have a MyCustomView subclassed from NSView designed in a .xib.
I would like to insert this view into some of my other xib's round my application. How should I do this? If i drag a custom view and change the class to MyCustomView, but that does not load my xib-file. Can this only be done programmatically or is there a way to do this inside interface builder?
EDIT1:
Here is a very small demo-project:
http://s000.tinyupload.com/index.php?file_id=09538344018446482999
It contains the default MainMenu xib and my CustomView xib. I would like my CustomView.xib to be displayed inside the custom view added to my MainMenu.xib -- using as less code as possible.
For loading the view you need to add on your window:-
Created custom class of view inheriting to NSViewController
#import <Cocoa/Cocoa.h>
#interface NewViewController : NSViewController
#end
#import "NewViewController.h"
#implementation NewViewController
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Initialization code here.
}
return self;
}
#end
Your xib name is yourview.xib
- (void)windowDidLoad {
NSViewController *yourVC = [[NewViewController alloc] initWithNibName:#"NewViewController" bundle:nil];
[[[self window] contentView] addSubview:[yourVC view]];
}
Sounds like you need a container view. But I think you will have to use storyboard for it to be doable in interface builder.
Use a view controller as it will handle nib loading for you and provide a place to hook up IBOutlet and IBActions in a reusable way.
In your app delegate or whatever controller create an instance of your view controller.
Ask your view controller to load its view.
Cast the return type to your view class name.
Then keep a reference to your view controller and possibly the view.
Tell whatever view to add your view as a subview.
Add any layout constraints.
( you can build out very generic constraints to add themselves in your view or view controller by overriding viewDidMoveToSuperview or viewDidMoveToWindow when superview or window are not nil. Use the same to remove your constraints. )
Oddly you remove a view by telling it to remove itself from its superview.
I'd advise just doing it programmatically:
Add a View to your main xib/storyboard and set the custom class to your custom view's class
In your xib for your custom view, set the File's Owner class to your custom view's class
Hook up any IBOutlets, etc. as needed
Make a __strong property/ivar for holding a reference to the top level NSView of the xib
Implement initFromFrame in your custom view's class roughly as follows:
#interface CustomView ()
{
__strong NSView *nibView;
}
#end
#implementation CustomView
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
NSArray *nibObjects;
[[NSBundle mainBundle] loadNibNamed:#"CustomView" owner:self topLevelObjects:&nibObjects];
nibView = nibObjects[1];
[self addSubview:nibView];
}
return self;
}
The IBOutlet are connected up immediately after the loadNibNamed call, so you can do further initialization from there.
Another option is to do things purely programmatically:
1. In your custom xib, set the root View's class to your custom class
2. Implement awakeFromNib in your custom class to perform initialization
3. Call loadNibNamed: on your custom xib and programmatically add it to the user interface without interface builder.

Objective-c proper delegation

I'm new to objective-c and, maybe I haven't grassped the concept of delegation very clearly yet, but i hope to do it by using it. I'm trying to implement a delegation in my app.
Idea is that i have class TableViewController which has NSMutableArray used for TableView initialization. I need to reinitialize this Array from my DropDown class. I'v tried to do that using delegation but failed to do it yet, maybe there is something wrong with it. I could pass TableViewController to DropDown class and edit the table via object. But i'd like to get it done using delegation.
Here is my TableViewController.h
#protocol TableViewControllerdelegate;
#interface TableViewController : UIViewController<UITableViewDataSource,UITableViewDelegate,MFMessageComposeViewControllerDelegate>
{
ControllerType controllerType;
}
#property (retain, nonatomic) IBOutlet UITableView *tableView;
#property (retain, nonatomic) NSMutableArray *dataArray;
#property (retain, nonatomic) NSArray *imageArray;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil andType:(ControllerType)type;
- (void)sendSMS: (NSString *) sms;
#end;
Here is my DropDown.h
#import "TableViewController.h"
#interface DropDownExample : UITableViewController <VPPDropDownDelegate, UIActionSheetDelegate> {
#private
VPPDropDown *_dropDownSelection;
VPPDropDown *_dropDownSelection1;
VPPDropDown *_dropDownSelection2;
VPPDropDown *_dropDownSelection3;
VPPDropDown *_dropDownSelection4;
VPPDropDown *_dropDownDisclosure;
VPPDropDown *_msg;
VPPDropDown *_dropDownCustom;
NSIndexPath *_ipToDeselect;
}
+ (bool) uncheck:(UITableViewCell *) cell andData:(NSString *) data;
- (void)reloadData;
#end
And this is how i try to edit my tableview object array
TableViewController *newControll = (TableViewController*)[UIApplication sharedApplication].delegate;
NSMutableArray *arrayWithInfo = [[NSMutableArray alloc] initWithObjects:AMLocalizedString(#"Status", nil),AMLocalizedString(#"Call", nil),AMLocalizedString(#"Location", nil),AMLocalizedString(#"Control", nil),AMLocalizedString(#"Sim", nil),AMLocalizedString(#"Object", nil),AMLocalizedString(#"Info", nil),nil];
newControll.dataArray = arrayWithInfo;
[arrayWithInfo release];
[newControll.tableView reloadData];
I get it running, but it get's '-[AppDelegate setDataArray:]: unrecognized selector sent to instance after reaching this code.
OK, I am not sure if I got this right but it finally clicked for me what delegation is and why I need it. Hopefully you'll understand too once you read through my scenario.
History
Previously, in my UITabBar app, I wanted to show a custom form view overlaid on top of my view controller to enter name and email.
Later I also needed to show the same custom overlay on top of another view controller on another tab.
At the time I didn't really know what delegation was for, so the first method I used to tackle this problem was NSNotificationCenter. I duplicated the same code to my second view controller and hooked it up to a button press event.
On pressing a button on the second view controller on another tab, it certainly showed my custom overlay, just like my first view controller.
However, this is where the problem starts.
The Problem
I needed to close my custom form view. So using NSNotificationCenter, I posted a notification and the listener callback method for the notification was told to close my custom view.
The problem was, using NSNotificationCenter, all listeners both in my first tab and my second tab responded to the posted notification and as a result, instead of closing just the custom form view overlaid on top of my second view controller, it closed ALL my custom view, regardless of where the custom view was opened from.
What I wanted was when I tap on the "X" button to close my custom form view, I only want it to close it for that single instance of the custom view, not all the other ones I had opened.
The Solution: Delegation
This is where it finally clicked for me - delegation.
With delegation, I tell each instance of my custom form view who the delegate was, and if I was to tap on the "X" button to close my custom view, it only close it for that single instance that was opened, all the other view controllers were untouched.
Some Code
Right, down to some code.
Not sure if this is the best way to do it (correct me if I am wrong) but this is how I do it:
// ------------------------------------------------------------
// Custom Form class .h file
// ------------------------------------------------------------
#protocol MyCustomFormDelegate <NSObject>
// if you don't put a #optional before any method, then they become required
// in other words, you must implement these methods
-(void)sendButtonPressed;
-(void)closeButtonPressed;
// example: these two methods here does not need to be implemented
#optional
-(void)optionalMethod1;
-(void)optioinalMethod2;
#end
#interface MyCustomFormView : UIView
{
...
id<MyCustomFormDelegate> delegate;
}
...
#property (nonatomic, retain) id<MyCustomFormDelegate> delegate;
#end
// ------------------------------------------------------------
// Custom Form class .m file
// ------------------------------------------------------------
...
#implementation TruckPickerView
#synthesize delegate;
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self)
{
...
[btnSend addTarget:self selector:#selector(sendEmail) forControlEvent:UIControlEventTouchUpInside];
...
[btnClose addTarget:self selector:#selector(closeForm) forControlEvent:UIControlEventTouchUpInside];
}
return self;
}
-(void)sendEmail
{
// code sends email
...
// ------------------------------------------------------------
// tell the delegate to execute the delegate callback method
//
// note: the implementation will be defined in the
// view controller (see below)
// ------------------------------------------------------------
[delegate sendButtonPressed];
}
-(void)closeForm
{
// ------------------------------------------------------------
// tell the delegate to execute the delgate callback method
//
// note: the implementation will be defined in the
// view controller (see below)
// ------------------------------------------------------------
[delegate closeButtonPressed];
}
// ------------------------------------------------------------
// view controller .h file
// ------------------------------------------------------------
#import "MyCustomFormView.h"
// conform to our delegate protocol
#interface MyViewController <MyCustomFormDelegate>
{
...
// create a single instance of our custom view
MyCustomFormView *customForm;
}
#property (nonatomic, retain) MyCustomFormView *customForm;
// ------------------------------------------------------------
// view controller .m file
// ------------------------------------------------------------
#synthesize customForm;
-(void)viewDidLoad
{
customForm = [[MyCustomFormView alloc] initWithFrame:....];
// tell our custom form this view controller is the delegate
customForm.delegate = self;
// only show the custom form when user tap on the designated button
customForm.hidden = YES;
[self.view addSubview:customForm];
}
-(void)dealloc
{
...
[customForm release];
[super dealloc];
}
// helper method to show and hide the custom form
-(void)showForm
{
customForm.hidden = NO;
}
-(void)hideForm
{
customForm.hidden = YES;
}
// ------------------------------------------------------------
// implement the two defined required delegate methods
// ------------------------------------------------------------
-(void)sendButtonPressed
{
...
// email has been sent, do something then close
// the custom form view afterwards
...
[self hideForm];
}
-(void)closeButtonPressed
{
// Don't send email, just close the custom form view
[self hideForm];
}
You get that error, because (as the error says) you're sending a setDataArray: message to your app delegate (the AppDelegate class).
[UIApplication sharedApplication].delegate;
This will return the delegate of you app. There are a couple of ways to find out which class is your app's delegate, but usually it's called AppDelegate (as in your case) and it's implementing the UIApplicationDelegate protocol too.
You can't simply cast that to a completely different class. If your app delegate has an ivar or property of type TableViewController you have to use accessors to get it. If it's a property, you can use the dot notation. If it's an ivar, you can either implement a getter method that returns the ivar, or make it a property instead.
// assuming your app delegate has a TableViewController property called myTableViewController.
AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
TableViewController *tableViewController = appDelegate.myTableViewController;
This will fix the error, but your use of the delegate pattern is wrong too. I don't see where you're using any custom delegates. You forward declare a TableViewControllerdelegate protocol, but I don't see any declaration of it, or I don't see where you're trying to use it.

iOS Detecting Whether Tab is Changed

I have tabbed iOS application. I need to know which tab is active and detect when tab is changed. In storyboard I have a tab view controller, which changes the view when you click a tab fine. I created a class TabBarController and it is defined as follows:
Header
#interface TabBarController : UITabBarController <UITabBarControllerDelegate>
#end
Implementation
#import "TabBarController.h"
#implementation TabBarController
// In the initialization section, set the delegate
- (id) init
{
self = [super init];
if (self)
{
self.delegate = self;
}
return self;
}
- (void)tabBarController:(UITabBarController *)tabBarController
didSelectViewController:(UIViewController *)viewController
{
NSLog(#"controller class: %#", NSStringFromClass([viewController class]));
NSLog(#"controller title: %#", viewController.title);
}
#end
However, I couldn't detect tab changes with the code above. What do you think that the problem is?
I haven't linked my tab view to any outlets, but segues to other views. Is this the problem? Then, where should I link my outlet to?
Have you confirmed that your init method is being called? I don't think init is the designated initializer for UITabBarController and may not be called when loading the controller from a nib/storyboard.
If that's the case you may find it easier to set the delegate in your viewDidLoad since that will be called regardless of how the object is initialized or else make sure you set the delegate when -initWithNibName:bundle: or -initWithCoder is used to instantiate the object.
Solution to this is the implementation viewDidLoad as follows:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
NSLog(#"Tabs showed up!");
self.delegate = self;
}

Subclassing UIViewController, viewDidLoad called repeatedly

I subclassed UIViewController as STViewController and noticed that classes inheriting from STViewController have their viewDidLoad method being called repeatedly. Ultimately crashing the app. STViewController is basically a blank implementation at this point. I am subclassing as shown below:
#import "STViewController.h"
#interface WelcomeViewController : STViewController {
STViewController.h
#import <UIKit/UIKit.h>
#interface STViewController : UIViewController
{
}
#end
STViewController.m
#import "STViewController.h"
#implementation STViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)loadView
{
// Implement loadView to create a view hierarchy programmatically, without using a nib.
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#end
viewDidLoad() from WelcomeViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
// hide the buttons
[[self signUp] setHidden: YES];
[[self logIn] setHidden: YES];
}
You are overriding loadView, but your implementation is empty, and you're not assigning a view. Remove the loadView override.
From UIViewController Class Reference (emphasis mine):
You should never call this method directly. The view controller calls
this method when the view property is requested but is currently nil.
If you create your views manually, you must override this method and
use it to create your views. If you use Interface Builder to create
your views and initialize the view controller—that is, you initialize
the view using the initWithNibName:bundle: method, set the nibName and
nibBundle properties directly, or create both your views and view
controller in Interface Builder—then you must not override this
method.
The default implementation of this method looks for valid nib
information and uses that information to load the associated nib file.
If no nib information is specified, the default implementation creates
a plain UIView object and makes it the main view.
If you override this method in order to create your views manually,
you should do so and assign the root view of your hierarchy to the
view property. (The views you create should be unique instances and
should not be shared with any other view controller object.) Your
custom implementation of this method should not call super.

Protocol is not calling methods

I have a modal view which gets the user to select some data to add to a table. When the user presses a save button, the modal view should disappear and send the required data back to the view controller that presented the modal view for further processing. To achieve this, I have set up a protocol. The protocol method in the original view controller does not get called. My code is below, what am I doing wrong?
The header file (modal view controller):
#protocol AddTAFDataSource;
#interface AddTAFViewController : UIViewController <UIPickerViewDelegate, UIPickerViewDataSource> {
id<AddTAFDataSource> dataSource;
NSString *newICAOCode;
}
#property (nonatomic, assign) id<AddTAFDataSource> dataSource;
- (IBAction)saveButtonPressed;
#end
#protocol AddTAFDataSource <NSObject>
- (void)addNewTAF:(AddTAFViewController *)addTAFViewController icao:(NSString *)icaoCode;
#end
The implementation file (modal view controller):
#import "AddTAFViewController.h"
#import "TAFandMETARViewController.h"
#implementation AddTAFViewController
#synthesize dataSource;
...
- (IBAction)saveButtonPressed {
[self.dataSource addNewTAF: self icao: newICAOCode];
}
#end
Presenting view controller header file:
#import "AddTAFViewController.h"
#interface TAFandMETARViewController : UITableViewController <AddTAFDataSource> {
}
#end
And finally, the presenting view controller:
#import "AddTAFViewController.h"
...
- (void)insertNewObject:(id)sender {
AddTAFViewController *addTAFViewController = [[AddTAFViewController alloc] initWithNibName: #"AddTAF" bundle: [NSBundle mainBundle]];
addTAFViewController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[(AddTAFViewController *)self.view setDataSource: self];
[self presentModalViewController: addTAFViewController animated: YES];
addTAFViewController = nil;
[addTAFViewController release];
}
- (void)addNewTAF:(AddTAFViewController *)addTAFViewController icao:(NSString *)icaoCode {
newICAO = icaoCode;
[self dismissModalViewControllerAnimated: YES];
}
Just to remind, it is the above -(void)addNewTAF: method that does not get messaged. Any help/pointers in the right direction are much appreciated.
Replace:
[(AddTAFViewController *)self.view setDataSource: self];
With:
[addTAFViewController setDataSource:self]
After all, the dataSource is a property of the controller, not a controller's view.
Rather than trying to use a separate object (your dataSource) to pass data between the two view controllers, you could simply use add properties to contain the data directly in the view controller you're going to present modally (here, the AddTAFViewController).
Then in the method you use to dismiss the modal view controller, before dismissing it you can send [self modalViewController] to get the modal view controller, and at that point the parent view controller can send it any messages it wants. That would allow you to grab whatever data you need from the modal view controller, so you wouldn't need the data source and the protocol at all.
You are wrong at this point:
[(AddTAFViewController *)self.view setDataSource: self];
you should write this instead:
addTAFViewController.dataSource = self;