What is the viewDidLoad for UIView?
I have a UIView with xib. I would like to hide one of it's subviews when it is loaded.
I tried to use this.
- (id)initWithCoder:(NSCoder *)aDecoder{
....
_theView.hidden = YES;
}
But the subview _theView is nil at this point.
This answer didn't help me, becouse at moment of creating the UIViewController, the UIView is not created yet. It is created programaticly, later on.
Try
-awakeFromNib method
Or in xib set the view property hidden for your subview
AwakeFromNib is called only if the view loaded from nib file.
layoutSubviews is called for all views, you can add bool _loaded = yes; in the layoutSubviews function and know if the view loaded.
The accepted answer is misleading.
awakeFromNib will always be called, not just if a nib is used.
From the apple docs:
awakeFromNib:
Prepares the receiver for service after it has been loaded from an
Interface Builder archive, or nib file.
Link
In the next example I've used only a storyBoard
You can test this very easily.
This is our ViewController:
ViewController.m:
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"viewDidLoad");
}
-(void)awakeFromNib
{
NSLog(#"awakeFromNib in view controller");
}
#end
RedView.m:
#import "RedView.h"
#implementation RedView
-(void)awakeFromNib
{
NSLog(#"awakeFromNib inside RedView");
self.green.hidden = YES;
}
#end
Order of print:
awakeFromNib in view controller
awakeFromNib inside RedView
viewDidLoad
And of course the green view will be hidden.
Edit:
awakeFromNib won't be called if you use only code to create your view but you can call it yourself or better yet, create your own method.
Example without a StoryBoard (only code):
RedView.m:
#import "RedView.h"
#implementation RedView
-(void)loadRedView
{
NSLog(#"loadRedView");
self.green = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 100, 100)];
self.green.backgroundColor = [UIColor greenColor];
[self addSubview:self.green];
self.green.hidden = YES;
}
#end
ViewController.m:
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.red = [[RedView alloc]initWithFrame:CGRectMake(0, 0, 200, 200)];
self.red.backgroundColor = [UIColor redColor];
[self.view addSubview:self.red];
[self.red loadRedView];
}
#end
There is no such method in general. The question is, where is your _theView coming from.
If your view, including its subview, is loaded from the same nib/xib/storyboard then you can use awakeFromNib which will be called after the complete object hierarchy has been loaded from the archive, so your _theView should be set as well.
If your view is created programmatically but does not create the subview for _theView itself, that means there has to be a place in your code where you add that subview. In that case you have two options
Either hide _theView from the caller after you added it
Or declare a prepareForDisplay method (or similar) on your view class and call that after your view has been created and _theView has been assigned. In that prepareForDisplay (or whatever name you choose) method you can do whatever you like, e.g. hide _theView.
I would not recommend to abuse layoutSubviews for this as it is meant for a different purpose and will be called several times during the lifetime of a view, not just once as you want it to be. Yes you can save whether it was called before, but I would consider that a hack as well. Better create your own method to initialize the view in a way you want after you set it up correctly and call that.
layoutSubviews will be call for all the views you can set you view as hidden there instead of awakeFromNib.
If you are using xib then you can set the default hidden property.
private var layoutSubviewsCounter = 0
override func layoutSubviews() {
super.layoutSubviews()
if layoutSubviewsCounter == 0 {
layoutSubviewsCounter += 1
viewDidLoad()
}
}
func viewDidLoad() {
// your code here
}
Related
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
I made a custom view.
I set two starting methods,
1. initWithFrame (for code initialization)
2. initWithCoder (for storyboard initialization)
In my custom class
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// [self setUp] method contain the code to run the delegate.
[self setUp];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
// [self setUp] method contain the code to run the delegate.
[self setUp];
self.backgroundColor = [UIColor greenColor];
}
return self;
}
Then, I add a View to storyboard and change the class at identity inspector.
I connect the view to the viewcontroller property.
I set the delegate and data source
In my view controller
#interface ViewController () <UIControlViewDataSource, UIControlViewDelegate>
#property (weak, nonatomic) IBOutlet UIControlView *controlView;
#end
- (void)viewDidLoad
{
self.controlView.delegate = self;
self.controlView.dataSource = self;
}
The initWithCoder is running before viewDidLoad,
Because of it the delegate will never run because the initWithCoder in my custom class is running before I set the delegate in viewDidLoad in my view controller.
And the delegate property will have NULL value.
How could I set the delegate?
If you are instantiating these objects from a xib or storyboard (which it looks like you are based on the IBOutlet, you should set your delegate in -(void)awakeFromNib
At this point all the outlets will have been set.
You shouldn't have to depend on the order in which objects get instantiated. You should let them all get instantiated, the outlets set and then do whatever you need to do with your delegate
Your problem is not getting the delegate set, but rather that your object requires a delegate for construction. A delegate should be an optional property.
Whatever operation your UIView requires its delegate for is likely something that can be pushed out of the actual constructor. If you want to have the delegate set automatically during the nib loading process, you could declare it as an IBOutlet property in your view. It will still be set after it is initialized though.
Without getting more detail about why you need to call the delegate, it's impossible to say where you could defer that logic to.
Ive created a UIView in code using the addSubview:view method. If I want this to be a custom class rather than the standard UIView, does all this customisation have to take place in the view controllers viewDidLoad method? Seems like there will be alot of code in the viewDidLoad if this is the case! This is the first time ive attempted to create a view in code - the other times ive done it in IB where Ive created a custom class and changed the class of the view in the identity inspector.
Create a new UIView subclass
// MyView.h
#interface MyView : UIView
// public properties, method declarations here
#end
// MyView.m
#implementation MyView
// implementation here, including anything you want to customize this view's
// look or behavior
#end
Then instantiate it in your view controller by importing and referring to the custom class
// ViewController.m
#import "MyView.h"
- (void)viewDidLoad {
[super viewDidLoad];
MyView *myView = [[MyView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:myView];
}
I have a UIViewController with its UIView which contains a UIButton. I want to trigger a method in UIViewController on button click event.
Keeping reference of UIViewController doesn't seem to be a good idea like the following link says:
Get to UIViewController from UIView?
So I want to achive this using a delegate. Any hint on how to achieve this?
You can do something like this
CustomView.h
#import <UIKit/UIKit.h>
#protocol CustomViewDelegate <NSObject>
-(void)didButtonPressed;
#end
#interface CustomView : UIView
#property (assign) id<CustomViewDelegate> delegate;
#end
CustomView.m
#import "CustomView.h"
#implementation CustomView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.backgroundColor = [UIColor whiteColor];
//[self addSubview:titleLbl];
UIButton *button= [UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame = CGRectMake(100, 100, 100, 50);
[button addTarget:self.delegate action:#selector(didButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[button setTitle:#"pressMe" forState:UIControlStateNormal];
[self addSubview:button];
}
return self;
}
in your ViewController.m
-(void)loadView
{
[super loadView];
CustomView *view = [[CustomView alloc]initWithFrame:self.view.bounds];
view.delegate = self;
[self.view addSubview:view];
}
This is what the responder chain was built for. When you add a target to your button, just supply nil for the target:
[mySpecialButton addTarget:nil
action:#selector(mySpecialButtonTapped:)
forControlEvents:UIControlEventTouchUpInside];
The nil target basically means "send mySpecialButtonTapped: to any object in the responder chain that can handle it".
Now you can handle this selector anywhere in the responder chain, which includes the button itself, its containing view, its containing view controller, the UIApplication, and finally your AppDelegate. Just place this method in the object most appropriate for your needs:
- (void)mySpecialButtonTapped:(id)sender {
NSLog("My special button was tapped!");
}
You don't need delegates or callback blocks (as in the accepted answer) if you just want to bubble a message up.
I guess that you expected something more fundamental then just pass some button action to controller.
I always follow MVC pattern in case of model/view/controller collaboration. It resolve your issue and many other. And I want to share my experience.
Separate controller from view and model: don't put all of the "business logic" into view-related classes; this makes the code very unusable. Make controller classes to host this code, but ensure that the controller classes don't make too many assumptions about the presentation.
Define callback APIs with #protocol, using #optional if not all the methods are required.
For view define protocol like <view class name>Protocol (example NewsViewProtocol). For controller define delegate like <view class name>Delegate (example NewsViewDelegate) and dataSource like <view class name>DataSource (example NewsViewDataSource). Keep all this #protocols in one separate file named <view class name>Protocol.h (example NewsViewProtocol.h)
Short example:
Contents of NewsView.h
//
// NewsView.h
#interface NewsView : UIView <NewsViewProtocol> {
#protected
NSObject* delegate_;
NSObject* dataSource_;
}
#end
Contents of NewsController.h and .m
//
// NewsController.h
#interface NewsController : UIViewController <NewsViewDataSource, NewsViewDelegate> {
}
#property (nonatomic, weak) UIView<NewsViewProtocol>* customView;
#end
#implementation NewsController
- (void)viewDidLoad {
[super viewDidLoad];
self.customView = (UIView<NewsViewProtocol>*)self.view;
[self.customView setDelegate:self];
[self.customView setDataSource:self];
}
#end
Contents of NewsViewProtocol.h
//
// NewsViewProtocol.h
#protocol NewsViewProtocol;
#protocol NewsViewDelegate<NSObject>
#optional
- (void)someAction;
- (void)newsView:(UIView<NewsViewProtocol>*)newsView didSelectItemAtIndexPath:(NSIndexPath *)indexPath;
#end
#protocol NewsViewDataSource<NSObject>
#required
- (id)newsView:(UIView<NewsViewProtocol>*)newsView itemAtIndexPath:(NSIndexPath *)indexPath;
- (NSInteger)numberOfItemsInNewsView:(UIView<NewsViewProtocol>*)newsView section:(NSInteger)section;
- (BOOL)newsView:(UIView<NewsViewProtocol>*)newsView shouldDisplaySection:(NSInteger)section;
#end
#protocol NewsViewProtocol<NSObject>
#required
//Never retain delegate instance into implementation of this method
- (void)setDelegate:(NSObject<NewsViewDelegate>*)delegate;
//Never retain delegate instance into implementation of this method
- (void)setDataSource:(NSObject<NewsViewDataSource>*)dataSource;
- (void)reload;
#end
You may consider that it is redundant. In simple view controller, YES. But if you develop very complex screen with huge amount of data then it gives you some advantages as:
Helps you to separate responsibility between view and controller.
Keeps your code clear.
Makes you code more reusable.
Life is easy in xCode.
At the very beginning be sure that your xib View (the one with your button inside it) is associated to the right ViewController class. Which can be the default ViewController class that comes with a new project or your custom one.
After this, here comes the magic trick! Separate your view into 2 panel. The goal is to see your xib and your viewController code (the .m file). Now press the control key of your keyboard and drag your UIButton to the code. Select IBAction. It will generate something you can call a "listener" in other language. Go to the core code of your View Controller and complete the method!
Easy as that! Have fun :)
You don't really need delegates for this - it is how UIButtons are intended to be used. Just control-click and drag from your button to the .m file for your UIViewController. This will create a new method. From there, you can either make a call to the method you wrote or just copy-paste what you have into the new method.
You can try this:
[yourButton addTarget:self action:#selector(yourButtonAction:) forControlEvents:UIControlEventTouchUpInside];
And in your selector specify the action
- (IBAction)yourButtonAction:(id)sender {
//Action to perform
}
To add a button programmatically, in myViewController.m
UIView *yourView = [[UIView alloc] init];
UIButton *yourButton = [[UIButton alloc] initWithFrame:CGRectMake(0,0,100,21)];
[yourButton addTarget:self action:#selector(yourMethod) forControlEvents:UIControlEventTouchDown];
[yourView addSubview:yourButton];
More info here.
I have create a UIVeiw class and a .xib. Within this .xib view I have its set to freeform with the dimensions of 400x200 and I have assigned it to my custom class with the same name:
Storyboard: blogView
Class Files: blogView.h & blogView.m
Within in the .xib i have added a label and a text field and linked them up to variable within the .h files etc (See code below).
blogCont.h
#import <UIKit/UIKit.h>
#interface blogCont : UIView
#property (strong, nonatomic) IBOutlet UILabel *lbBlogDate;
#property (strong, nonatomic) IBOutlet UITextView *txtBlogTitle;
#end
blogCont.m
#import "newsStoryView.h"
#implementation blogCont
#synthesize lbBlogDate;
#synthesize txtBlogTitle;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code place a log to see if it loads
NSLog(#"View Loaded");
}
return self;
}
#end
Now with in my main viewController.m file i have added the following code to init this view class, and I have added a background colour to see if this loads in.
viewController.m
UIView *blogCont = [[blogView alloc] init];
blogCont.backgroundColor = [UIColor purpleColor];
[subview addSubview:blogCont];
Now when I run this it all works well but as I do not see the purple background it looks as if the view does not load, but within the log I do see the NSLog message I have within this view NSLog(#"View Loaded"); so it seems it initiating this, but I cannot for the life of me get this to display?
Now if I change the code slightly to my main View Controller.m fiel to:
CGRect blogFrame;
blogFrame.origin.x = 20;
blogFrame.origin.y = 20;
blogFrame.size = CGRectMake(400,200);;
newsStoryView *blogCont = [[blogView alloc] blogFrame];
blogCont.backgroundColor = [UIColor purpleColor];
[subview addSubview:blogCont];
Then I get my view display a nice purple box, so this shows up when I set a frame size and the init the view with it 'blogFrame', bu tI thought that all this would be set within the .xib settings so no need to do this?
SO how can I create this external view class and assign it into another view and then manipulate its data, as accessing the label in the .xib using blogCont.lbBlogDate.text does not seem to work that is it probably does but as I cannot view it i cannot confirm it.
What am i doing wrong?
Thanks
Seems I nearly answered my own question then did:
I was not setting the size within my separate class view I was asking for a size when init it:
- (id)initWithFrame:(CGRect)frame
this is asking for a size
so I could do the following to the above:
- (id)init
{
self = [super initWithFrame:CGRectMake(0, 0, 478, 220)];
.... rest of code
Setting the size within the view load.
But I could also set it when I init it in my main view controller as below:
newsStoryView *blogCont = [[blogView alloc] initWithFrame:CGRectMake(0, 0, 400, 200)];
This is better as I can control the position of each one. Hope this helps anyone