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.
Related
I came across the exception 'NSInternalInconsistencyException' when I write the code below in a viewcontroller which is associated with storyboard:
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
self.view.backgroundColor = [UIColor blackColor];
}
return self;
}
However, if I delete the line 'self.view.backgroundColor = [UIColor blackColor];', there is no problem. I don`t know the reason.
Actually, in the stroyboard I have several controllers, and I want to make a base controller for all the controllers. The purpose is that I want to set a background view(UIView) for them.
I try to add a UIView in the viewDidLoad method of the basecontroller, but the view covers any components that set in the storyboard. I think the reason is that the components on the sub viewcontrollers are initialized in the 'initWithCoder' method before the 'ViewDidLoad' method is called. So I try to add the bgView in the 'initWithCoder' method of the basecontroller, and this led to the problem above.
So I also want to know a right way to achieve my purpose. Thanks!
dont set graphics related properties in init and bring subview to front. Set them from
-(void)awakeFromNib
{
[self.view.superview bringSubviewToFront:self.view];
self.view.backgroundColor = [UIColor blackColor];
}
Where should I initialize a UITablewView as a subview, in - (id)initWithFrame:(CGRect)frame, in viewdidload or loadView? Which is the better approach? Where should I make the frame (I mean which is more effective)?
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
self.backgroundColor = kViewBackgroundColor;
_tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
_tableView.backgroundView = nil;
_tableView.backgroundColor = [UIColor clearColor];
_tableView.bounces = NO;
[self addSubview:_tableView];
}
return self;
}
On my projects I usually create a baseViewController with a custom initialiser like this:
- (id)init{
self = [self initWithNibName:NSStringFromClass([self class]) bundle:[NSBundle mainBundle]];
if (self) {
//You should create the tableView and other properties here
//and add as subviews inside viewDidLoad
_tableView = [[UITableView alloc]initWithFrame:CGRectMake(0,0,100,100)];
}
return self;
}
and I always create my view controller by using this init method, because I don't think the other vc's need to know the name of the nib file. If I were you I would create subviews inside init method, add as subviews inside viewDidLoad or viewWillAppear and finally releasing them inside dealloc if you are not using ARC.
Generally this depends on your requirement that if you are having custom view called with a tableview as subview then loadview with the initwithframe method will be better and while you want to initialize it from viewcontroller then viewdidload is better. I think you should bifurcate your requirement. Hope this helps.
Since you are initializing table view with zero frame it wont be visible even if you added that
you can try this
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
self.backgroundColor = kViewBackgroundColor;
UITableView *_tableView = [[UITableView alloc] initWithFrame:self.bounds style:UITableViewStyleGrouped];
_tableView.backgroundView = nil;
_tableView.backgroundColor = [UIColor clearColor];
_tableView.bounces = NO;
[_tableView setAutoresizingMask:UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth]
[self addSubview:_tableView];
}
return self;
}
If you'd like to create subviews programmatically based on superview metrics (frame or bounds), you should do it in viewDidLoad, because if you do in in initializer, metrics of self.view will not be avaliable, and you subviews will be created with zero frame and will not be visible (as Johnykutty) mentioned above.
But that does not seem to be a good practice in case your subview initialization routines require heavy operations.
Offtopic: I've been codin' for a looong time trying to do everything programmatically, never using xibs, etc. Well, if you ask me now, xibs really save your time and make life easier. Consider using interface builder for layout\autolayout, colors\borders etc stuff.
I am wanting to create a custom UIView class that will show a dynamic number of UISegmentedControl objects depending on some input. For example, if a client has 5 products in their cart, the UIView should generate 5 UISegmentedControl objects that I will then link with each item.
The problem I am having is getting this to work in a UIView. Here is what I have done so far. I am successfully able to create a UISegmentedControl object and display it programmatically within my main UIViewController. I don't get any display when adding it to my UIView class. Here is the implementation code for the UIView class:
#import "ajdSegmentView.h"
#implementation ajdSegmentView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
NSArray *itemArray = [NSArray arrayWithObjects:#"Yes", #"No", nil];
UISegmentedControl *button = [[UISegmentedControl alloc] initWithItems:itemArray];
button.frame = CGRectMake(35,44, 120,44);
button.segmentedControlStyle = UISegmentedControlStylePlain;
button.selectedSegmentIndex = 1;
[self addSubview:button];
}
return self;
}
#end
I created a new UIView object via Storyboard and placed it inside the UIViewController scene. I made sure to set the class from the generic UIView class to my new custom class. I added and outlet for the UIView in my UIViewController class. Here is the code inside the implementation of UIViewController:
#import "ajdViewController.h"
#interface ajdViewController ()
#end
#implementation ajdViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.segmentView = [[ajdSegmentView alloc] init];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
That's all I have tried. I have been searching through a lot of pages and trying to implement this without asking here, but I seem to be looking in the wrong places.
First you need to check ajdSegmentView is UIVIew or UIViewController. It is fine if it is UIView. If it is type of UIViewController then you need to add this line while adding Segment.
[self.view addSubview:button];
In place of:
[self addSubview:button];
And One more thing You forget to add this View to your main after allocating so You can declare like this:
objajdSegmentView = [[ajdSegmentView alloc] init];
[self.view addSubview:objajdSegmentView.view];
I have just added this thing. i got result like this way.
Hope this will work for you.
You're initializing your custom view using the init method, but your initialization for ajdSegmentView is in your initWithFrame: method (which in your case is not getting called).
So replace:
self.segmentView = [[ajdSegmentView alloc] init];
with:
// Change the frame to what you want
self.segmentView = [[ajdSegmentView alloc] initWithFrame:CGRectMake(0,0,100,40)];
Also don't forget to add your view to the view controller's view also.
[self.view addSubview:self.segmentView];
Unless this view is being created with interface builder, in which case you will need to override initWithCoder: in your ajdSegmentView class.
I'm not familiar with Storyboard though, so maybe I'm missing something, but in a standard scenario what I said above will solve your problem.
I have a custom view which contains two UILabel. I want to customize their fonts before so I did that in initWithCoder method.
#implementation HomeTitleView
#synthesize ticketLabel;
#synthesize monthLabel;
- (id) initWithCoder:(NSCoder *)aDecoder{
self = [super initWithCoder:aDecoder];
if (self) {
[monthLabel setFont:[UIFactory getFontForKey:#"home_month"]];
[ticketLabel setFont:[UIFactory getFontForKey:#"home_ticket"]];
}
return self;
}
#end
Unluckily, this did not work. Using a debugger, I found that monthLabel and ticketLabel are both nil. Anyone has idea how can I solve this? What callback or method I should implement so that I can access both of my labels?
You can't do that. The views don't exist yet. They are instantiated when the loadView method is called, which happens automatically when the view property is first accessed. If you want to manipulate your views after they have loaded, the correct method to use is viewDidLoad.
Edit: That's assuming you are working with a UIViewController class. If you are working with a UIView class, you can use awakeFromNib or didAddSubview:.
Do you ever assign monthLabel and ticketLabel to UILabel? Something like:
self.monthLabel = [[[UILabel alloc] init] autorelease];
self.ticketLabel = [[[UILabel alloc] init] autorelease];
If so, can you update your post with the code?
I can't figure out what the problem is here. I have a very simple UIViewController with a very simple viewDidLoad method:
-(void)viewDidLoad {
NSLog(#"making game view");
GameView *v = [[GameView alloc] initWithFrame:CGRectMake(0,0,320,460)];
[self.view addSubview:v];
[super viewDidLoad];
}
And my GameView is initialized as follows:
#interface GameView : UIView {
and it simply has a new drawRect method:
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
NSLog(#"drawing");
}
In my console, I see "making game view" being printed, but "drawing" never is printed. Why? Why isn't my drawRect method in my custom UIView being called. I'm literally just trying to draw a circle on the screen.
Have you tried specifying the frame in the initialization of the view? Because you are creating a custom UIView, you need to specify the frame for the view before the drawing method is called.
Try changing your viewDidLoad to the following:
NSLog(#"making game view");
GameView *v = [[GameView alloc] initWithFrame:CGRectMake(0,0,320,460)];
if (v == nil)
NSLog(#"was not allocated and/or initialized");
[self.view addSubview:v];
if (v.superview == nil)
NSLog(#"was not added to view");
[super viewDidLoad];
let me know what you get.
Check if your view is being displayed. If a view is not currently on screen, drawRect will not be getting called even if you add the view to its superview. A possibility is that your view is blocked by the some other view.
And as far as I know, you don't need to write [super drawRect];
Note that even if viewDidLoad is called on a view controller it doesn't necessarily indicate the view controller's view is displayed on screen. Example: Assume a view controller A has an ivar where a view controller B is stored and view controller A's view is currently displayed. Also assume B is alloced and inited. Now if some method in A causes B's view to be accessed viewDidLoad in B will be called as a result regardless whether it's displayed.
If you're using a CocoaTouch lib or file you may need to override the initWithCoder method instead of viewDidLoad.
Objective-C:
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
//Do Stuff Here
}
return self;
}
I had a view that was offscreen, that I would load onscreen and then redraw. However, while cleaning up auto-layout constraints in XCode, it decided my offscreen view should have a frame (0,0,0,0) (x,y,w,h). And with a (0,0) size, the view would never load.
Make sure your NSView has a nonzero frame size, or else drawRect will not be called.