I had this question when/where to create and initialize views that are created programatically, so I hope some discussions here will shed more light on this topic for me.
This slide:
says: "not to initialize something based on the geometry of the view in viewDidLoad" and suggests viewDidAppear.
Imagine my view controller has view. I want to add 10 dynamic UIButtons to it.
Shall I put the code like below to the viewDidAppear?
-(void) viewDidAppear:(BOOL)animated
{
...
UIButton *button1 = [[UIButton alloc] initWithFrame: rect1];
[self.view addSubview: button1];
UIButton *button2 = [[UIButton alloc] initWithFrame: rect2];
[self.view addSubview: button2];
...
}
But this creates the buttons each time the view is shown. Is it what we want?
On the other hand if I put the code in viewDidLoad slide suggest not to initialize geometry of these views there.
Or shall we create buttons in viewDidLoad and set their frames in viewDidAppear?
What approach do you usually take?
But this creates the buttons each time the view is shown. It's true.
So the best thing you can do is to add a boolean (lets name it isLaunched). You set it to FALSE in the method -(void)viewDidLoad
Then add a if condition in your -(void)viewDidAppear where you perform creation of buttons (or other stuff) and set the boolean to true at the end.
You should have something like that :
-(void)viewDidLoad
{
//some settings
isLaunched = FALSE;
}
-(void)viewDidAppear:(BOOL)animated
{
if(!isLaunched)
{
//creating and adding buttons
isLaunched = TRUE;
}
}
zbMax (and now Amar) offered good solutions to implement the view creations in viewDidAppear: I will provide the rational for doing this (over viewDidLoad).
It is pretty simple actually. In viewDidLoad none of the views are actually setup yet, so any attempt to set/create frames or bounds will be extremely inconsistent. Struts and springs (or autolayout) will take effect after this method which will create additional changes to your views. viewDidAppear: is the correct place to do this because you can now rely on existing views and setting frames.
Reason for not playing with the geometry in viewDidLoad is because view is still in the memory and not on the window. Once the view is put on the window, then you can specify geometry. That happens when viewDidAppear is called for your controller.
As recommended, you should do all the initialisation in viewDidLoad as this is one time task and need not be repeated. Hold references to the added subviews and give them appropriate frame in viewDidAppear.
When you are dealing with custom UIView and its subviews, layoutSubviews is the method you need to override in the custom view in order to rearrange the geometry of its subviews.
Hope that helps!
Related
I have a view controller which I created with IB, It has a CollectionView that will load some data, during loading process I want to show an activity indicator. For that I set the center property on activityIndicator to be equal to the center property of collectionView.
the problem is that I don't know on which method of viewController should I do this. I tried it first inside viewDidLoad method but inside this method the frame of collectionView is equal to what it was inside IB and activityIndicator can't be set properly.
So my questions are :
I want to know where should I do this kind of positioning of UIView objects
When does ios sdk give the views the sizes they should have in respect of the device the app is running on not the IB.
What happens inside of viewController methods like viewDidLoad and viewWillLayoutSubviews when we call those methods on super object of viewController like [super viewDidLoad]
All drawing-related operations should be performed on your application’s main thread then you could create it in the viewDidLoad or any other delegate function.
Threading Considerations
Manipulations to your application’s user interface must occur on the main thread. Thus, you should always call the methods of the UIView class from code running in the main thread of your application. The only time this may not be strictly necessary is when creating the view object itself but all other manipulations should occur on the main thread.
dispatch_async(dispatch_get_main_queue(),{
// Call function here.
})
For Reference [UIView] :
(https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/index.html)
1. I want to know where should I do this kind of positioning of UIView objects
If you're using interface builder, you should be positioning them in interface builder using autolayout, otherwise pretty much anywhere on the main thread is fine. Sometimes you may need to call [self.view setNeedsLayout] / [self.view setNeedsDisplay] depending on what time of change you've made, but ultimately for normal CGRectMake calls you're fine.
2. When does ios sdk give the views the sizes they should have in respect of the device the app is running on not the IB.
UIView has a method called layoutSubviews
Otherwise for VC look at viewWillLayoutSubviews and viewDidLayoutSubviews called either side of that
3. What happens inside of viewController methods like viewDidLoad and viewWillLayoutSubviews when we call those methods on super object of viewController like [super viewDidLoad]
In general for these methods you should call super at the top of the override to get whatever is happening underneath out of the way. Then proceed to work...
-(void)viewDidLoad
{
[super viewDidLoad];
UIView *myView = [[UIView alloc]initWithFrame:CGRectMake(0.0f, 0.0f, self.view.bounds.size.width, 100.0f)];
myView.backgroundColor = [UIColor redColor];
myView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
[self.view addSubview:myView];
}
The above will work completely fine. No need for viewDidAppear. Look into auto layout and auto resizing. Personally I think working in code and not interface builder is better... but that's a personal choice.
You shoud use MBProgressHud the great library to show activity indicator. Just downlod this from github. drag and drop MBProgressHud.h and MBProgressHud.m in your project. Import .h file in your class.
then call , [MBProgressHUD showHUDAddedTo:self.view animated:YES];
and call when want to dissmiss or hide,
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
// Do something...
//using main thread to hide immediately
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
});
It will automatically maintain center and other kind of stuff. That's it. Hope this will help. :)
I want to override the loadView method of my custom view controller in order to customise the view that is being shown. For that custom view I want to apply the background color that was set in the Storyboard editor.
If I for example implement loadView like this:
-(void)loadView{
self.view = [[MyCustomView alloc] init];
//Do some initialisation work for the custom view
//...
}
Then the resulting view has a black background. However, if I only call the default implementation:
-(void)loadView{
[super loadView];
}
Then the orange background color (see screenshot) is applied to the view. How can I get the color that was set in Storyboard editor? I would like to do something like this (pseudo code):
-(void)loadView{
self.view = [[MyCustomView alloc] init];
self.view.backgroundColor = [self colorThatWasSelectedInStoryboardEditor];
//Do some initialisation work for the custom view
//...
}
How does apple achieve that in the default implementation of loadView?
Since you apparently really want to use "loadView" here instead of "viewDidLoad", AND Apple's documentation for loadView says NOT to call "super", you will need to "hide" your custom color somewhere else. E.G. some other view (e.g. a hidden one?) in your ViewController, where it's connected to an IBOutlet and where you can extract it via the view's ".backgroundColor" property.
Or you will need to set your ".backgroundColor" property via good ol' UIColor methods like "colorWithRed:green:blue:alpha:".
All your views from the Storyboard are laid out before viewWillAppear:, which means, in viewDidLoad you don't have them yet. Try moving your code to viewWillAppear:, or even better, to viewWill/DidLayoutSubviews and than self.view.backgroundColor will give you the one you set in the IB.
To answer the question in your comment, go to the IB, select your view, go to the Identity Inspector tab under the Utilities (right) pane, and set a custom class to be MyCustomView. No need to implement loadView to do that.
e.g. a UIView that I have declared as a custom view class AGBlurView:
I've created a UIView programmatically that embeds several UIControls (UIButtons, UISwitchs and UILabels mainly). I set in the -(id)initWithFrame: of this class the background color.
I have created a message to add the UIControls in the view in order to put inside of my custom view. It's something like that:
-(void) CreateGuiObjects
{
UIView *container = self;
//create uiswitch
mOnOffSwitch = [[UISwitch alloc] initWithFrame:CGRectMake(10, 20, 0, 0)];
mOnOffSwitch.translatesAutoresizingMaskIntoConstraints = NO; //used for constraint testing
//add parent view (self)
[container addSubview: mOnOffSwitch];
/*
other stuff like above
*/
}
then in my view controller there is an event handler for an external button; the action is to add the above custom view in an empty UIView created with Storyboard Interface Builder in Xcode.
the code is like the following:
-(void)CreateButton
{
MyCustomView *view = [MyCustomView alloc] initWithFrame:CGRectMake(20,20,300,200)];
[self.view addSubview: view];
//now call my create method
[view CreateGuiObjects];
}
now, the problem is that it draws the controls, but it seems to position them in a different place...i set the frame origin for the container view in (20,20) and then put the switch in (10,20) where this point is relative to the custom view origin. Instead of that position the view seem to be far away from that position and the second problem is that the background color (gray) set in the initWithFrame is totally ignored.
If i remove every call to addSubview inside the CreateGuiObjects, it draws the empty view with the correct background color and in the correct position.
Edit:
if remove `mOnOffSwitch.translatesAutoresizingMaskIntoConstraints = NO; it works fine...but if i put again it doesn't work. Need to understand deeply the meaning of this property.
Could someone can help me? i think it is a silly question but i'm new to iOS development :(
thanks in advice
Method translatesAutoresizingMaskIntoConstraints = YES means that the UIView is using Auto Layout.
The fundamental building block in Auto Layout is the constraint.
Constraints express rules for the layout of elements in your
interface; for example, you can create a constraint that specifies an
element’s width, or its horizontal distance from another element. You
add and remove constraints, or change the properties of constraints,
to affect the layout of your interface.
When calculating the runtime positions of elements in a user
interface, the Auto Layout system considers all constraints at the
same time, and sets positions in such a way that best satisfies all of
the constraints.
read more about Auto Layout Concepts
If you don't know how to use Auto Layout I would recommend you to turn it off.
Everybody knows that you can't trust the frame size on a UIViewController init/viewDidLoad method; this:
- (void)viewDidLoad: {
NSLog(#"%d", self.view.frame.size.width);
}
will print wrong sizes in many occasions (in particular it's pretty much broken in landscape mode)
This will actually return always corrected results so it's good to layout the subviews:
- (void)viewWillAppear: {
NSLog(#"%d", self.view.frame.size.width);
}
The problem is that viewWillAppears gets called every time the view appears, so it's not suitable to alloc or add subviews. So you end up declaring every single view in the interface and you end up with huge header files that I don't like at all since most of the items don't need any more manipulations after the initial setup.
So question one is: Is there a better way to handle subviews positioning?
Question two is very related, let's say I have a subclass of UIView including various others subviews. I declare it inside my interface, and i alloc/init it in my init/viewDidLoad method.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
...
menu = [[SNKSlidingMenu alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
...
}
As we already know we now need to reposition it in viewWillAppear to get a more accurate reading
- (void)viewWillAppear:(BOOL)animated{
....
menu.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
....
}
The problem is that of course all the subviews needs to be repositioned as well. This is done by the layoutSubviews function that get called automatically, but we got the same problem: All the subviews need to be declared inside the interface of the SNKSlidingMenu class.. Is there a way around this?
Thanks.
If you are targetting iOS 5.0 or better you can use viewWillLayoutSubviews and viewDidLayoutSubviews to make changes.
As for your second question, if you need access to an instance variable in other method than init, you need to keep it around, I don't see a problem with it.
You can, however, try to use Auto Layouts and set up rules between the subviews so it's automatically laid out for you without the need to keep a reference.
viewDidLoad only gets called when your view is created, but lots of things can affect the frame's size, and it doesn't get called again when frame changes.
Instead:
create subviews in viewDidLoad
set their sizes in
viewWillLayoutSubviews.
See some additional discussion here for handling rotation: https://stackoverflow.com/a/16421170/1445366
viewWillLayoutSubviews and viewDidLayoutSubviews can resolve this problem.
But the two methed would be performed more times.
this is my code to get correct self.view.frame.
- (void)viewDidLoad {
[super viewDidLoad];
...
dispatch_async(dispatch_get_main_queue(), ^{
// init you view and set it`s frame. this can get correct frame.
...
}
...
}
This saved my life more than once (Swift 4):
override func viewDidLoad() {
super.viewDidLoad()
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
}
Basically this forces the viewcontroller to correctly layout it's view and from there you can get the correct frames for all your subviews. This particularly helps when doing transition animations and your view controllers are using autolayout and interface builder.
From what I've noticed it looks like the initial frames are set to whatever your interface builder's default size class is set to. I normally edit using the iPhone XS size class so in viewDidLoad it seems that the view's width is always 375 regardless whether you are using an iPhone XR or not. This corrects itself before viewWillAppear though.
The above code will correct this issue and allow you to get the correct frames for your view / subviews before the view controller is rendered to the screen.
There are a bunch of related questions here, but none that feels like a concise or correct answer. Here's the situation:
I am creating a new ViewController and don't want to use a nib file. My understanding from the Apple docs is that if I don't want to use a nib, I should implement loadView to manually create my own view hierarchy.
However, its not clear to me how I should properly instantiate self.view with the proper bounds (given this view controller might be used in a bunch of different situations, setting it simply to the screen bounds doesn't feel right).
Somehow the default UIViewController loadView does seem to properly initiate the frame size, but its not clear if I'm writing my own version what I should be doing to do this.
There is no need to implement loadView. Instead, implement viewDidLoad and create and add any and all desired subviews you want. Just add them to the default self.view.
If you want to add a view that fills the view controller's view then do something like the following:
- (void)viewDidLoad {
[super viewDidLoad];
UIView *someView = [[UIView alloc] initWithFrame:self.view.bounds];
someView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.view addSubview:someView];
}
In loadView, you should set the view property of viewController, and nothing else. Adding subviews should be done in viewDidLoad.
- (void)loadView {
self.view = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 80, 40)];
}
Roopesh Chander has an interesting blog post on which strategy to choose: loadView vs viewDidLoad
for Programmatic UI Setup. He recommends setting the frame in loadView rather than viewDidLoad for maximum efficiency.