How to set UITableViewController custom class programmatically? - objective-c

This is my storyboard:
The UITableViewController, has a generic UITableCell (MMSwitchTableCell) that has an image, a label and switch.
The idea is to be able to create different UITableViewControllers that present different data with the same layout i.e with the same cell object and same behavior. for example one time the UITableView has a list of cells that helps you select fruits, second UITable helps you select furniture.
The two UITablesViewController have no relation between them (no inheritance or aggregation), they are different instances in different viewControllers, I only want to re-use the designed control and the UITableCell code.
So my code has a UIViewController where I declare a property:
#property (strong, nonatomic) MMGoSeePopoverTableViewController* goSeePopoverTableViewController;
and lazy load it:
-(MMGoSeePopoverTableViewController*) goSeePopoverTableViewController
{
if(_goSeePopoverTableViewController == nil)
{
_goSeePopoverTableViewController =(MMGoSeePopoverTableViewController*)
[self.storyboard instantiateViewControllerWithIdentifier:#"switchPopover"];
}
return _goSeePopoverTableViewController;
}
and a second UIViewController in which I declare a property:
#property (strong, nonatomic) MMLayersPopoverTableViewController* layersPopoverTableViewController;
and lazy load it:
-(MMLayersPopoverTableViewController*) layersPopoverTableViewController
{
if(_layersPopoverTableViewController == nil)
{
_layersPopoverTableViewController =(MMLayersPopoverTableViewController*)
[self.storyboard instantiateViewControllerWithIdentifier:#"switchPopover"];
}
return _layersPopoverTableViewController;
}
In the storyboard I've set the custom class to MMLayersPopoverTableViewController, instead I wish to leave it blank and somehow set it in the code. I guess I should do this inside the lazy loaders, but I can't figure how.
Edit
The suggested "This question may already have an answer here:" is not the same as what I'm asking. I have amended the post to explain my problem better.

The idea is to be able to create different UITableViewControllers that
present different data with the same layout i.e with the same cell
object & same behavior.
This sounds like a case where you should use a .xib file instead of a storyboard. The advantage of storyboards compared to .xib files is that you can see the structure of the app in terms of views and the corresponding view controllers. In your case, though, you're trying to reuse the same view with different view controllers. Putting your table in a .xib file that's owned by the view controller will let you load the same table, cell, etc. with whatever view controller you decide to instantiate.
In your .xib file, set the type of the File's Owner proxy to some common superclass of all your view controller classes which contains all the necessary functionality. For example, if all your view controllers are derived from UITableViewController and you don't need any special outlets, set the type to UITableViewController and connect the table to the proxy's tableView outlet. If your view controllers have other common behavior, put all that in a subclass of UITableViewController, use that as the proxy's type, and derive the other view controllers from that class.
Once you've done all that, you can use the -initWithNibName:bundle: method to initialize any of your view controllers and load the same view:
// in one place...
MMGoSeePopoverTableViewController *goSeeVC = [[MMGoSeePopoverTableViewController alloc]
initWithNibName:#"CommonTableView.xib" bundle:nil"];
// and in some other place...
MMLayersPopoverTableViewController *layersVC = [[MMLayersPopoverTableViewController alloc]
initWithNibName:#"CommonTableView.xib" bundle:nil"];

Related

Connecting drill-down NSSplitView outlets using Storyboards

I have an NSSplitView that manages a drill-down hierarchy. The parent/left side displays groups, while the child/right side receives notification that the group selection has changed, and updates to display the child items.
However: When creating an NSSplitView using storyboards, there are 3 scenes created: One for the split view itself, and one for each of the right/left NSViewController instances.
The problem here is that I have two controllers that are also acting as NSTableViewDataSource items, and the parent controller should have an IBOutlet to the child controller so that it can provide direct notification that the selection has changed.
But! Because these controllers are in different scenes, I cannot connect them. Nor can I move them both up to the parent scene for the split view, because they would then not have access to the NSTableView outlets. (Nor would the tables reference the controllers as delegates/datasources.)
Do I need to use NSNotification here? It seems so indirect and wishy-washy, and I haven't found a segue-based approach on the Mac.
The new NSSplitViewController and NSTabViewController class are what Apple is calling container controllers. you can get references to other controllers within the container using the new property's in NSViewController
#property (readonly) NSViewController *parentViewController
#property (copy) NSArray *childViewControllers
so for example if you would like to get a reference to your child Table controller from your parent table controller. In your parent table controller viewDidLoad you can do the following
myChildTableController = [self.parentViewController childViewControllers][1];
or vice versa from your child table controller
myParentTableController = [self.parentViewController childViewControllers][0];
As you say, Storyboards on OS X (and iOS before that), won't let you connect IBOutlets between scenes so you need to convey the actions in some other way.
I would configure the actions/delegates properties on the awakeFromNib method of any of the three controllers. At this moment the NSSplitView and its child controllers will all be created and connected.
I wouldn't use notifications when there are no multiple target objects, or the target objects are known and fixed, as in your case.
You don't necessarily want to use an NSSplitViewController. Instead, just drag an NSSplitView into the main view of your Storyboard.
As Cory explained in his answer, the new NSSplitViewController and NSTabViewController have methods for accessing parent and child controllers. This is useful if you really do want to divide your interface into different Storyboard scenes.
In your example though, you want all of the views to be in the same scene so that you can connect up all their IBOutlets to the same view controller. So, just use an NSSplitView and don't bother with an NSSplitViewController at all.
I was having the same problem and have found a workaround. It is not extremely elegant and it does not allow you to connect outlets with storyboard, but it does allow you to create relationships identical to those with IBOutlets.
The key is to implement a singleton class:
import Foundation
import Cocoa
class SplitViewConnector {
static let sharedInstance = SplitViewConnector()
var masterController:NSViewController?
var detailController:NSViewController?
}
Now in awakeFromNib method of your master controller you can set a master relationship and, similarly, you can set a relationship for your detail controller.
If, for example, your master controller needs to have a direct relationship to your detail controller you can also implement it. Assuming that the class of your master controller is YourViewController and the property for accessing your details controller is called myDetail it would look like:
var masterController:YourViewController? {
didSet {
self.assignDirectConnections()
}
}
var detailController:NSViewController? {
didSet
self.assignDirectConnections()
}
}
func assignDirectConnections() {
if let master = self.masterController {
if let detail = self.detailController {
master.myDetail = detail
}
}
}

Change the custom class in a storyboard using code when instantiating

I have a tab bar controller and a bunch of the same tabs. Each tab only differs in functionality, but the UI's are all the same. In the storyboard I designed the flow and UI of one tab and set it base class. Then when I create the tabs I tried typecasting them before adding them to the tab bar but it didn't work.
In the storyboard the View Controller indentified "TabView" has the custom class "TabColor"
TabRed *red = (TabRed *)[storyboard instantiateViewControllerWithIdentifier:#"TabView"];
TabBlue *blue = (TabBlue *)[storyboard instantiateViewControllerWithIdentifier:#"TabView"];
However the loadView method in TabColor gets called, not the TabRed/TabBlue.
Also if I nslog it the result is a TabColor object:
NSLog(#"%#", red)
Expected: TabRed
Actual: TabColor
tl;dr:
Storyboards and xibs contain collections of serialized objects. Specifying a class in a storyboard means you will get an instance of that class when you load the storyboard. A way to get the behavior you're looking for would be to use the delegation pattern common in cocoa/cocoa-touch.
Long Version
Storyboards, and similarly xib/nib files, are actually sets of encoded objects when you get down to it. When you specify a certain view is a UICustomColorViewController in the storyboard, that object is represented as a serialized copy of that an instance of that class. When the storyboard is then loaded and instantiateViewControllerWithIdentifier: gets called, an instance of the class specified in the storyboard will be created and returned to you. At this point you're stuck with the object you were given, but you're not out of luck.
Since it looks like you're wanting to do different things you could architect your view controller such that that functionality is handled by a different class using delegation.
Create a protocol to specify the functionality you'd like to be different between the two view controllers.
#protocol ThingDoerProtocol <NSObject>
-(void) doThing;
#end
Add a delegate property to your viewcontroller:
#interface TabColor
...
#property (strong, nonatomic) thingDoerDelegate;
And then have your new objects implement the protocol and do the thing you want them to.
#implementation RedTabDoer
-(void) doThing {
NSLog(#"RedTab");
}
#end
#implementation BlueTabDoer
-(void) doThing {
NSLog(#"BlueTab");
}
#end
Then create and hook up those objects when you load the storyboard.
TabColor *red = [storyboard instantiateViewControllerWithIdentifier:#"TabView"];
red.thingDoerDelegate = [[RedTabDoer new] autorelease];
TabColor *blue = [storyboard instantiateViewControllerWithIdentifier:#"TabView"];
blue.thingDoerDelegate = [[BlueTabDoer new] autorelease];
This should then allow you to customize the functionality of the view controller by changing the type of object that is assigned to the controllers delegate slot.
TabRed *red = (TabRed *)[storyboard instantiateViewControllerWithIdentifier:#"TabView"];
TabBlue *blue = (TabBlue *)[storyboard instantiateViewControllerWithIdentifier:#"TabView"];
Casting doesn't change values, it only changes the way the compiler interprets those values (and stops it from complaining when you use type in place of another). So casting a TabColor* to a TabRed* tells the compiler to pretend that your first pointer points to a TabRed instance, but it doesn't transmogrify the object that the pointer refers to into an instance of TabRed.
As waltflanagan explains, storyboards and .xib files contain actual objects, and the type of each object is determined when you create the file; you can't change it at run time. What you can do, though, is to have each of your several view controllers load the same view hierarchy. You don't even have to write any code to do this. Just create a .xib file containing your tab controller and the view controllers for each tab:
Be sure to set the type for each view controller appropriately in the .xib so that the right kind of view controller will be created for each tab:
Set the "NIB Name" field for each view controller to specify a .xib file that contains the view hierarchy that these controllers will use. If you specify the same .xib file for each controller, each controller will instantiate its own copies of those views:
Specify any IBOutlets in the common superclass of your view controllers so that all your view controllers have the same outlets. You can specify that superclass as the type of "File's Owner" in the common .xib file so that IB knows what outlets are available. File's owner is really a proxy for the object that's loading the .xib, so when one of your view controllers (TabRed for example) loads the common view .xib, that controller will be the one that the views in the .xib are connected to. When TabBlue loads the .xib, that object will be the one that those views are connected to.
This might seem confusing at first, but play with it. Understanding this will really help you understand .xib files (and therefore storyboards). They're a lot less magical than they seem when you're a beginner, but once you get it they'll seem even cooler.

Object mixup between programmatically created view and interface builder placeholder

I have a view controller which contains a scroll view. Inside the scroll view there is another UI core graphics view. In the view controller I create a temp object for the core graphics view and assign some data to it, then assign it to the attribute of the view controller. Eg: In the view controller:
#interface controller : UIViewController {
GraphView *graph;
}
#property ... IBOutlet GraphView *graph;
#implementation
GraphView *temp = [[GraphView alloc] init];
temp.someArray = anExistingDataArray;
self.graph = temp;
In IB, I open the view controller nib and add a scroll view, and embed a view and assign it the core graphics view class. Then hook up the IBOutlet from that view to the attribute in the view controller.
My problem is that the view controller creates the temp view object, assigns it to itself, with the correct data, however IB seems to instantiate its own object (different memory ID) and displays that, instead of the one in the view controller.
What is the correct way to build this type of setup?
If you drag an object into a nib, IB creates and archives that object. This is why you don't have to alloc/init views that you create in IB.
I'm guessing that you are creating your view in IB so that you can get the geometry correct, or...? It's rather unusual to create a view and then immediately replace it at run-time. More common, for geometric purposes, is to create a container view in IB and then add your programmatically-created views as subviews of that.
Your code left out the most important piece of this, though, which is when it's getting run. Is it in -init...? -awakeFromNib? -loadView? -viewDidLoad? The exact location matters since these occur in a well-defined sequence. If you put your code in the wrong place, it will run before the nib is unarchived and fully reconnected, so the nib will clobber whatever your code did.
So: when is your [self setGraph] (I can't bring myself to use dot syntax) code getting run?

Add UIView on UIViewController

I want to add a "custom" uiview onto a uiviewcontroller, this custom view i created with xib and its a seperate from the view controller,
does anyone know how to add a uiview with a xib into a uiviewcontroller?
Many thanks in advance
You mean an additional view, not the main controller view? In that case you can declare a property for the view and load the NIB by hand:
#interface Controller {}
#property(retain) IBOutlet UIView *extraView;
#end
…
- (void) viewDidLoad // or anywhere else
{
[[NSBundle mainBundle] loadNibNamed:#"extras" owner:self options:nil];
NSAssert(extraView != nil, #"The extra view failed to load.");
[[self view] addSubview:extraView];
}
This assumes that you set the Controller as the file owner in the Interface Builder and you link the view to the extraView outlet. Also note that there might be more elegant solutions, like inserting the extra view into the main NIB for your controller; depends on the situation.
It looks like you want the most common scenario – simply load an intialized custom UIView subclass into a controller.
Create a new XIB, called “View XIB” in the Xcode new file wizard.
In the Interface Builder select the File Owner object and on the Object Identity tab in the Object Inspector (Cmd-4) enter Controller (or however your controller class is named) into the Class field.
Do the same with the view, entering the name of your view class.
Ctrl+drag from the file owner to the view, you should be able to connect the view to the view outlet defined on your controller.
Save, let’s say Controller.xib.
In your code, initialize the controller using initWithNibName:#"Controller" bundle:nil. The initialization code should load the interface for you and set the view property to the view unpacked from the interface file.
Go through some Interface Builder tutorial, IB is a very nice tool and it’s good to be familiar with it.

iPhone subview design (UIView vs UIViewController)

I'm designing a simple Quiz application. The application needs to display different types of QuizQuestions. Each type of QuizQuestion has a distinct behavior and UI.
The user interface will be something like this:
alt text http://dl.getdropbox.com/u/907284/Picture%201.png
I would like to be able to design each type of QuizQuestion in Interface Builder.
For example, a MultipleChoiceQuizQuestion would look like this:
alt text http://dl.getdropbox.com/u/907284/Picture%202.png
Originally, I planned to make the QuizQuestion class a UIViewController. However, I read in the Apple documentation that UIViewControllers should only be used to display an entire page.
Therefore, I made my QuizController (which manages the entire screen e.g. prev/next buttons) a UIViewController and my QuizQuestion class a subclass of UIView.
However, to load this UIView (created in IB), I must[1] do the following in my constructor:
//MultipleQuizQuestion.m
+(id)createInstance {
UIViewController *useless = [[UIViewController alloc] initWithNibName:#"MultipleQuizQuestion" bundle:nil];
UIView *view = [[useless.view retain] autorelease];
[useless release];
return view; // probably has a memory leak or something
}
This type of access does not seem to be standard or object-oriented. Is this type of code normal/acceptable? Or did I make a poor choice somewhere in my design?
Thankyou,
edit (for clarity): I'd like to have a separate class to control the multipleChoiceView...like a ViewController but apparently that's only for entire windows. Maybe I should make a MultipleChoiceViewManager (not controller!) and set the File's Owner to that instead?
You're on the right track. In your QuizController xib, you can create separate views by dragging them to the xib's main window rather than to the QuizController's main view. Then you can design each view you need according to your question types. When the user taps next or previous, remove the previous view and load the view you need based on your question type using -addSubview on the view controller's main view and keep track of which subview is currently showing. Trying something like this:
[currentView removeFromSuperView];
switch(questionType)
{
case kMultipleChoice:
[[self view] addSubview:multipleChoiceView];
currentView = multipleChoiceView;
break;
case kOpenEnded:
[[self view] addSubview:openEndedView];
currentView = openEndedView;
break;
// etc.
}
Where multipleChoice view and openEndedView are UIView outlets in your QuizController connected to the views you designed in IB. You may need to mess with the position of your view within the parent view before you add it to get it to display in the right place, but you can do this with calls to -setBounds/-setFrame and/or -setCenter on the UIView.
Yeah, IB on iPhone really wants File's Owner to be a UIViewController subclass, which makes what you want to a bit tricky. What you can do is load the nib against an existing UIViewController instead of instantiating one using the nib:
#implementation QuizController
- (void) loadCustomViewFromNib:(NSString *)viewNibName {
(void)[[NSBundle mainBundle] loadNibNamed:viewNibName owner:self options:nil];
}
#end
That will cause the runtime to load the nib, but rather than creating a new view controller to connect the actions and outlets it will use what you pass in as owner. Since we pass self in the view defined in that nib will be attached to whatever IBOutlet you have it assigned to after the call.