What is more efficient, overriding drawRect or using viewWillAppear - ios

I have several UITextViews in several ViewControllers. In the past, when I have a couple of instances of needing a custom drawing for a TextView or Label, etc I would just adjust the drawing in viewWillAppear inside the VC that owned the UI object. This time, I will be needed several instances to be customized.
Would it be more appropriate to just create a subclassed UITextView and include the drawing code in drawRect versus having the same drawing code spread around several VC's. I am mainly worried about performance. Code maintainability is a secondary concern though.
To be clear, this is what I would use in drawRect:
- (void)drawRect:(CGRect)rect {
self.layer.cornerRadius = 10;
self.clipsToBounds = YES;
}
So after further testing, initWithFrame doesn't get called, but initWithCoder does. I have also found that initWithCoder is called once and so is drawRect. In my use case (a StaticCellTableView with the UITextView in a cell, what would the difference be?

You can include those 2 lines in init or initWithCoder (In case it's a xib or storyboard) method of the subclassed UITextView.
Basically, this is just properties of the object - the don't need to run every time the UIView needs to refresh itself.

Create a ViewController baseclass, and then use that as the superclass for all your other VCs. You can then add this and other convenience methods to it and share the wealth so to speak.
In this particular case, putting it at the end of viewDidLoad would be a good place, as that is only messaged once, where viewWillAppear may be called multiple times. If you use viewdidLoad the code will be executed once.

I would not put those lines in the view's drawRect since that's called for every refresh. I'd recommend that you subclass UITextView and add those lines to the custom init method.

Related

Single drawRect implementation across multiple UIView subclasses

Across my app I have several different subclasses of UIView: UIDatePicker, UIPicker, UIButton, UITableView, UITableViewCell, UITextView, etc. etc... For each of these I'd like to add a very simple drawRect custom implementation that I have working great.
Is there a simple way to get multiple subclasses of UIView to all have the same drawRect implementation without creating a subclass and repeating the same code across each UIPicker, UIButton, etc. etc... ?
I realize the solution to this is probably to write a delegate class for UIView's layer property and do the custom drawing in drawLayer, but I thought I would ask before I go re-working my code.
The answer seems to be no.
Furthermore, it appears that my idea for the workaround also doesn't work: I was thinking I could write a nice little class that implements the drawLayer method from CALayerDelegate and do the drawing in there, and then in each UIView or UIView subclass' init method do a self.layer.delegate = niceLittleCALayerDelegateClass.
My research, however, happened upon this: https://developer.apple.com/library/ios/documentation/GraphicsImaging/Reference/CALayer_class/#//apple_ref/occ/instp/CALayer/delegate which contains the damning sentence: In iOS, if the layer is associated with a UIView object, this property must be set to the view that owns the layer.
So, double nope.
Against rmaddy's advice I'm going to just write a subclass for each of the UIView subclasses I want to implement this drawing behavior in. We'll see how that goes.
Update:
I can't believe I didn't think of this before, but the "right" way to do this (that is to say without subclassing UIView's subclasses and adding a custom drawRect method to each subclass's subclass), from everything I can find, seems to be to either create a subview or a sublayer with a transparent background that does whatever custom drawing you want.
Obviously this is going to draw on top of the UIView you're actually using, so this would get exceedingly complicated if you're trying to draw things that interact with the default elements of the UIView, but for my purposes (just a simple frame drawn with a UIBezierPath) it seems to work great.
At present I'm not sure if the subview or the sublayer approach is more efficient. If anyone can shed light on that, I'd appreciate it.

UIVIewController and UIView - How to refresh UIView

After having spent the whole day looking for a solution I feel only more confused and upset.
Let's face the problem:
I'm developing a single view iOS app made up of an AppDelegate (of course..), A ViewController and a "DrawingClass" (subclass of UIView).
In the main.storyboard i can see my mainViewControllerScene, and inside this main view directed by the viewController, I have inserted a UIView object from the palette in the interface builder and set it to be controlled by my "Drawing class" because I need to use the DrawRect method to draw custom lines.
Well, when starting the app, the defaults lines in my "DrawingClass" are being drawn, so drawrect is being called.
But when, after having pressed a button linked to an IBAction, I try
to call again drawrect through setNeedsDisplay or anything it doesn't
work.
Let's be more clear:
-I'm sure that the view controlled by "DrawingClass" is being drawn correctly on startup
-I'm sure that the IBAction is called (I used an NSLog)
-I can't figure out how to redraw that view. (The one controlled by "DrawingClass")
In the viewController I tried both [self.view setNeedsDisplay] and [myView setNeedDisplay] but none of them called my drawrect method in the "DrawingClass"
What I'm doing wrong? Am I forgetting to init something ? I tried even to call those methods on the main thread but nothing.
I think this question could help many so please ask if you need something more to work out this problem.
Thank you so much.

Proper practice for subclassing UIView?

I'm working on some custom UIView-based input controls, and I'm trying to ascertain proper practice for setting up the view. When working with a UIViewController, it's fairly simple to use the loadView and related viewWill, viewDid methods, but when subclassing a UIView, the closest methosds I have are `awakeFromNib, drawRect, and layoutSubviews. (I'm thinking in terms of setup and teardown callbacks.) In my case, I'm setting up my frame and internal views in layoutSubviews, but I'm not seeing anything onscreen.
What is the best way to ensure that my view has the correct height and width that I want it to have? (My question applies regardless of if I'm using autolayout, although there might be two answers.) What's the proper "best practice"?
Apple defined pretty clearly how to subclass UIView in the doc.
Check out the list below, especially take a look at initWithFrame: and layoutSubviews. The former is intended to setup the frame of your UIView whereas the latter is intended to setup the frame and the layout of its subviews.
Also remember that initWithFrame: is called only if you are instantiating your UIView programmatically. If you are loading it from a nib file (or a storyboard), initWithCoder: will be used. And in initWithCoder: the frame hasn't been calculated yet, so you cannot modify the frame you set up in Interface Builder. As suggested in this answer you may think of calling initWithFrame: from initWithCoder: in order to setup the frame.
Finally, if you load your UIView from a nib (or a storyboard), you also have the awakeFromNib opportunity to perform custom frame and layout initializations, since when awakeFromNib is called it's guaranteed that every view in the hierarchy has been unarchived and initialized.
From the doc of NSNibAwaking (now superseded by the doc of awakeFromNib):
Messages to other objects can be sent safely from within awakeFromNib—by which time it’s assured that all the objects are unarchived and initialized (though not necessarily awakened, of course)
It's also worth noting that with autolayout you shouldn't explicitly set the frame of your view. Instead you are supposed to specify a set of sufficient constraints, so that the frame is automatically calculated by the layout engine.
Straight from the documentation:
Methods to Override
Initialization
initWithFrame: It is recommended that you implement this method. You can also implement custom initialization methods in addition to,
or instead of, this method.
initWithCoder: Implement this method if you load your view from an Interface Builder nib file and your view requires custom
initialization.
layerClass Implement this method only if you want your view to use a different Core Animation layer for its backing store. For example,
if you are using OpenGL ES to do your drawing, you would want to
override this method and return the CAEAGLLayer class.
Drawing and printing
drawRect: Implement this method if your view draws custom content. If your view does not do any custom drawing, avoid overriding this
method.
drawRect:forViewPrintFormatter: Implement this method only if you want to draw your view’s content differently during printing.
Constraints
requiresConstraintBasedLayout Implement this class method if your view class requires constraints to work properly.
updateConstraints Implement this method if your view needs to create custom constraints between your subviews.
alignmentRectForFrame:, frameForAlignmentRect: Implement these methods to override how your views are aligned to other views.
Layout
sizeThatFits: Implement this method if you want your view to have a different default size than it normally would during resizing
operations. For example, you might use this method to prevent your
view from shrinking to the point where subviews cannot be displayed
correctly.
layoutSubviews Implement this method if you need more precise control over the layout of your subviews than either the constraint or
autoresizing behaviors provide.
didAddSubview:, willRemoveSubview: Implement these methods as needed to track the additions and removals of subviews.
willMoveToSuperview:, didMoveToSuperview Implement these methods as needed to track the movement of the current view in your view
hierarchy.
willMoveToWindow:, didMoveToWindow Implement these methods as needed to track the movement of your view to a different window.
Event Handling:
touchesBegan:withEvent:, touchesMoved:withEvent:, touchesEnded:withEvent:, touchesCancelled:withEvent: Implement
these methods if you need to handle touch events directly. (For
gesture-based input, use gesture recognizers.)
gestureRecognizerShouldBegin: Implement this method if your view handles touch events directly and might want to prevent attached
gesture recognizers from triggering additional actions.
This still comes up high in Google. Below is an updated example for swift.
The didLoad function lets you put all your custom initialization code. As others have mentioned, didLoad will be called when a view is created programmatically via init(frame:) or when the XIB deserializer merges a XIB template into your view via init(coder:)
Aside: layoutSubviews and updateConstraints are called multiple times for the majority of views. This is intended for advanced multi-pass layouts and adjustments when a view's bounds changes. Personally, I avoid multi-pass layouts when possible because they burn CPU cycles and make everything a headache. Additionally, I put constraint code in the initializers themselves as I rarely invalidate them.
import UIKit
class MyView: UIView {
//-----------------------------------------------------------------------------------------------------
//Constructors, Initializers, and UIView lifecycle
//-----------------------------------------------------------------------------------------------------
override init(frame: CGRect) {
super.init(frame: frame)
didLoad()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
didLoad()
}
convenience init() {
self.init(frame: CGRectZero)
}
func didLoad() {
//Place your initialization code here
//I actually create & place constraints in here, instead of in
//updateConstraints
}
override func layoutSubviews() {
super.layoutSubviews()
//Custom manually positioning layout goes here (auto-layout pass has already run first pass)
}
override func updateConstraints() {
super.updateConstraints()
//Disable this if you are adding constraints manually
//or you're going to have a 'bad time'
//self.translatesAutoresizingMaskIntoConstraints = false
//Add custom constraint code here
}
}
There's a decent summary in the Apple documentation, and this is covered well in the free Stanford course available on iTunes. I present my TL;DR version here:
If your class mostly consists of subviews, the right place to allocate them is in the init methods. For views, there are two different init methods that could get called, depending on if your view is being instantiated from code or from a nib/storyboard. What I do is write my own setup method, and then call it from both the initWithFrame: and initWithCoder: methods.
If you're doing custom drawing, you indeed want to override drawRect: in your view. If your custom view is mostly a container for subviews, though, you probably won't need to do that.
Only override layoutSubViews if you want to do something like add or remove a subview depending on if you're in portrait or landscape orientation. Otherwise, you should be able to leave it alone.
layoutSubviews is meant to set frame on child views, not on the view itself.
For UIView, the designated constructor is typically initWithFrame:(CGRect)frame and you should set the frame there (or in initWithCoder:), possibly ignoring passed in frame value. You can also provide a different constructor and set the frame there.

Where to place the code to change the properties of a UIView

If I'm creating a UIView programmatically and I wish to change the UIView properties (background, for example, or actually, messing with CALayers), must I place the code outside of UIView such as in the View controller? Can I put the code somewhere inside UIView?
I was checking out the CoreAnimationKioskStyleMenu example, its code is inside UIView but it's loaded from Nib and can be placed at awakeFromNib, so it doesn't seem to apply to my case.
That depends. Obviously, a good way to handle this is to use a xib file, as it is designed to hold data like this, but that isn't always the best answer for every situation.
If the view is meant to be reused frequently (like a button, or some widget) throughout the application, its best to store all that customization in a subclass of the UIView.
If its a single larger view that will always be managed by a UIViewController, you can keep some of the information in the UIViewController. However, if you end up subclassing a UIView anyway it's probably best practice to keep the data in the UIView.
As a general note, I believe its worth your time to push as much of this data into a xib using interface builder. Magic values (like colors or sizes) peppered through your code will always be a problem if you want to modify it. I have found modifying a xib to be much easier.
Actually there are some methods where you could place initialization/ customization code.
(void)willMoveToSuperview:(UIView *)newSuperview;
(void)didMoveToSuperview;
will get called as soon as u add the view as a subview to another view, at which point you already have the frame and all the properties, and you can do further customizing as you wish.
(void)layoutSubviews -- generally used for changing subviews' frames and layout organization.
Will get called each time the view needs to be redrawn by the system, or when you specifically call [self setNeedsLayout] on your UIView.
Hope this helps.

The right way to manage subviews in a UIControl

(iPhone SDK 3.x:) I have a UIControl subclass that creates a different number of subviews depending on the length of an NSArray property. Please take my word for it that this needs to be a UIControl rather than a UIView.
Currently I implement subview management in drawRect, beginning by removing all subviews and then creating the appropriate number based on the property. I don't think this is very good memory management and I'm not sure if drawRect is really the appropriate place to add subviews. Any thoughts on the best way to handle this pattern?
Thank you.
There is a method called layoutSubviews, and like the name already says, that method is thought to layout the subviews. You can call setNeedsLayout and the layoutSubviews method will be called (do not call layoutSubviews directly).

Resources