Using interface builder to Create Loabable Views - iphone

New Method of Getting Items out of Nib Files.
Example in answer below.
This new Answer is the latest change in this project and Question
I want to create a UIView subclass and I want to create an Interface builder file for the UIView. How would I connect the files owner to itself. I want to be able to Design the view and have it create the connections but I dont have a need for a view controller.
Other than loading the nib into an array can I use the nib file as a connector to the view itself ?
I hope i am Asking this properly. I will elaborate tho.
Creating a UIViewController I am given the choice to create an xib file for it. At which time i can add the UIViews to the xib and those connections would be made. However the files owner does not appear to have a UI Editor associated to it so I want to create a UIView that will become a sub view in another view. I dont really want to create it with code because Its a pain to layout the items manually. So I want to use the same construct to create a small uiview to put into another
EDIT:
I Edited this again when I found another even better way to load nib classes. I figured some stack overflow users would want to see this.
I wanted to find an elegant way to load a UIView from a nib. and with some info from stack overflow user PeyloW I have managed to do just that.
I am going to post my results as the information will lead to a solution but this solution feels exceptionally elegant to me.
First of all, I created a Xib File named "TableViewCells.xib"
(Name Matches the Class Name therefore [[self class] description] == "TableViewCells")
Next I created the Classes that I am using for the views in the Nib.
Classes dont matter, Just Make sure the "Custom Class" is selected for the Class Objects in interface builder
Finally I created the Class "TableViewCells" to match the xib filename
My new Header File.
(Each of the header files imported associate to the items in the single xib file.)
#import <Foundation/Foundation.h>
#import "TitleCell.h"
#import "ToggleCell.h"
#import "ButtonCell.h"
#import "TextCell.h"
#interface TableViewCells : NSObject {
UINib *uiNibHolder;
}
#property (nonatomic, readonly) NSArray *nibArray;
// These are the classes that will be loadable.
- (TitleCell*) titleCell;
- (ToggleCell*) toggleCell;
- (ButtonCell*) buttonCell;
- (TextCell*) textCell;
- (id) loadViewByClass: (id) viewClass;
#end
and the Class file.
loadViewByClass: is the method that finds the item in the nib file.
the convenience accessors have the correct type for the class objects to be instantiated into.
NOTE: I would Not Recommend loading a Bunch of Views into this, the more views you load the more you have to create when you load the nib file.
#import "TableViewCells.h"
#implementation TableViewCells
#synthesize nibArray;
- (void) unloadProperties{
[uiNibHolder release];
}
- (NSArray *)nibArray{
return [uiNibHolder instantiateWithOwner:nil options:nil];
}
- (id) init{
if ((self = [super init]))
{
// Im using the class name for the Xib file.
// This means it will look in TableViewCells.xib
// You can change the class name, or pass a Nib name to this class
uiNibHolder = [[UINib nibWithNibName:[[self class] description] bundle: [NSBundle mainBundle]] retain];
}
return self;
}
- (TitleCell *)titleCell{
return [self loadViewByClass:[TitleCell class]];
}
- (ToggleCell *)toggleCell{
return [self loadViewByClass:[ToggleCell class]];
}
- (ButtonCell *)buttonCell{
return [self loadViewByClass:[ButtonCell class]];
}
- (TextCell *)textCell{
return [self loadViewByClass:[TextCell class]];
}
- (id)loadViewByClass:(Class)viewClass{
for (id item in self.nibArray)
{
if ([item isKindOfClass:viewClass])
{
return item;
}
}
return nil;
}
- (void)dealloc{
[self performSelector:#selector(unloadProperties)];
[super dealloc];
}
#end
Being able to load by class is incredibly nice. And if the class is not in the xib file it will return nil.
This could be adapted to use multiple nib files depending on the type of views you were loading. Using a lazy load property technique you could load those nibs only when needed leaving the memory footprint small and still allowing you to get the classes loaded through one convenient class loader.
As a demonstration I used a TableView to load a handfull of views
#synthesize tableViewCellLoader = _tableViewCellLoader;
- (TableViewCells *)tableViewCellLoader{
if (_tableViewCellLoader == nil){
_tableViewCellLoader = [[TableViewCells alloc] init];
}
return _tableViewCellLoader;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
switch (indexPath.row) {
case 0:
return self.tableViewCellLoader.toggleCell;
break;
case 1:
return self.tableViewCellLoader.textCell;
break;
case 2:
return self.tableViewCellLoader.titleCell;
break;
case 3:
return self.tableViewCellLoader.buttonCell;
break;
}
return nil;
}
As you can see I lazy loaded my TableViewCellLoader and if I wanted to I could make the TableViewCells class lazy load UINib objects only when their classes were called.
I love convenience in code.
And Still,
Thanks again PeyloW.

Files Owner is only a proxy object, it is not required to be specified at run-time, but will always be visible at design-time in Interface Builder.
You may ignore hooking anything up to Files Owner if you do not need it. The at run-time load the NIB-file using something like this:
NSArray* rootObject = [[NSBundle mainBundle] loadNibNamed:#"MyNib"
owner:nil
options:nil];
What you pass for the owner argument is what passes as the Files Owner proxy when loading the nib, nil works just fine.
The rootObject array contains all root level objects, but no proxies. Do note that the array is autoreleased, so if you need the loaded objects to stay around you must retain the array, or just the particular elements you are interested in.
Not that using loadNibNamed:owner:options: is IO bound. If performance is needed then you should use an instance of UINib to cache the NIB file in memory and instantiate objects from it.
UINib* myNib = [[UINib nibWithNibName:#"MyNib"
bundle:[NSBundle mainBundle]] retain];
// And then instantiate using:
NSArray* rootObject = [myNib instantiateWithOwner:nil
options:nil];

You can do this, but there is no reason you can't create IBOutlets in your UIView and connect directly to them. So in the xib file you would have a root UIView, make sure you change the class to your custom class. Then you can can set the outlets directly onto the view, you are not required to use the file owner.

Newest Answer by me
After much time and playing with this possibility I have tried a number of scenarios and came to the conclusion that the best possible scenario is to create the UINib for each request and get the object in question. A one time pass gives the best results. If you have an object you plan to make very often it is best to put it into its own nib, rather than in a nib with multiple different classes as each one will be created each time the UINib is initialized.
Here is the class that I use to accomplish this task.
LSObjectLoader.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#interface LSObjectLoader : NSObject {
UINib *nibHolder;
NSString *currentNibName;
NSArray *nibArray;
}
- (id) loadViewFromNib: (NSString*) nibName ofClassType:(Class) classType;
#end
LSObjectLoader.m
//
// LSObjectLoader.m
// APMobile
//
// Created by jason.mckinley on 2/8/12.
// Copyright (c) 2012 LoKi-Systems. All rights reserved.
//
#import "LSObjectLoader.h"
#implementation LSObjectLoader
- (id) loadViewFromNib: (NSString*) nibName ofClassType:(Class) classType
{
if (![currentNibName isEqualToString:nibName])
{
currentNibName = [nibName copy];
nibHolder = [UINib nibWithNibName:currentNibName bundle:[NSBundle mainBundle]];
}
nibArray = [nibHolder instantiateWithOwner:nil options:nil];
for (id item in nibArray)
{
if ([item isKindOfClass:classType])
{
return item;
}
}
return nil;
}
#end
Finally a couple ways to implement this.
Method 1 Loading Directly.
LSObjectLoader *oLoader = [[LSObjectLoader alloc] init];
MyClass *thisInstance = [oLoader loadViewFromNib:#"TableViewCells"
ofClassType:[MyClass class]];
Method 2 Class loading itself
Nib File "TableViewCells.xib" (Contains a "MyChatCell" class object)
Class File "MyChatCell.h/MyChatCell.m"
Header
#import <UIKit/UIKit.h>
#interface MyChatCell : UITableViewCell
#property (weak, nonatomic) IBOutlet UILabel *name;
#property (weak, nonatomic) IBOutlet UILabel *message;
#property (weak, nonatomic) IBOutlet UIImageView *image;
+ (MyChatCell*) newChatCell;
#end
Implementation
#import "MyChatCell.h"
#import "LSObjectLoader.h"
#implementation MyChatCell
+ (LSObjectLoader*) nibLoader{
static LSObjectLoader *_objectLoader;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_objectLoader = [[LSObjectLoader alloc] init];
});
return _objectLoader;
}
+ (MyChatCell *)newChatCell{
return [[self nibLoader] loadViewFromNib:#"TableViewCells" ofClassType:[self class]];
}
- (id)initWithCoder:(NSCoder *)aDecoder{
self = [super initWithCoder:aDecoder];
if (self){
// This is the init code for nib loading
}
return self;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
#end
Method 3 Custom Loader Class
Nib File "MyChatCells.xib" (Contains "MeChat" and "ThemChat" class objects)
Class File "MyChatCells.h/MyChatCells.m"
Header File
#import <UIKit/UIKit.h>
#import "MeChat.h"
#import "ThemChat.h"
#interface MyChatCells : NSObject
+ (MeChat*) newMeChat;
+ (ThemChat*) newThemChat;
#end
Implementation File
#import "MyChatCells.h"
#implementation MyChatCells
+ (LSObjectLoader*) nibLoader{
static LSObjectLoader *_objectLoader;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_objectLoader = [[LSObjectLoader alloc] init];
});
return _objectLoader;
}
+ (MeChat*)newMeChat{
return [[self nibLoader] loadViewFromNib:NSStringFromClass([self class])
ofClassType:[MeChat class]];
}
+ (ThemChat*)newThemChat{
return [[self nibLoader] loadViewFromNib:NSStringFromClass([self class])
ofClassType:[ThemChat class]];
}
#end

Related

How to access values from a different UIViewController

How can I access the value from an inputField located in a second viewController?
The class name of the second view controller is SettingsViewController and the outlet name for the inputField is setRateInput.
I tried this but it didn't work…
double taxRateFromInput = [[self.settings.setRateInput text]doubleValue];
when I NSLog it comes out as The value is: (null)
Any idea what I'm doing wrong?
Here is the implementation file for the main viewController:
#import "SettingsViewController.h"
#interface ViewController ()
#property (strong, nonatomic) SettingsViewController * settings;
#end
#implementation ViewController
// lazy instantiation
-( SettingsViewController *) settings
{
if (_settings == nil) {
_settings = [[SettingsViewController alloc]init];
}
return _settings;
}
- (IBAction)calculatePrice:(id)sender {
double taxRateFromInput = [[self.settings.setRateInput text]doubleValue];
#end
In theory, you could create a global. Create a new class, call it something like taxRate (.h and .m)
In taxRate.h, add the following code:
#import <Foundation/Foundation.h>
#class MyTaxRate;
#interface TaxRate : NSObject {
}
#property (nonatomic, retain) double * taxRateFromInput;
+(TaxRate*)getInstance;
#end
Then, in your controller, put a "#import taxRate.h" in there. In your .m file, add the following:
#import "TaxRate.h"
#implementation TaxRate
#synthesize taxRateFromInput;
static TaxRate *instance =nil;
+(TaxRate *)getInstance
{
#synchronized(self)
{
if(instance==nil)
{
instance= [TaxRate new];
}
}
return instance;
}
#end
Note: This is extremely similar in structure to what I'm purposing.
if you have the reference from the object view controller you can just access by the property from your attribute.
You instantiated a new SettingsViewController, but you didn't do anything to instantiate its textfield setRateInput. You can do it when you instantiate it:
_settings = [[SettingsViewController alloc]init];
_settings.setRateInput = [UITextField alloc] initWithFrame:someFrame]];
or, as a beter solution, instantiate the text field in -init of SettingsViewController
- init {
if (self = [super init] {
self.setRateInput = [UITextField alloc] initWithFrame:someFrame]];
}
return self;
}
If you use nib files, this would be a lot easier.
Note: setRateInput is a bad name for a property. Consider rateTextField instead.
Edit I forgot to add that you have to add the text field as a subview to its parent view.
So it will be like,
_settings = [[SettingsViewController alloc]init];
_settings.setRateInput = [[UITextField alloc] initWithFrame:someFrame] autorelease];
[_settings.view addSubView:_settings.setRateInput];
In this case, the setRateInput is retained by its super view. You're not using ARC, so you can call autorelease on your text field.
The better solution: Use - (void) loadView; inside SettingsViewController. Loading the view is the responsibility of the correspondent view controller.
- (void) loadView {
self.setRateInput = [[UITextField alloc] initWithFrame:someFrame] autorelease];
[self.view addSubView:_settings.setRateInput];
}
Edit: xib files and storyboards can help you out. Give these tutorials a try.
You are on the right track, also well done with your lazy instantiation (as
a demonstration that you grasped the concept, I mean).
But note, that outlets don't get connected until viewDidLoad is called. So if you
just alloc/init your viewController (lazily), the outlet to your textfield is pointing to nil.
The outlet doesnt get connected until your controller's view property is accessed, ie the view is displayed.
What you could do is give the settings viewController a handle to your calculating viewController and let it set a public property on the calculating viewController that represents the rate.
This is a common pattern - delegation - where one viewController (settingsViewcontroller) calls a method on its delegate (calculating viewController).
You wouldn't need the settingsViewcontroller property in your calculating viewController then, but just instantiate a new settings viewController every time you want it to be brought up, giving it a reference to your calculating viewController.
Another possibility - maybe even better - is to define a model object that does calculation and takes care of the rate it needs to calculate. Then you could give your settingsViewcontroller a reference to that model object (probably instantiated in your
other viewController), so that it can change the rate on it.
PS: also re think how you instantiate viewControllers generally. The designated initialiser is -initWithNibName:bundle: - so usually, you wouldn't just alloc/ -init them.
If you use storyboards (you probably should!), use storyboard's -instantiateViewControllerWithIdentifier: or use the above mentioned designated initialiser.

Sending data from NSObject to UIViewController

I have been working on this problem for close to 4 days now.
I am at the point where I think its not so much a problem with my code, but the structure of my application that is causing the issue.
I am trying to implement protocols and delegates to get an array from one NSObject(class) to a ViewController.
my code is pretty much line by line copied from this tutorial the only differences are in the face I have ARC turned on so have had to replace (nonatomic, retain) to (strong) and have not used dealloc :)
so with that being said its still not passing the data back to the viewcontroller. (highly annoying) I have tried dozens of different combinations of solutions that I have had help with and nothing has worked. This has lead me to believe that maybe there is an error in the structure of my application or the way things have been initialized etc, which I will attempt to explain now.
When my viewcontroller with tableview loads the viewdidload method called the delegate of my parser class, then once the first cell of the tableview has loaded it called my connection class and tells it to download some data from the server.
Inside my connection class I use NSURLConnection delegates from the apple library, in the delegate method connectionDidFinishLoading the data that has been downloaded is passed over to my parser class (however this is where i think its going wrong because i declare the object again.. which i think is where things are going amiss)
this is how I call my parser class from my connection class.
parserClass *myparser = [[EngineResponses alloc] init];
[myparser ReciveResponse:receivedData];
then once the data is in my parser class it gets parsed and then I try to pass the data across to my viewcontroller.. but its never accessing that delegate method that I set up.
Hopefully this is where the problem is because I just dont know where else I am going wrong.
what do you think?
UPDATE: heres my code -
ViewController.h
#import "EngineResponses.h" //delegates & protocols
interface SearchViewController : UITableViewController <PassParsedData> {
//delegates to parser class
EngineResponses *engineResponses;
//..
ViewController.m
#import "EngineResponses.h"
//this is where I set up the delegate/protocol for the parser class
- (void)viewDidLoad
{
[super viewDidLoad];
//..
engineResponses = [[EngineResponses alloc] init];
[engineResponses setMydelegate:self];
//..
}
//this is where i set up and call the connection class
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//..
if(indexPath.section == 0){
//..
if (indexPath.row == 0){
EngineRequests *engineRequests = [[EngineRequests alloc] init];
[engineRequests initalizePacketVariables:0 startCode:#"myReg" activationCode:#"myAct" methodName:#"GetStuff"];
//..
}
#pragma - Reciver methods
- (void)sendArray:(NSArray *)array
{
ICMfgFilterArray = array;
[self.tableView reloadData];
}
EngineRequests.m
//connection delegates etc..
//then I pass the data from the connection delegates over to the parser class
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
EngineResponses *engineResponses = [[EngineResponses alloc] init];
[engineResponses ReciveResponse:receivedData];
}
EngineResponses.h
#protocol PassParsedData
- (void)sendArray:(NSArray *)array;
#end
//..
id <PassParsedData> mydelegate;
//..
#property (strong) id <PassParsedData> mydelegate;
EngineResponses.m
- (void)parserDidEndDocument:(NSXMLParser *)parser
{
//..
[[self mydelegate]sendArray:filteredArray];
}
1
Allright. I will re-do it based on your updated code. To make it easy I copy your code and do the amendments.
ViewController.h
#import "EngineResponses.h" //delegates & protocols
interface SearchViewController : UITableViewController <PassParsedData> {
//delegates to parser class
EngineResponses *engineResponses;
EngineRequests *engineRequests;
//..
Explanation:
You are using ARC. If you define the pointer locally, as you did before, and to not
retain it - which you can't because of ARC - then it will be released directly after its
creation. You will have to keep at least one reference to the object.
Bare in mind that ARC means Automatic Reference Counting. As soon as there is no
reference to an object it will be released.
This proposal with the engineRequests object defined here only works while you
submit only one request at a time. If you have several requests, i.e. for more than one cell or
whatver, then you may go for a mutable array or mutable dictionary where you keep the requests while you use them.
ViewController.m
#import "EngineResponses.h"
//this is where I set up the delegate/protocol for the parser class
- (void)viewDidLoad
{
[super viewDidLoad];
//..
engineResponses = [[EngineResponses alloc] init];
[engineResponses setMydelegate:self];
engineRequests = [[EngineRequests alloc] init]; // Use instance variable instead of local variable
[engineRequests setEnineResponses:engineResponses];
//..
}
//this is where i set up and call the connection class
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//..
if(indexPath.section == 0){
//..
if (indexPath.row == 0){
[engineRequests initalizePacketVariables:0 startCode:#"myReg" activationCode:#"myAct" methodName:#"GetStuff"];
//..
}
#pragma - Reciver methods
- (void)sendArray:(NSArray *)array
{
ICMfgFilterArray = array;
[self.tableView reloadData];
}
Explanation: The engineRequets is now an instance varaible and should not be re-defined locally.
You could define a variable of the same name locally which would hide the instance variable. I think
in that case you get a compiler warning but that will work and will most probably confuse you.
Again, if you use more than one request at a time then this solution will not work!
EngineRequests.h
EngineResponses *engineResponses;
EngineRequests.m
#synthesize engineResponses;
//connection delegates etc..
//then I pass the data from the connection delegates over to the parser class
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
//EngineResponses *engineResponses = [[EngineResponses alloc] init]; //This Object has already been created!
[engineResponses ReciveResponse:receivedData];
}
Explanation: Here, too, the reference to EngineResponses is now an instance variable, not a locally defined one. The object will not be newly created but it references to that very object that was created in the view controller. That is the one EngineResponses that 'knows' its view controller object and can therefore pass back the parsed data.
EngineResponses.h
#protocol PassParsedData
- (void)sendArray:(NSArray *)array;
#end
//..
id <PassParsedData> mydelegate;
//..
#property (strong) id <PassParsedData> mydelegate;
EngineResponses.m
- (void)parserDidEndDocument:(NSXMLParser *)parser
{
//..
[[self mydelegate]sendArray:filteredArray];
}
... give it a try :)
Always check for nil objects. Sending a message to a nil object will do nothing and your app will continue. I bet this is the problem since you are locally allocing all over the place. Why dont you make the receivedata method a static method instead since it looks like you dont need these classes for more than a few moments for some calculations and parsing. Then nil objects wont be a factor.

Objective C access variables from other classes?

I'm trying to get a variable from a class that I made from a table view. Basically what I want this to do is tell the other controller what row was selected so this is what I tried to do.
Table View Class .h file:
#property(nonatomic) NSInteger itemId;
-(NSInteger)itemId;
I would then make methods that set and get the variable in the .m file of the Table View Class
(I synthesized it and did all that stuff, I'm just showing you the methods)
-(NSInteger)itemId {
return self.itemId;
}
And now the table cell selected method...
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)
indexPath {
NSUInteger row = [indexPath row];
self.itemId = row;
//segue stuff (if you want me to include this just let me know)
}
Thats all for that class and now the class that I need the value for
View Controller Class .h that is being pushed via segue
#property (nonatomic) NSInteger itemId;
View Controller Class .m
#import "TableViewController.h"
//Skip a few things
#synthesize itemId;
//skip a few things
-(void)viewDidLoad {
TableViewController *tvc = [[TableViewController alloc] init];
itemId = [tvc itemId];
NSLog(#"%i", itemId);
For some reason this doesn't work... When I print out the "itemId" in the "didselectrow" method it returns the right number but when I try to print it out in the other class it just gives me '0'
Any thoughts?
If there are things that I left out that you want to see in my code I'd be more than happy to write it out :) I just wanted to save time and space by cutting down the code a little.
Thanks in advance!
EDIT:
I did find a possible solution but it involves using the delegate. I'm sure there's got to be a better way of doing this so if you have any ideas, just let me know.
TableViewController *tvc = [[TableViewController alloc] init];
Doing this in a different class you create&initialize a new object, so it will be nill(for int 0). For sharing data between classes&view controllers use Delegates and properties.
With a little code i shall explain;
#import <UIKit/UIKit.h>
//YourClassNameAppDelegate.m
#interface YourClassName : UIResponder <UIApplicationDelegate>
{
NSString *uName;
NSDictionary *YourDictionary;
}
#property (copy, readwrite) NSString *uName;
#property (copy, readwrite) NSString *YourDictionary;
And in the other class you want to use this string and dictionary,
//import your delegate class
#import "YourClassNameAppDelegate"
.
.
.
//to me, do this in viewDidLoad(or something like that) method of new view controller
YourClassNameAppDelegate *sharedData= (YourClassNameAppDelegate *)([[UIApplication sharedApplication]delegate]);
.
.
-(IBAction)sharedData{
NSLog(#"Shared String: %#\n And Shared Dictionary: %#, sharedData.uName, sharedData.YourDictionary);
}
The - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *) is not invoked - you have to select a row to change the value of itemId, and that is not possible between the initialization of the table and the itemId = [tvc itemId]; line.
this code may helps you.
#import <UIKit/UIKit.h>
extern int outsider;
#interface ViewController : UIViewController {
}
#property int outsider;
#end
to all the .m files you #import the .h file where you declared the variable in can acces it.
You are creating a new instance of your table view controller within your detail view controller, this is incorrect and is not going to give you a reference to your original table.
Simply create a property on your pushed view controller which holds whatever detail information you want to pass. Then set this in prepareForSegue: in your table view controller, where segue.destinationViewController will give you a pointer to the VC that is about to appear.
Also, fix your accessor method as suggested by Yuji in comments.
I suggest saving the indexPath for the pressed row in a singleton. Here is a guide on how to use the singleton class to store objects and use them across classes.
In this example you can also just declare an instance variable in the singleton and set it when the row is pressed in the appropriate delegate method for your UITableView. This could be done like so:
#import <Foundation/Foundation.h>
#interface Singleton : NSObject {
NSIndexPath *tableViewPath;
}
#import "DataContainerSingleton.h"
#implementation DataContainerSingleton
#synthesize tableViewPath;
static DataContainerSingleton* _theDataContainerSingleton = nil;
+ (DataContainerSingleton*) theDataContainerSingleton;
{
if (!_theDataContainerSingleton)
_theDataContainerSingleton = [[DataContainerSingleton alloc] init];
return _theDataContainerSingleton;
}
- (id)init
{
self = [super init];
if (self) {
tableViewPath = [NSIndexPath indexPathForRow:0 inSection:0];
}
return self;
}
In your view controller with the UITableView just do like this:
#implementation
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
tableViewPath = indexPath;
}
You should adjust your initialization to what would be appropriate for your specific program!
Try this:
#property NSInteger itemId;
Hope this helps you

Best Practice: Presenting a Subclassed UIView (with own xib) as subview of UIViewControllers

My goal is, to have a subclassed UIView (lets call it infoView) designed in his own XIB so that I can present it in many UIViewController's.
The Problem:
So far, when I was adding UIView's to a UIViewController I always had to make an UIViewController the file's owner of the UIView's .xib file to load the view with something like:
...
//this is inside the calling UIViewController's method
// InfoView *infoView is ivar and a subclass of UIView
infoView = nil;
NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:#"InfoView"
owner:self options:nil];
for (id object in bundle) {
if ([object isKindOfClass:[InfoView class]])
infoView = (InfoView *)object;
}
[[self view] addSubview:infoView];
...
But I want to use the same UIView in many different UIViewController's, so I actually don't want a file's owner except maybe the class itself. In ThomasM's question he was setting the UIView itself to be the file's owner but without success.
In the answers there I found a solution to set the file's owner to nil. To do so I had to add all calling UIViewController objects from the Interface Builder object library to the InfoView.xib file and connect them with their infoView outlets.
But this doesn't feel right. So here I would like to collect solutions to
encapsulate a UIView together with his xib-file to use it in many different view controllers. How do you guys handle that?
Thx for any help.
EDIT:
The infoView is something like an overlay which appears when the user presses a button on one of the view controllers. It's NOT the View controllers "main" view. It gives detailed informations about the view of his superviews view controller and will disappear afterwards. I only fill the infoView with different contents threw out all the calling view controllers.
Like Hollance answer was pointing out I am using UINib.
To use it, leave the .xib files owner nil and place all customization of the infoView inside the initWithCoder: method of your InfoView class implementation. This will get called if you obtain the InfoView.xib like:
// here InfoView is the name of the .xib file
UINib *infoNib = [UINib nibWithNibName:#"InfoView" bundle:nil];
NSArray *topLevelObjects = [infoNib instantiateWithOwner:self options:nil];
QInfoView *infoView = [topLevelObjects objectAtIndex:0];
So you want to load a UIView from a nib that you wish to use in more than one UIViewController, and you want to connect it to an outlet on each of those view controllers. Is that correct?
Then make a UIViewController subclass (let's call it FakeViewController) with an IBOutlet property. Set that FakeViewController as the nib's File's Owner and connect your UIView to its outlet.
Done.
You just need to make sure all your other view controllers also have these outlet properties (although they don't need to be IBOutlets), but the nib loader doesn't actually check to make sure the class that you pass into the owner parameter equals the class name you specified in Interface Builder. So you can fake it.
Oh, and if you're OS 4.0 and higher, use UINib to load the nib file.
And yet another way is to create your own "controller" based on NSObject to define your own life-circle (instead of standard UIViewController life-circle).
For example:
BaseSubview.h:
#interface BaseSubview : NSObject {
UIView* _view;
}
#property (nonatomic, retain) IBOutlet UIView* view;
- (void)myMethod;
#end
BaseSubview.m:
#import "BaseSubview.h"
#implementation BaseSubview
#synthesize view = _view;
- (id)init
{
self = [super init];
if (self) {
// ...
}
return self;
}
- (void)dealloc
{
[_view removeFromSuperView];
[super dealloc];
}
- (void)myMethod
{
// view specific logic here
_view.backgroundColor = [UIColor redColor];
}
#end
InfoView.h:
#import "BaseSubview"
#interface InfoView : BaseSubview {
UILabel* _labelInfo;
}
#property (nonatomic, retain) IBOutlet UILabel* labelInfo;
#end
InfoView.m:
#import "InfoView.h"
#implementation InfoView
#synthesize labelInfo = _labelInfo;
- (id)init
{
self = [super init];
if (self) {
// ...
}
return self;
}
- (void)myMethod
{
// view specific logic here
_labelInfo.text = #"current time...";
[super myMethod];
}
#end
InfoView.xib:
file owner is InfoView
assign of outlets as usual
view is parent all other controls (such as labels, etc)
HugeAndComplicatedViewController.h:
// ...
// among other var definitions
InfoView* _infoView;
// ...
HugeAndComplicatedViewController.m, most interesting part:
// when you decide to show your view
// probably in loadView
_infoView = [[InfoView alloc] init];
[[NSBundle mainBundle] loadNibNamed:#"InfoView" owner:_infoView options:nil];
[self.view addSubview:_infoView.view];
// possibly perform specific logic
[_infoView myMethod];
// no need sub-view any more
// probably in dealloc
[_infoView release];
So now you have your own sub-view with logic and design separated from "Huge & Complicated" view-controller. It can have any life-circle you need for your current project.
does infoView need to be a subview?
in your viewController:
-(id) init {
self = [super initWithNibName:#"myNib" bundle:nil];
if (self) {
// code here
}
}

UIView subclass with its own XIB [duplicate]

This question already has answers here:
UIView and initWithFrame and a NIB file. How can I get the NIB file loaded?
(5 answers)
Closed 7 years ago.
I created a custom UIView subclass, and would prefer to not layout the UI in code in the UIView subclass. I'd like to use a xib for that. So what I did is the following.
I created a class "ShareView" which subclasses UIView. I created a XIB file with its file's owner set to "ShareView". Then I link some outlets I declared in my "ShareView.h".
Next I have a ViewController, MainViewController, which adds the ShareView as a subview. whith this code:
NSArray *arr = [[NSBundle mainBundle] loadNibNamed:#"ShareView" owner:nil options:nil];
UIView *fv = [[arr objectAtIndex:0] retain];
fv.frame = CGRectMake(0, 0, 320, 407);
[self.view addSubview:fv];
But now I get NSUnknownKeyException errors on the outlets I declared in my ShareView.
The reason I did all this is because I want a UIView, with its own logic in a seperate XIB file. I read in several places that ViewControllers are only used to manage a full screen, i.e. not parts of a screen...
So what am I doing wrong? I want my logic for ShareView in a seperate class, so my MainController class doesn't get bloated with logic from ShareView (which I think is an aption to solve this problem?)
ThomasM,
We had similar ideas about encapsulating behavior inside a custom view (say, a slider with companion labels for min/max/current values, with value-changed events also handled by the control internally).
In our current best-practice, we would design the ShareView in Interface Builder (ShareView.xib), as described by Eimantas in his answer. We then embed the ShareView to the view hierarchy in MainViewController.xib.
I wrote up how we embed custom-view Nibs inside other Nibs in our iOS developer blog. The crux is overriding -awakeAfterUsingCoder: in your custom view, replacing the object loaded from MainViewController.xib with the one loaded from the "embedded" Nib (ShareView.xib).
Something along these lines:
// ShareView.m
- (id) awakeAfterUsingCoder:(NSCoder*)aDecoder {
BOOL theThingThatGotLoadedWasJustAPlaceholder = ([[self subviews] count] == 0);
if (theThingThatGotLoadedWasJustAPlaceholder) {
// load the embedded view from its Nib
ShareView* theRealThing = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([ShareView class]) owner:nil options:nil] objectAtIndex:0];
// pass properties through
theRealThing.frame = self.frame;
theRealThing.autoresizingMask = self.autoresizingMask;
[self release];
self = [theRealThing retain];
}
return self;
}
You defined owner of the loaded xib as nil. Since file owner in xib itself has outlets connected and is defined as instance of ShareView you get the exception about unknown keys (nil doesn't have outleted properties you defined for ShareView).
You should define the loader of the xib as owner (i.e. view controller responsible for loading the xib). Then add separate UIView object to xib and define it as instance of ShareView. Then when loading the xib.
ShareView *shareView = [[[[NSBundle mainBundle] loadNibNamed:#"ShareView" owner:self options:nil] objectAtIndex:0] retain];
You can also define shareView as an IBOutlet in view controller's interface (and connect the outlet from file owner to that view in the xib itself). Then when you load the xib there won't be any need for reassigning the shareView instance variable since the xib loading process will reconnect the view to the instance variable directly.
I would like to add to the answer. I hope people would improve this answer though.
First of all it DOES work.
XIB:
Result:
I would like to subclass UIView for a long time especially for tableViewCell.
This is how I did it.
It's succesful, but some part is still "awkward" in my opinion.
First I created a usual .h, .m, and xib file. Notice that Apple do not have the check box to automatically create an xib if the subclass you created is not a subclass of UIViewController. Well create those anyway.
#import <UIKit/UIKit.h>
#import "Business.h"
#interface BGUIBusinessCellForDisplay : UITableViewCell
+ (NSString *) reuseIdentifier;
- (BGUIBusinessCellForDisplay *) initWithBiz: (Business *) biz;
#end
Really simple UITableViewCell, that I want to initialize latter with biz.
I put reuseidentifier which you should do for UITableViewCell
//#import "Business.h"
#interface BGUIBusinessCellForDisplay ()
#property (weak, nonatomic) IBOutlet UILabel *Title;
#property (weak, nonatomic) IBOutlet UIImageView *Image;
#property (weak, nonatomic) IBOutlet UILabel *Address;
#property (weak, nonatomic) IBOutlet UILabel *DistanceLabel;
#property (weak, nonatomic) IBOutlet UILabel *PinNumber;
#property (strong, nonatomic) IBOutlet BGUIBusinessCellForDisplay *view;
#property (weak, nonatomic) IBOutlet UIImageView *ArrowDirection;
#property (weak, nonatomic) Business * biz;
#end
#implementation BGUIBusinessCellForDisplay
- (NSString *) reuseIdentifier {
return [[self class] reuseIdentifier];
};
+ (NSString *) reuseIdentifier {
return NSStringFromClass([self class]);
};
Then I eliminated most init codes and put this instead:
- (BGUIBusinessCellForDisplay *) initWithBiz: (Business *) biz
{
if (self.biz == nil) //First time set up
{
self = [super init]; //If use dequeueReusableCellWithIdentifier then I shouldn't change the address self points to right
NSString * className = NSStringFromClass([self class]);
//PO (className);
[[NSBundle mainBundle] loadNibNamed:className owner:self options:nil];
[self addSubview:self.view]; //What is this for? self.view is of type BGCRBusinessForDisplay2. That view should be self, not one of it's subview Things don't work without it though
}
if (biz==nil)
{
return self;
}
self.biz = biz;
self.Title.text = biz.Title; //Let's set this one thing first
self.Address.text=biz.ShortenedAddress;
//if([self.distance isNotEmpty]){
self.DistanceLabel.text=[NSString stringWithFormat:#"%dm",[biz.Distance intValue]];
self.PinNumber.text =biz.StringPinLineAndNumber;
Notice that it's really awkward.
First of all the init can be used in 2 ways.
It can be used to right after aloc
It can be used by we having another existing class and then we just want to init that existing cell to another biz.
So I did:
if (self.biz == nil) //First time set up
{
self = [super init]; //If use dequeueReusableCellWithIdentifier then I shouldn't change the address self points to right
NSString * className = NSStringFromClass([self class]);
//PO (className);
[[NSBundle mainBundle] loadNibNamed:className owner:self options:nil];
[self addSubview:self.view]; //What is this for? self.view is of type BGCRBusinessForDisplay2. That view should be self, not one of it's subview Things don't work without it though
}
Another icky things that I did is when I do [self addSubview:self.view];
The thing is I want self to be the view. Not self.view. Somehow it works nevertheless. So yea, please help me improve, but that's essentially the way to implement your own subclass of UIView.
You can create your custom UIView designed in xib and even make Interface Builder to display it inside other xib files or storyboards in new Xcode 6 using IB_DESIGNABLE. In xib set file owner to your custom class but do not set UIView class to avoid recurrency loading problems. Just leave default UIView class and you will add this UIView as a subview of your custom class view. Connect all your outlets to file owner and in your custom class load your xib like in the code below. You can check my video tutorial here: https://www.youtube.com/watch?v=L97MdpaF3Xg
IB_DESIGNABLE
#interface CustomControl : UIView
#end
#implementation CustomControl
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder])
{
[self load];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
[self load];
}
return self;
}
- (void)load
{
UIView *view = [[[NSBundle bundleForClass:[self class]] loadNibNamed:#"CustomControl" owner:self options:nil] firstObject];
[self addSubview:view];
view.frame = self.bounds;
}
#end
If you are using autolayout then you might want to change: view.frame = self.bounds; to:
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[view]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view)]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[view]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view)]];
To use Yang's pattern with Auto-Layout, you need to add the following somewhere in the -awakeWithCoder: method.
theRealThing.translatesAutoresizingMaskIntoConstraints = NO;
If you don't turn off -translatesAutoResizingMaskIntoConstraints it can cause your layout to be incorrect as well as causing a LOT of debugging nonsense in the console.
EDIT: Auto-layout can still be a pain. Certain constraints aren't respected, but other are (e.g. pinning to the bottom doesn't work but pinning to the top does). We're not exactly sure why, but you can work around this by manually passing constraints from the placeholder to theRealThing.
It's also worth noting that this pattern works just the same way with Storyboards as it does with regular .xibs (i.e. you can create a UI Element in a .xib and drop it into a StoryBoard View controller by following your steps.)
Instead of subclassing UIView why don't you subclass UIViewController. Check out the following link. In that made a "RedView" and "BlueView" UIViewControllers with their xibs and added them to the MultipleViewsController view by creating and instance of the former two classes and adding [self.view addSubview:red.view] and [self.view addSubview:blue.view] in the MultipleViewsController's viewDidLoad method
MultipleControllers in one view
Just add (id)sender to the button pressed function in RedView and BlueView in the code of the above link.