I have a view called FeedView, handled by FeedViewController.
I also have a XIB called "NearestStore" which is handled by a view controller named "NearestStoreViewController". NearestStore xib has labels, buttons, etc. In the view controller I have outlets that are connected to the subviews in NearestStore.xib.
NearestStore inherits from UIButton (so it's easier to handle click event).
On FeedViewController.xib I have a UIButton that has been set to be of type NearestStore.
So far so good. This is on my FeedViewController:
__weak IBOutlet NearestStoreButton *btn_nearestStore;
The outlet is connected on the xib to the outlet.
NearestStoreViewController has several outlets to subviews like:
#property (nonatomic, weak) IBOutlet UILabel *lbl_distance;
#property (nonatomic, weak) IBOutlet UIImageView *img_distance;
For some reason, on my FeedViewController the reference to btn_nearestStore is fine, but all the subviews are nil.
For example:
btn_nearestStore.lbl_distance
is nil
What am I missing?
This sounds exactly as the system is supposed to work. It is not easy to create a custom widget using xibs.
Here's how it works:
Your FeedViewController will preform xib loading for the corresponding FeedView.
During this load, it notices the NearestStoreButton subview. As a consequence, it creates such a view using the - (id)initWithCoder: message on the NearestStoreButton class. It will not magically notice the corresponding .xib nor the corresponding viewController.
If you need to use a xib within a xib, you need to do the loading manually for all subviews. Keep in mind that you somehow need to create/use the appropriate owners (view controllers) for these secondary xibs.
It's hard to tell from your description, but this sounds like a problem with "owner" of the loaded NearestStoreButton XIB. When you load a NIB, you give the loader an owner, and its this owner on which most outlet bindings and actions are made. If you're loading your NearestStoreButton with UINib, then when you call instantiateWithOwner:options:, make sure you pass the object on which the outlets should be set as the owner.
When are you calling the outlet? If you are trying to access the property in the initWithCoder method of the view, it's not guaranteed that object has been instantiated.
If you access your property in the awakeFromNib method within the view you should be able to get it. For instance, I have a custom view, and my code looks as such:
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
//Don't style the subviews in here since they won't be initialized
}
return self;
}
- (void)awakeFromNib
{
[super awakeFromNib];
[self styleViews];
}
- (void)styleViews
{
//access my properties and style them in here
}
Following post contains detailed explanation about creating custom views using Nib:
creating custom view using Nib
After I create the customInit as mentioned in the post, I am able to get the IBOutlets allocated.
Related
Newbie question for Xcode gurus...
I have two views. They both use the same custom class. In view_1 I have a button and when this is pressed view_2 will show. In view_2 I have a label which will have it´s text changed when I press the button in view_1. As of now the Label_1 is nil when I set a breakpoint at it and therefor useless. How can I get to update this label when I press the button? Her are some snippets from my code...
This is my .h file:
#interface ViewController : UIViewController
{
IBOutlet UIButton *buttonSelectTimeInterval;
IBOutlet UILabel *labelTimer;
}
#end
This is the button action in my .m file:
- (IBAction)startPouring_ButtonClick:(id)sender
{
labelTimer.text = #"foo";
}
…but my .m file doesn't seem to know the labelTimer since it is a ´nil´. Why is this so? It is instantiated in the .h file.
Anyone?
You can use NSNotificationCenter. Put this in you IBAction.
[[NSNotificationCenter defaultCenter] postNotificationName:#"buttonPressed" object:nil];
And this to your viewDidLoad.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(selectorhere) name:#"buttonPressed" object:nil];
somewhere in your .m
(void)selectorhere {
labelTimer.text = #"foo";
}
You can use NSNotificationCenter for this.
Here are the apple documentation link.
First, nothing is "instantiated" in the .h file - that's just the public listing of what properties and methods are available to other classes. Think of the header file as a table of contents, but only for the things the class wants others to see.
Those properties don't exist in memory until the instance of the class itself is created, and then only if you set them to some initial value once they're needed.
How & where are you creating the 2nd view? Is it a storyboard segue or something? The 1st view doesn't seem to have any way of knowing the 2nd one exists, so it won't be able to see or access the label.
View1Class.m
#import View2Class.h
#implementation View1Class
- (IBAction)startPouring_ButtonClick:(id)sender {
//Instantiate the 2ndView when you need it.
// This gives View1 a reference to View2 and its public UILabel.
View2Class * my2ndView = [[View2Class alloc] init];
my2ndView.labelTimer.text = #"foo";
}
#end
As I said, it's still not clear how/where you're actually displaying the 2nd view though, so the snippet above is incomplete. You could use a modal w/a delegate, or this is where NSNotificationCenter is a helpful option - the 2nd view can sign up to get notifications & change accordingly. There are numerous tutorials about creating a 2nd/modal view and displaying it on a button click - you should probably look at those to clarify how the structure of such an app ought to work.
This answer should get you on the right track.
Other specific issues:
Why is the label nil? Because there isn't one...
In this IBAction, which seems to be in View 1:
- (IBAction)startPouring_ButtonClick:(id)sender
{
labelTimer.text = #"foo"; //this is looking for labelTimer in the clicked view.
}
... it is looking for its own labelTimer IBOutlet (in which case it should probably be self.labelTimer.text), and not that of the 2nd view. If the 1st view doesn't even have a UILabel IBOutlet, this is another problem.
If the views have different functions & different properties, they probably shouldn't be instances of the same custom class. If the 1st view doesn't have or need a UILabel, it shouldn't have one in its .h. If the 2nd view doesn't have or need a button it shouldn't have one in its .h. If the views serve different purposes, then make them different classes.
BTW,
Since you're using instance variables for your IBOutlets, you'd need to write your own getter & setter methods if you want to change their values. Did you? To make those values accessible to other classes, you'd need to make those methods public & put them in the .h. It's not good practice for an instance to set its instance variables directly w/o a getter/setter, and other objects definitely should not.
The preferred method is to use #properties for your IBOutlets instead of declaring them as instance variables. This will automatically create the getter & setter methods, backing store in memory, and as of XCode 4.4 it automatically adds #synthesize so you no longer need to do so. Declaring your IBOutlets as "weak" references prevents retain cycles & memory leak, where the view holds on to the outlets & the outlets hold on to the view & nothing ever goes away...
View1Class.h
#interface ViewController : UIViewController
#property (nonatomic, weak) IBOutlet UIButton *buttonSelectTimeInterval;
#end
View2Class.h
#interface ViewController : UIViewController
#property (nonatomic, weak) IBOutlet UILabel *labelTimer;
#end
I have a storyboard driven iOS application.
My objective is to add a small square view (defined as other XIB) to another UIView which is on main ViewController.
*// Please note that, I have assigned the Class names in Identity Inspector correctly*
On the main ViewController, I added a UIView (myHolderView) manually and added a UIView member in the ViewController interface.
//.h
#interface ViewController : UIViewController
#property(nonatomic, strong) IBOutlet UIView *myHolderView;
#end
And, I have connected this IBOutlet by "BlueString" :)
Now, I have added another XIB with single view - Called it MySquare.xib and added a label on it.
And, added a UIView class with a .h and .m files. And, "BlueString" connections are made.
#interface MySquare : UIView
#property(nonatomic, strong) IBOutlet UILabel *myLabel;
#end
Now, in the implementation:
#implementation ViewController
#synthesize myHolderView;
- (void)viewDidLoad
{
[super viewDidLoad];
MySquare *sq=[[MySqure alloc]init];
// Do any additional setup after loading the view, typically from a nib.
[self.myHolderView addSubview:sq];
}
When I run it in the simulator, it just shows the main view, but on it the square view is not coming.
I know that main view had loaded because... I changed the colour of it so that I could see.
Please help.
Probably you don't do in your MySquare.m file the view initialisation using your xib.
- (id) initWithFrame:(CGRect)frame {
NSLog(#"This is called if you add programatically this view");
self = [super initWithFrame:frame];
if (self) {
[self addSubview:[[[NSBundle mainBundle] loadNibNamed:#"MyIBView" owner:self options:nil] objectAtIndex:0]];
}
return self;
}
If you plan to add your xib using Interface Builder, you have to implement similarly the initWithCoder method:
-(id)initWithCoder:(NSCoder *)aDecoder {
if ((self = [super initWithCoder:aDecoder])) {
[self addSubview:[[[NSBundle mainBundle] loadNibNamed:#"MyIBView" owner:self options:nil] objectAtIndex:0]];
}
return self;
}
Plese go through in this checklist, it might help others too:
Checklist adding a custom view with Interface Builder layout (xib):
1) create a new xib (File->New->IOS/User Interface->View), name it (eg. MyIBView)
2) create a new class (File->New->IOS/Cocoa Touch->Objective-C class), make sure it is a Subclass of UIView! Name it MyIBView.
Now you have MyIBView.xib, MyIBView.h and MyIBView.m files added to your project
3) Edit your MyIBView.xib: select the File's owner and using the Identity inspector (3rd icon) select Class you created recently, MyIBView.
4) Select the top level View (if its a new xib, only this exists) and make sure using the Identity inspector that the Class is UIView (grey) and not overwritten
5) Now you can add IBOutlets and/or IBActions to your MyIBView.h file and you can do connections using Interface Builder in your MyIBView.xib
Using this view programatically in another view:
6a) Create the first method (initWithFrame) above in your MyIBView.m file if you would like to use this custom view programatically
7a) include your MyIBView.h file where you would like to add your custom view programatically
8a) now you can add your view:
MyIBView *myIBView = [[MyIBView alloc] initWithFrame:CGRectMake(0,0,100,100)];
Using this view in Interface Builder in another view:
6b) Create the second method (initWithCoder) above in your MyIBView.m file if you would like to use this custom view in Interface Builder
7b) In your another view drop an UIView to your view canvas. Adjust the size and modify the Class using the Identity inspector to MyIBView.
Please note that you cannot see design time your custom view, only after trying to run the code.
Hope it helps, and you can find your miss-configuration!
I want to create a self-contained UICollectionView subclass (acting as its own data source and delegate) so that I could load it in different viewControllers. Here's what I have so far:
CustomCollectionView.h
#interface CustomCollectionView : UICollectionView <UICollectionViewDataSource, UICollectionViewDelegate>
#property (weak, nonatomic) IBOutlet UICollectionView *collectionView;
#end
CustomCollectionView.m
#import "SSCalendarView.h"
#implementation SSCalendarView
#synthesize collectionView;
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil];
[self registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:#"Identifier"];
[self addSubview:collectionView];
}
return self;
}
// Below are UICollectionViewDataSource and UICollectionViewDelegate methods
#end
CustomCollectionView.xib
Contains only one view - UICollectionView. It's class is set to CustomCollectionView
File's Owner's class is also set to CustomCollectionView
File's Owner is UICollectionView's delegate and data source
I understand that I have quite a few things wrong here. But perhaps we could use this as a starting point.
My questions are:
How to implement this sub-class correctly? I want to load the view fully from xib
To begin with, and aside from potential MVC violation (this sub-class would do it all), can a UICollectionView be its own data source and delegate?
If above is possible, how do I correctly create an instance of this subclass to use in my view controllers?
There already exists an object which you can use for this purpose - UICollectionViewController. This can be subclassed and added to any view controller (as a child view controller) and already contains a collection view which it is the datasource and delegate for.
The problems with your current approach are:
As you point out, you're putting too much responsibility on one object by having a view be its own datasource and delegate
File's owner for a xib can't be an object from within the xib. When you load the xib, the object you send to the owner argument is the file's owner. I've no idea what you actually end up with using the code you currently have.
I'm not sure why you insist on using a xib anyway - what does this give you, except the headache of an extra file and the complexity of nib loading? Just create a collection view controller, you can specify the layout and register cells in the init and viewDidLoad methods.
First of all making view to act like view controller is violation of MVC, ask you've said so - so you shouldn't probably do it.
Theoretically it's possible to force view to act as delegate & data source but I wouldn't recommend it.
If you still want to do it - just assign delegate & data source to self:
self.dataSource = self;
self.delegate = self;
and adopt UICollectionViewDelegate and UICollectionViewDataSource protocols in .h file
I have added my custom UICollectionViewController as an object (A) to the interface builder, and given it the proper custom class.
this EOCollectionViewController is also an IBOutlet in my main Viewcontroller
I have added the UICollectionView to the main view of the application.
I have made all the links possible. UICollectionView has the object (A) as a delegate and a datasource.
The object (A) has the UICollectionView linked to the view property. (not the self.collectionView property, as this is not there)
It all works well. Collection view get's filled. The CollectionViewController resides in the property of my main view controller.
Only the self.collectionView is nil...when I try to reach it from within the UICollectionViewController.
My first guess is that "extra" UIControllers used in IB do not get initalized in the same way. I also tested, and viewDidLoad and init never get called for these extra objects.
Attached the header of EONoteController (and the IBOutlet added as a work-around)
#interface EONoteController : UICollectionViewController<UIGestureRecognizerDelegate>{
__weak IBOutlet UICollectionView *cvNotes;
}
#property (nonatomic) EDAMNotebook* notebook;
#property id <EODragHandler> draghandler;
#end
My first guess was to "fix" it, in the viewDidLoad, as follows
self.collectionView = self.view, but viewDidLoad never get's called.
What am i Missing?
How can I load nib from a UIView in iOS 4 ?
I don't have a UIViewController, since I'm adding this custom view to a parent view.
I want to initialize this custom view from a nib file. Which initializer should I use ? initWithFrame or initWithCoder ?
So far, I've only assigned the custom class in the interface builder. (See pic: http://cl.ly/7pmj). Is this necessary ? If so, it is still not working, why is it not enough ?
NB I've found this related question in StackOverflow. However, it doesn't explain how to initialize the view itself with the nib.
I need to customize drawRect method, for this view, so I can't just add a subview from a nib file. I need the view itself being initialized from the nib file.
thanks
Yes, it's a must to change the custom view class in your nib file.
Make sure two things:
1. Go to yourCustomView.nib file, select the view in object, then make sure the custom class is correct under the identity inspector.
2. In the youCustomView.m file, make sure you have this part:
- (id)initWithCoder:(NSCoder*)coder
{
if ((self = [super initWithCoder:coder]))
{
//add custom change after init from nib.
}
return self;
}
Use the method from the related question (NSBundle loadNibNamed) and make sure the owner in that call is an object that has an IBOutlet for the view in question.
Hook that iboutlet up to the view in interface builder (you will need to set the class of the owner in IB to the class of the owner in your load nib call).
The nib will load the view into that iboutlet variable.
In .h file:
IBOutlet UIView *myView;
in .m file:
[[NSBundle mainBundle] loadNibNamed:#"viewToLoad" owner:self options:nil];
[someOtherView addSubView:myView];
The rest is in IB.