I have a simple UIView subclass where I place a UILabel within, in order to give such UIView a certain frame, background color and text format for its label from a UIViewController.
I'm reading the View Programming Guide for iOS but there are some things that I'm not fully understanding... when your view has only system standard subviews, such my UILabel, should I override the drawRect: method? Or is that only intended for Core Animation staff? If I shouldn't setup standard subviews within such method, what is the correct place to do so? Should I then override the init method?
Thanks in advance
No, you should not override the drawRect for initializing the sub views because it causes a performance hit. That should be done either in the init, initWithFrame, or initWithCoder methods. For example, this is how you do it using the initWithFrame method
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
//initialize sub views
}
return self;
Related
I think this is not an opinionated question, but rather a question made to know if this is a good practice or not in iOS.
I'm currently doing this registration of custom tableViewCells in every controller in my project.
// MARK: Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.tableHeaderView = UIView()
self.tableView.tableFooterView = UIView()
self.tableView.register(R.nib.customCellXX)
self.tableView.register(R.nib.customCellYY)
// and so on...
self.tableView.estimatedRowHeight = 150.0
self.tableView.rowHeight = UITableViewAutomaticDimension
}
I'm just wondering if I could just make a subclass of tableView and register there all the custom tableViewCells I have as well as put the auto-height of the tableView + tableHeaderView + tableFooterView. In this case, I'm reducing the line of codes in my controllers and avoiding redundancies.
EDIT: If this is a good practice, where can I put the registration of the custom cells in my subclassed BaseTableView?
It can be done in the following way:
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style {
self = [super initWithFrame:frame style:style];
if (self) {
[self registerClass:[MyTableViewCell class] forCellReuseIdentifier:#"reUseIdentifier"];
}
return self;
}
Later, you can subclass this UITableView or even use this in different controllers.
You COULD, as long as you call super.viewDidLoad(), inside of the child VC's overridden viewDidLoad methods. That said, subclassing VCs is generally bad practice in iOS.
If you use Interface Builder, the IBOutlets you wire up from your Storyboard to your .swift files, all have identifying values, and can cause nil reference crashes, and cause build errors.
If you're trying to use the same IBOutlets across multiple VCs, which you will necessarily do if you try to wire any UI element to your parent VC.
If you want all of your VCs to have some shared SIGNATURE (i.e., the same methods or properties), create a custom protocol. If you want them all to have access to the same UI elements in Interface Builder, learn to use Storyboards.
You can create a VC in a Storyboard, and instantiate it in code, easily.
In my program I have started doing all initialization of objects in the init method without setting a frame and then in layoutSubviews I set the frames for these objects to make sure that they are properly set.
Firstly is this proper practice to initialize all objects in the init function without a set frame and then in layoutSubviews set each of their frames. The reason for my concern is that it is called quite often.
So I have a UIView subclass where I call these methods in the layoutSubviews
- (void)layoutSubviews
{
[super layoutSubviews];
[self.filterSwitcherView setFrame:self.viewFrame];
[self.drawingView setFrame:self.viewFrame];
[self.textView setFrame:self.textViewFrame];
[self.colorPicker setFrame:self.colorPickerFrame];
}
This currently works fine and all the objects are set correctly, but the problem is in my colorPicker class when the user touches the screen I adjust the frame of the colorPicker and by doing so this method gets called from the subview colorPicker and it readjusts a frame that it shouldn't since it has been modified in the subview. The subview causes the superviews layoutSubview to be called and this is not what I need.
My question is, is there a way to stop this behavior from happening or should I not use layoutSubviews to set frames because I was told this is a better way of making views programmatically?
Off the top of my head, there's two ways to fix this. You can either move this code to where the view is initialized, either in init, initWithFrame:, or initWithCoder:, depending on which you're using. It's good practice to make a separate method to initialize everything for your view, and call it from all the init methods to make sure it's always initialized correctly no matter how you instantiate the view.
Alternatively, if you want to keep your code in layoutSubviews, in your #interface add a boolean to flag that the frames were already set
#interface MyView : UIView
#property (nonatomic, assign) BOOL framesAreSet;
#end
Then when you set your frames, check if you already did
- (void)layoutSubviews
{
[super layoutSubviews];
if (!_framesAreSet)
{
[self.filterSwitcherView setFrame:self.viewFrame];
[self.drawingView setFrame:self.viewFrame];
[self.textView setFrame:self.textViewFrame];
[self.colorPicker setFrame:self.colorPickerFrame];
_framesAreSet = YES;
}
}
Your issue is likely that your colorPicker class is handling the touch methods to adjust its own frame. Instead, you should handle the touch methods in colorPicker's superview class, and have that superview class adjust colorPicker's frame in response to the touches.
Also, I would recommend doing all UI initialization in initWithFrame:, not init. The reason is because calling init on UIView ends up calling initWithFrame:.
I am trying to figure out all the different ways to create a view and am having problems with dragging it out from the object library when I have a custom xib.
This is what I have in my UIView:
-(id)initWithCoder:(NSCoder *)aDecoder{
self=[super initWithCoder:aDecoder];
if (self) {
}
NSLog(#"about to return here");
return self;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
UIView *myView=[[[NSBundle mainBundle] loadNibNamed:#"testview" owner:nil options:nil] lastObject];
[self addSubview:myView];
}
return self;
}
// drawRect is commented out
I am able to drag out a custom view if I override drawRect but am unable to do this if I drag it out from the Object Library and update the Custom Class in the Identity Inspector.
I know I am doing something really naive but I can't believe I can't get this to work. Any ideas on how to be able to drag out a generic view to a storyboard controller and update the Custom Class and have it work?
edit #1
here's a screenshot. It is trying to draw something but it's like it doesn't know what to draw. I've added a couple of setNeedsDisplay but with no success. The main view's background is light blue and the custom view should be green. The white lines up with where this custom view should be.
Even with the current version of XCode 4.6.3, you cannot have custom objects shown in Interface Builder without writing a XCode Plugin (which is not even supported at the moment).
So the only solution is to drag in a Generic View from the existing objects, and change the object type afterwards. Unfortunately, you won't see any visuals of your custom UIView in Interface Builder. Annoying, isn't it?
I want my UIView subclass to position itself automatically when it is added to a parent container view.
Can I somehow detect when it is added and run my positioning code then or do I need to do something like?
[parentView addSubview:subView];
[setView calcPosition];
UIView provides the methods willMoveToSuperview: and didMoveToSuperview. Just override those to know when the view is added to another view (or later removed).
Write calcPosition methord inside the subview and call it from the didMoveToSuperview of subview
- (void)didMoveToSuperview
{
[super didMoveToSuperview];
[self calcPosition];
}
I have a .xib file for my viewController. It contains a TableView and a UIView. The UIView is backed by a custom class - CommentsBarView (trying to position a small bar with a comments field underneath my tableView).
So in my Document Outline list I have:
view
tableView
comments bar view
UITextView
UILabel
Custom class for "comments bar view" is CommentsBarView.
I have outlets connected from within CommentsBarView to the textfield and label.
(and from the ViewController to the tableView).
When I load my with controller with:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
I can access the tableView property and change the appearance of the tableVIew, however, from my commentsBarView initWithCoder I can not set the text value on my textView and label:
- (id) initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self.commentTextView setText:#"Helo, World!"];
[self.characterCountLabel setText:#"200"];
}
return self;
}
It seems as if these properties are not available at initWithCoder time.
If I manually, from my controllers initWithNibName, accesses self.commentsBar.label.text = 200, there is no problem.
Am I experiencing a timing issue where the views are not ready yet or can I not nest a view inside a viewControllers view and have it backed by a custom UIView subclass?
IB is confusing me a bit.
Thanks for any help given.
When loading from a XIB file, the IBOutlets are not ready in the init methods of the objects being unarchived.
You need to override awakeFromNib in your CommentsBarView to have access to the ready and connected IBOutlets.
Once you get used to IB it becomes better. Since Cocoa is a MVC (Model-View-Controller) you should probably not create a UIView subclass and set your UIView to it. You should probably put the UIView back to just a UIView. I generally subclass a UIView if I need to have a customized look. For example; a good time to subclass a UIView is if you want it to have a gradient. Then you can reuse your subclass for any UIView you wish to show the gradient.
In your case you are trying to "control" the UITextView and UILabel. You can instead wire-up outlets of your UITextView and UILabel directly to your UIViewController (File Owner). That is the "controller" of the MVC in this case. Think of your UIView as a container that is simply holding the two controls for this example. Now you can use the viewDidLoad method or some other method of you UIViewController to set the values of you UITextView and UILabel. It is generally the UIVeiwController that interprets the data from the Model in Cocoa and places the data where it needs to be. It is not a rock-solid rule, but a good one.