I'm create a custom UIView with some sub views.
In the IB - i'v set my subview to be a CreateAlbumView which is a UIView, and created some subviews and outlet those.
When i'm inflating this view in code, and adding it to a super view the follow code is being called:
-(id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self)
[self setViews];
return self;
}
but the subviews are all nil there - making my setViews() function to become useless.
I've changed my code to set the code in the following function, but it doesn't seems right:
-(void)layoutSubviews {
[super layoutSubviews];
[self setViews];
}
Where does the subviews are actually initialized and where can i start using them ?
You should use
- (void)awakeFromNib
The nib-loading infrastructure sends an awakeFromNib message to each object recreated from a nib archive, but only after all the objects in the archive have been loaded and initialized. When an object receives an awakeFromNib message, it is guaranteed to have all its outlet and action connections already established.
I've changed my code to set the code in layoutSubviews, but it
doesn't seems right.
— well, have a method create the views (setViews is a bad name due to cocoa conventions) and layout them and add them in layoutSubviews, than layoutSubviews does exactly what it's name suggests.
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
[self createSubviews];
return self;
}
-(void)layoutSubviews
{
[super layoutSubviews];
for (UIView *v in mySubviews) {
v.frame = CGRectMake(...);
[self addSubview:v];
}
}
Related
I am trying to subclass UILabel. The first try involves that my custom UILabel simply sets the property adjustsFontSizeToFitWidth to YES. The problem is that I am new to iOS programming and unsure about where to put this code. I tried the code below but they are never called.
- (id)initWithFrame:(CGRect)frame
{
NSLog(#"init custom label");
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.adjustsFontSizeToFitWidth=YES;
}
return self;
}
- (id)init
{
NSLog(#"init custom label");
self = [super init];
if (self) {
// Initialization code here.
self.adjustsFontSizeToFitWidth=YES;
}
return self;
}
I got it to work by using:
lblCustom = [lblCustom init];
But is there someway I can get this call to be called automatically?
When a label is used in interface builder is then the coded use the NSCoder protocol:
- (id)initWithCoder:(NSCoder *)aDecoder
{
NSLog(#"init custom label");
self = [super initWithCoder:aDecoder];
if (self) {
// Initialization code here.
self.adjustsFontSizeToFitWidth=YES;
}
return self;
}
What I do, is create one method to setup my custom UI object and let all the init call this method.
I would personally stay away from initWithCoder: and instead use awakeFromNib instead. Here is why (Apple UIKit Documentation):
The nib-loading infrastructure sends an awakeFromNib message to each
object recreated from a nib archive, but only after all the objects in
the archive have been loaded and initialized. When an object receives
an awakeFromNib message, it is guaranteed to have all its outlet and
action connections already established.
I have a variety of UI subclasses in my program and here is the solution I came up in my BaseLabel class.
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self)
{
// We were not loaded from a NIB
[self labelDidLoad:NO];
}
return self;
}
- (void)awakeFromNib
{
[super awakeFromNib];
// We were loaded from a NIB
[self labelDidLoad:YES];
}
- (void)labelDidLoad:(BOOL)loadedFromNib
{
// Do some initialization here...
}
Now all of my subclasses simply override {type}didLoad:(BOOL)loadedFromNib.
For example buttonDidLoad:, textFieldDidLoad:, and tableViewDidLoad: (you get the idea).
Calling only init on an UIView is the same as calling initWithFrame: with a zero-rect. So you should override initWithFrame or initWithCoder if you are using nib-files.
In my application I've subclassed UIView and it has three ivars declared in .m file (2UIButton and an UITextField ). I use this object by drag the UIView from the objects library and convert them to my subclass. below is my implementation
#interface Prescriber()<UITextFieldDelegate>
{
UIButton *_addButton;
UIButton *_lessButton;
UITextField *_valueField;
}
#end
#implementation Prescriber
#synthesize value=_value,intValueofStrig=_intValueofStrig;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
NSLog(#"prescriber called");
return self;
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
-(void)layoutSubviews
{
if (!_addButton) {
_addButton=[[UIButton alloc]init];
[_addButton addTarget:self action:#selector(addbuttonClicked:) forControlEvents:UIControlEventTouchUpInside];
[_addButton setBackgroundImage:[UIImage imageNamed:#"add1.png"] forState:UIControlStateNormal];
_addButton.layer.cornerRadius=5;
}
if (!_lessButton) {
_lessButton=[[UIButton alloc]init];
[_lessButton addTarget:self action:#selector(lessButoonClicked:) forControlEvents:UIControlEventTouchUpInside];
[_lessButton setBackgroundImage:[UIImage imageNamed:#"Minus.png"] forState:UIControlStateNormal];
}
if (!_valueField) {
_valueField=[[UITextField alloc]init];
_valueField.delegate=self;
}
///positioning the addbutton on left corner and right corner and position and
textfield in the center
}
I've done the configuring code in the -(void)layoutSubviews method as my -initwithFrame not get called as they are already in the xib file.
what I want to know is ,Is it right to do our initialization in the -layoutSubViews as there is a chance for this method to get called another time. Or am I able to invoke the
-viewDidLoad like delgate method for my subclassed View too?
Edit:- It is possible by creating an IBOutlet but that way is a constraint for me.
Do it in a private setup method:
#interface MyViewClass ()
- (void)_setup;
#end
#implementation MyViewClass
- (void)_setup
{
_addButton=[[UIButton alloc]init];
[_addButton addTarget:self action:#selector(addbuttonClicked:) forControlEvents:UIControlEventTouchUpInside];
[_addButton setBackgroundImage:[UIImage imageNamed:#"add1.png"] forState:UIControlStateNormal];
_addButton.layer.cornerRadius=5;
_lessButton=[[UIButton alloc]init];
[_lessButton addTarget:self action:#selector(lessButoonClicked:) forControlEvents:UIControlEventTouchUpInside];
[_lessButton setBackgroundImage:[UIImage imageNamed:#"Minus.png"] forState:UIControlStateNormal];
_valueField=[[UITextField alloc]init];
_valueField.delegate=self;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self != nil)
{
[self _setup];
}
return self;
}
- (id)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self != nil)
{
[self _setup];
}
return self;
}
#end
For IB instances, you can setup you view and initialize its subviews in -awakeFromNib:
- (void)awakeFromNib
{
[super awakeFromNib];
// your initialization code here
}
Using this method, your instance is fully initialized and the IB connections have been established. It is also guaranteed to be called exactly once (per instance). If you also create instances of this type programmatically, then you should consider a shared initialization method which can be called in -awakeFromNib.
The problem with adding subviews in the initializer is that your view is in a partially constructed state, and ultimately there may be side effects or difficulty initializing your view/object graph in a well defined and deterministic manner; -awakeFromNib at least ensures all NIB objects have been created and the connections exist, although the order in which -awakeFromNib is called is not defined. If you need exact ordering, then you should really approach it programmatically.
I've done the configuring code in the -(void)layoutSubviews method as
my -initwithFrame not get called
UIView has two designated initializers. When the view is loaded from a nib, -initWithCoder: is called instead of -initWithFrame:. You can put initialization code in that method, or put it in a common initialization method that you call from both initializers.
Is it right to do our initialization in the -layoutSubViews as there
is a chance for this method to get called another time.
That is, indeed, the problem with using -layoutSubviews for initialization: it may be called more than once, and not always at the beginning of your view's lifetime. Better to use the initialization methods.
I'm developing an iOS app with latest SDK.
I have created a class that inherits from UIView and I have to do some initialization every time the class is instantiated.
I have to call a method called setUpVars: but I don't know where to send a message to that method:
- (id)initWithFrame:(CGRect)frame;
- (id)initWithCoder:(NSCoder*)aDecoder;
This class can be used with a custom xib, or added to a Storyboard, so I need to be sure that that method will be called on every case.
- (void)setUpVars
{
_preferenceKey = #"";
_preferenceStatus = NO;
_isDown = NO;
}
Where do I have to add [self setUpVars];?
Essentially you will be wanting to cover both cases
- (id)initWithFrame:(CGRect)frame;
{
self = [super initWithFrame:frame];
if (self) {
[self setUpVars];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder;
{
self = [super initWithCoder:aDecoder];
if (self) {
[self setUpVars];
}
return self;
}
I think that you need to send this message from each method, also do not forget about awakeFromNib method.
You can create BOOL variable, something like isAlreadySetup and set it to YES in setUpVars method.
Docs Says
awakeFromNib
Prepares the receiver for service after it has been loaded from an
Interface Builder archive, or nib file.
- (void)awakeFromNib
{
[self setUpVars];
}
If you use Interface Builder to design your interface, initWithFrame: is not called when your view objects are subsequently loaded from the nib file. Instead initWithCoder gets called. So you can initialize your variables in both methods if you prefer a generic way. Works in both case
I tend to think you should call this method from the -(void)viewDidLoad method of the controller in charge
I am designing a Custom UIView for my app.
The UIView will comprise of below components:
UISearchbar
UITableView
My initialiser is below:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
_searchBar = [[UISearchBar alloc]initWithFrame:CGRectZero];
_tableView = [[UITableView alloc]initWithFrame:CGRectZero];
_tableView.dataSource = self;
[super addSubView:_searchBar];
[super addSubView:_tableView];
// Initialization code
}
return self;
}
I am planning to set the frame of the _searchBar and _tableView in layoutsubviews method.
But I think think the way I have added the _tableView to super is wrong. Because the moment the _tableView is added to subview the data source methods of the _tableView will be triggered. And this happens even before the creation of the custom class itself.
Is this a correct design?
Can I add just _tableView alone in layoutSubviews as in below manner?
-(void)layoutSubViews{
//Adjust frame
[_tableView removeFromSuperView];
[self addSubView:_tableView];
}
You shouldn't be assigning the UITableViewDataSource in the view. It should be assigned in the ViewController.
You're right. There is no restriction on it. But your question is about design. Imagine something like this:
#implementation CustomViewController
- (void)loadView {
customView = [[CustomView alloc] initWithFrame:CGRectZero];
customView.tableView.dataSource = self;
customView.tableView.delegate = self;
}
With a ViewController, you can control when you initialize your custom view and control when its tableView loads the data. While you can certainly put all of this code into your customView, you will be running into problems much worse than the one you are asking about now.
You should definitely add it in init, because layout sub-views will get called each time you view will resize and will need to re-layout its sub-views.
Layout subviews method is strictly use as a callback telling you that your view will layout, and is used as an override point for any additional layout you wish to make.
Also, as an additional note, it's not good design adding the view using super.
In the ViewController's interface, I have
#property int count;
and in the implementation, I have
#synthesize count;
-(id) init {
self = [super init];
if (self) {
self.count = 100;
}
return self;
}
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"%i", self.count++);
}
but for some reason, the first time self.count got printed, it is 0 but not 100?
One of various -init methods will be called on your UIViewController, depending on whether it came out of a .xib, storyboard, or is alloc'd manually somewhere else in your code.
A better place to put this kind of initialization is in -viewDidLoad, something like this
- (void)viewDidLoad {
[super viewDidLoad];
self.count = 100;
}
Put a NSLog or debugging breakpoint in your init method and I suspect you'll find it isn't called. If you look at UIViewController, you'll see other initialization methods (e.g. if you're using a NIB, it would invoke initWithNibName:bundle:). If it's via a storyboard, it can differ. See the discussion of initialization in Apple's View Controller Programming Guide for iOS.
A better place for general view controller configuration is viewDidLoad.
Change it to:
- (id)initWithCoder:(NSCoder *)aDecoder{
self = [super initWithCoder:aDecoder];
if (self) {
self.count = 100;
}
return self;
}
The view is actually getting created by the XIB, which is 'decoding' it or unboxing it. When this happens, the XIB calls initWithCoder: