168

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"?

Andrew Barber
  • 39,603
  • 20
  • 94
  • 123
Moshe
  • 57,511
  • 78
  • 272
  • 425

4 Answers4

310

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.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Gabriele Petronella
  • 106,943
  • 21
  • 217
  • 235
  • what about - (void) setFrame:(CGRect)frame? – pfrank Sep 28 '13 at 22:08
  • well you can definitely override it, but for what purpose? – Gabriele Petronella Sep 28 '13 at 22:09
  • to change layout/drawing anytime the frame size or location changes – pfrank Sep 28 '13 at 22:13
  • 1
    What about `layoutSubviews`? – Gabriele Petronella Sep 28 '13 at 22:14
  • From http://stackoverflow.com/questions/4000664/is-there-a-uiview-resize-event, "the trouble with this is that subviews can not only change their size, but they can animate that size change. When UIView runs the animation, it does not call layoutSubviews each time." Haven't tested it personally though – pfrank Sep 28 '13 at 22:38
  • it's a big vague. You may want to test a specific use case and report back ;) – Gabriele Petronella Sep 28 '13 at 22:52
  • Workin' on it, check out http://stackoverflow.com/questions/19072585/static-background-image-location-in-obj-c-similar-to-background-attachment-fix :) – pfrank Sep 28 '13 at 22:59
  • It's also often useful to override intrinsicContentSize – Pierre Houston Aug 22 '18 at 23:05
  • The documentations for `updateConstraints` says `Override this method to optimize changes to your constraints.` implying that it's not for regular use. So where is the truth? It maybe called multiple times, why they say that it's a place for creating constraints? – DanSkeel Feb 21 '19 at 15:01
42

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
  }
}
seo
  • 1,959
  • 24
  • 18
  • Can you explain when/why you would split up layout constraint code between a method called from your init process and layoutSubviews and updateConstraints? It seems like they are all three possible candidate locations to place layout code. So how do you know when/what/why to split up layout code between the three? – Clay Ellis Mar 24 '16 at 17:39
  • 3
    I never use updateConstraints; updateConstraints can be nice because you know your view hierarchy was set up fully in init, so you can't raise an exception by adding a constraint between two views not in the hierarchy :) layoutSubviews should never have constraint modifications; it can easily cause an infinite recursion as layoutSubviews is called if constraints are 'invalidated' during the layout pass. Manual layout setup (as in setting frames directly, which you rarely need to do anymore unless for performance reasons) goes in layoutSubviews. Personally, I place constraints creation in init – seo Apr 07 '16 at 19:07
  • For custom rendering code, should we override `draw` method? – Petrus Theron Aug 07 '18 at 14:22
14

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.

dpassage
  • 5,423
  • 3
  • 25
  • 53
  • I use your answer to change the view(which is awakeFromNib)'s subView's frame in `layoutSubViews`, it did worked. – aircraft Nov 07 '16 at 08:27
1

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.

proxi
  • 1,243
  • 10
  • 18
  • could you take it more detail? I did not know your mean.how to set a view's subView's frame ? the view is `awakeFromNib` – aircraft Nov 07 '16 at 08:19
  • Fast-forward to 2016, you probably shouldn't set frames at all and use autolayout (constraints). If the view is coming from XIB (or storyboard), the subview should already be setup. – proxi Nov 08 '16 at 10:24