Im curious, where is the best option to allocate/init, set attributes of views (uibutton, uilabel, uitextfield, initializing variables, etc).
This is in regards to developing an app strictly programatically. I see some cases where these views have been allocated/init in the class -init method, but then other times i see other views set in the -loadview method.
Can anyone provide some clarity about this? And maybe some abstract examples of when the best time to do it for either method would be.
Thanks
The -init* family of functions would be a good place to initialize simple properties, e.g. strings, numbers, and the like. The initializer runs just after the memory for the object is allocated, and if you have something that can be initialized there then you should do it there.
For UIViewController instances, you probably have to wait until the nib has been loaded before you can initialize everything else. If you've got images that need to be placed inside subviews, or fonts that need configuring, or whatever, then you need to have the nib loaded first. -viewDidLoad is the best place for that stuff.
For UIView instances (or subclasses like UITableViewCell), you need to wait for the nib to be loaded too. You can use -awakeFromNib in that case.
Here's a quick comment on this:
-SubClass a UIView, smash all your UI elements into that view, well as many as you can at least. Import this subclassed view's header into your view controller's implementation file
-In your view controller, typecast your view controller's view like so:
-(HHYSignUpViewFirstPhase*)contentView
{
return (id)[self view];
}
-Invoke the loadView method
-(void)loadView
{
[self setView:[HHYSignUpViewFirstPhase new]];
}
-In your viewdidLoad, you can now set handlers to buttons and such from your subclassed UIView by calling to "[self contentView]" like so:
-(void)viewDidLoad
{
[super viewDidLoad];
[self setTitles:#"Sign Up"];
[[[self contentView] nameField] setDelegate:self];
[[[self contentView] emailField] setDelegate:self];
[[[self contentView] passwordField] setDelegate:self];
[[[self contentView] signupButton] addTarget:self action:#selector(signupPressed) forControlEvents:UIControlEventTouchUpInside];
}
Now you have it all set up, you just need to add methods to handle events from the button, for example in the view did load from your subview that you subclassed:
-(void)signupPressed
{
///do work
}
UIVIew subclass:
HHYSignUpViewFirstPhase.h
#interface HHYSignUpViewFirstPhase : UIView
#property (nonatomic) UIButton * signupButton;
#property (nonatomic) UITextField * emailField;
#property (nonatomic) UITextField * nameField;
#property (nonatomic) UITextField * passwordField;
#end
HHYSignUpViewFirstPhase.m
#import "HHYSignUpViewFirstPhase.h"
#implementation HHYSignUpViewFirstPhase
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self == nil)
return nil;
//do work, set up buttons, constraints, etc, etc.
return self;
}
#end
Essentially, what I'm saying here is that in the subclassed UIView you can initialize the UIView and set up all its constraints and EVERYTHING, frames included and then in the load view method of your UIViewController, you then call to this view and typcast the view of the UIViewController. So, sometimes you do the set up in the init, sometimes you do it in the load view, it depends on what you are trying to do, but this is how you set this up in a pure programmatic fashion with separation of duties, encapsulation, and all tied together in an MVC framework -- all work is separated into classes, and all controllers control a single class.
http://matthewmorey.com/creating-uiviews-programmatically-with-auto-layout/
and this
https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/ViewLoadingandUnloading/ViewLoadingandUnloading.html#//apple_ref/doc/uid/TP40007457-CH10-SW36
Related
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.
I have these two properties defined in my appDelegate:
#property (nonatomic, strong) UIView * mainView;
#property (nonatomic, strong) MyCustomClass * customObj
What is the best way of adding subviews to mainView from code inside customObj?
I am not providing any sample code because (a) my code is terrible and (b) I just want to understand the best approach of doing this, so I can learn from this in the future, rather than having one solution to one specific problem.
Many thanks.
well, what about creating a method in MyCustomClass like this:
-(void)addSubViewToView:(UIView *)view
{
[view addSubview:otherView];
}
And then call it like this:
[customObj addSubViewToView:mainView];
It depends on what kind of class MyCustomClass is. Is it responsible for building mainView's view hierarchy? Then I'd inject a reference of mainView to customObj, like this:
customObj = [[MyCustomClass alloc] initWithView:mainView];
In this scenario, customObj would be some kind of builder object, that creates the view hierarchy inside mainView. Then I'd use the addSubView: selector inside MyCustomClass:
-(id)initWithView:(UIView*)view{
if(self = [super init]){
[view addSubView: ...];
[view addSubView: ...];
[view addSubView: ...];
}
}
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 a class that extends UIViewController called HorizontalModal, which, among other things, I want to automatically generate a back button on each view that extends it. If I put the code to draw the back button in the viewDidLoad method for HorizontalModal, nothing shows up. But when I put that same code in the viewDidLoad method for the class that inherits from HorizontalModal, the button shows up fine.
E.g:
self.backButton = [[BackButton alloc] initWithFrame:CGRectMake(10, 10, 50, 50)];
[self.view addSubview:self.backButton];
Seems to me that I'm missing something about class extension and inheritance in objective C. Clearly self.view in HorizontalModal and self.view in the child class refer to two different things. So is there a way to keep that method in the HorizontalModal class, but be able to add the back button to the child class view? IE, can I access a sub-class property from a super class? Or is there a different way of doing this (maybe with categories)?
Thanks
If I put the code to draw the back button in the viewDidLoad method
for HorizontalModal, nothing shows up. But when I put that same code
in the viewDidLoad method for the class that inherits from
HorizontalModal, the button shows up fine.
It sounds like your subclass's -viewDidLoad isn't calling it's superclass's -viewDidLoad, like:
- (void)viewDidLoad
{
[super viewDidLoad];
// subclass-specific view setup stuff here
}
That allows the superclass's version of -viewDidLoad to do its thing first, followed by whatever the subclass needs to do.
can I access a sub-class property from a super class? Or is there a
different way of doing this (maybe with categories)?
If the superclass needs some method that every subclass is expected to provide, then you should put that method in the superclass and let subclasses override it. Normally, the subclass's implementation of the method calls the superclass's version, but if that's required then you should document that fact.
The same is true for properties: if your superclass needs to access some property of the subclass (e.g. backButton), the superclass should define that property itself. The subclass is then free to change the value of the property or even provide different property accessors, and code in the superclass that uses the property will just work. In general, the superclass shouldn't have to know anything about its subclasses.
So, to be more concrete, let's say you have MyModal, a subclass of HorizontalModal:
#interface HorizontalModal : UIViewController
#property(strong) UIView *backButton;
#end
#implementation HorizontalModal
- (void)viewDidLoad
{
self.backButton = [[BackButton alloc] initWithFrame:CGRectMake(10, 10, 50, 50)];
[self.view addSubview:self.backButton];
}
#end
#interface MyModal : HorizontalModal
//...
#end
#implementation MyModal
- (void)viewDidLoad
{
[super viewDidLoad];
// more code specific to MyModal's view hierarchy
}
#end
Now the -viewDidLoad method of HorizontalModal will be able to access the backButton property, even if MyModal has changed the value of the property or overridden the accessors. Remember that self points to the exact same object in both versions of -viewDidLoad, so both versions have access to the same information.
I have a UIView called baseViewand in that I have initWithFramewhere I add some other views and do some custom stuff. The same view also has a NIB file.
Now I have a UIViewController class named AppController in which I want to add the baseView view to the view of the AppController view so I am doing this:
self.view = baseView; but the problem is that the NIB file does not get loaded. How do I make sure the customized stuff AND the NIB file get´s loaded/run?
You have many options, depending on how your "baseView" class is meant to be used and integrated in to your application. It's not clear just how you intend to use this class -- as the view in a UIViewController subclass, or as a reusable modular component mean to be instantiated multiple times throughout your application, for use in many different view controllers.
If your view is meant to be the only view in a UIViewController subclass, then Phonitive is correct -- bundle it together with the UIViewController subclass .xib file and use the UIViewController's viewDidLoad to do final initialization.
But if you want your View class to be a subcomponent reused multiple times in different view controllers, integrated either via code or via inclusion in a .xib file for another controller, then you need to implement both the initWithFrame: init method, and awakeFromNib, to handle both cases. If your internal initialization always includes some objects from .xib, then in your initWithFrame you'll need to load your .xib manually in order to support "customer" classes that want to create your widget via code. And likewise, if a .xib file contains your object then you'll need to make sure you call any code-required finalization from awakeFromNib.
Here's an example of how to create a UIView subclass component with the UI design in a nib.
MyView.h:
#interface MyView : UIView
{
UIView *view;
UILabel *l;
}
#property (nonatomic, retain) IBOutlet UIView *view;
#property (nonatomic, retain) IBOutlet UILabel *l;
MyView.m:
#import "MyView.h"
#implementation MyView
#synthesize l, view;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
// Initialization code.
//
[[NSBundle mainBundle] loadNibNamed:#"MyView" owner:self options:nil];
[self addSubview:self.view];
}
return self;
}
- (void) awakeFromNib
{
[super awakeFromNib];
// commenters report the next line causes infinite recursion, so removing it
// [[NSBundle mainBundle] loadNibNamed:#"MyView" owner:self options:nil];
[self addSubview:self.view];
}
- (void) dealloc
{
[l release];
[view release];
[super dealloc];
}
Here's what the nib file looks like (except that file's owner needs to be changed to MyView class).
be sure to hook up both the view and label outlets to File's Owner. That's it! A template for creating re-usable UIView widgets.
The really neat thing about this structure is that you can place instances of your MyView object in other nib files, just place a UIView at the location/size you want, then change the class in the identity inspector (CMD-4) to MyView, and boom, you've got an instance of your widget in whatever views you want! Just like UIKit objects you can implement delegate protocols so that objects using your widget can be notified of interesting events, and can provide data to display in the widget to customize it.
I found this post after having a problem trying to do this in my app. I was trying to instantiate the view from a NIB in the ViewDidLoad method, but the controls acted as if they were disabled. I struggled with this trying to directly set the userInteractionEnabled property and programmatically set the touch event selector for a button in this view. Nothing worked. I stumbled upon another post and discovered that viewDidLoad was probably too soon to be loading this NIB. I moved the load to the ViewWillAppear method and everything worked. Hope this helps someone else struggling with this. The main response was great and works well for me now that I have it being called from the proper place.
if you want to use a NIB, it's better for your UIView to be linked with a UIViewController, in this case you can use
UIViewController *vc=[[UIViewController alloc] initWithNibName:#"YourNIBWihtOUTEXTENSION" bundle:nil]
[self.view addSubView:vc.view ];
becareful of memory leaks, you have to release vc
If you have a custom UIView with a xib file.
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
id mainView;
if (self)
{
NSArray *subviewArray = [[NSBundle mainBundle] loadNibNamed:#"HomeAllAdsView" owner:self options:nil];
mainView = [subviewArray objectAtIndex:0];
}
return mainView;
}
- (void) awakeFromNib
{
[super awakeFromNib];
}
This post helped me Building Reusable Views With Interface Builder and Auto Layout. The trick had to do with setting the IBOutlets to the FileOwner and then adding the content view to itself after loading the nib