I created a subclass of UIView class AView: UIView and drag a UIView to storyboard UIViewController interface builder file, change the class name to AView, then i drag another 2 UIViews on the AView assign tag 10, 11.
My question is when both subviews was created and initialized?
class AView: UIView{
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
print(self.subviews)
}
override func layoutSubviews() {
super.layoutSubviews()
print(self.subviews)
}
override func awakeFromNib() {
super.awakeFromNib()
print(self.subviews)
}
}
Only layoutSubviews print the subviews. Of course it should be. As documentation said seems the subviews has been created before this method:
The default implementation of this method does nothing on iOS 5.1 and earlier. Otherwise, the default implementation uses any constraints you have set to determine the size and position of any subviews.
Subclasses can override this method as needed to perform more precise layout of their subviews. You should override this method only if the autoresizing and constraint-based behaviors of the subviews do not offer the behavior you want. You can use your implementation to set the frame rectangles of your subviews directly.
Another question if super.layoutSubviews() is necessary?
When a nib file is loaded, first thing that happens is individual objects are loaded first, and initWithCoder is sent to them so that they can be initialised. Note that the object graph is not yet connected here. This is the exact reason, if you try to access subviews in init, you won't find any. So is the case with awakeFromNib that is called on to this view, it is only an indication that view is now loaded, but yet the object graph need not have been connected yet at this point.
To quote documentation on awakeFromNib-
The order in which the nib-loading code calls the awakeFromNib methods
of objects is not guaranteed. In OS X, Cocoa tries to call the
awakeFromNib method of File’s Owner last but does not guarantee that
behavior. If you need to configure the objects in your nib file
further at load time, the most appropriate time to do so is after your
nib-loading call returns. At that point, all of the objects are
created, initialized, and ready for use.
System calls layoutSubviews before it can actually present them or finds a re -org is needed, and here all object graph would have been connected by the nib loading process. So, you can access any view here and position/size it if you want by overriding. It is very important to call super.layoutSubviews before you do any changes.
Now coming to the question of when exactly all views are initialised and available for me to access?-
If we are loading via view controllers, then viewDidLoad is the place where every object from nib has been loaded and all outlets are connected, etc.
If we are loading the nib ourselves, it will be only after the nib loading call returns.
Related
Is there any function that is similar to viewWillAppear() for UIIbutton classes?
All these functions below are called only once
prepareForInterfaceBuilder()
awakeFromNib()
init()
So not exactly like a viewWillAppear, No.
This is because these are fundamentally different aspects of the architecture. One is usually the manager(a viewController), and the other always is a minion(UIView).
Their lifecycles are naturally different.
But if you want to perform a change in your custom view, whether a UIButton or any other UIView subclass, what you can do to reset is to use the method setNeedsLayout().
From the apple docs
Call this method on your application’s main thread when you want to
adjust the layout of a view’s subviews. This method makes a note of
the request and returns immediately. Because this method does not
force an immediate update, but instead waits for the next update
cycle, you can use it to invalidate the layout of multiple views
before any of those views are updated. This behavior allows you to
consolidate all of your layout updates to one update cycle, which is
usually better for performance.
Blockquote
You should perform further operations on the setting of the view by overriding
the layoutSubviews() method
Again, from the docs
Subclasses can override this method as needed to perform more precise
layout of their subviews. You should override this method only if the
autoresizing and constraint-based behaviors of the subviews do not
offer the behavior you want. You can use your implementation to set
the frame rectangles of your subviews directly. You should not call
this method directly. If you want to force a layout update, call the
setNeedsLayout() method instead to do so prior to the next drawing
update. If you want to update the layout of your views immediately,
call the layoutIfNeeded() method.
Hope I have been clear in the explanation.
You can also post your exact situation with code for more clarity and answers.
There is no strait way to do it: UILabel, UIButton haven't such functions because they are inherited from UIControl -> UIView -> UIResponder these classes haven't such functionality.
What you can to do: in your main controller which contains buttons and labels you can call custom method at viewWillAppear which will update content on your custom elements.
PS. Elements you can organize like an array or also check super views and based on protocol / class call your custom method.
With the help of Abhishek Arora, I was able to change the UILabel's textColor this way.
override func setNeedsLayout() {
tintColor = .clear
}
override func tintColorDidChange() {
print("TINT COLOR DID CHANGE")
textColor = .blue
}
}
I want to recall awakeFromNib when the tableView reload it's cells, to re-execuse the code I am executing there and newly load a pageViewController who is inside the cell, is that possible?
I believe awakeFromNib is called on an object when it is initialized from a nib file. So you can not call it manually.
Read this thread for object loading process from nib file.
See the documentation from apple about awakeFromNIb method
After all objects have been instantiated and initialized, the
nib-loading code reestablishes the outlet and action connections for
all of those objects. It then calls the awakeFromNib method of the
objects.
So, calling it manually looks not possible. Moreover I suggest you to put the lines of code in awakeFromNib into a seperate method and call that method. It will work for you.
As the other respondents point out, you should not call any of the "lifecycle" methods yourself. However, there is no reason on earth why you can't do something like:
override func awakeFromNib() {
super.awakeFromNib()
self.doSetupStuffINeedToRepeat()
}
func doSetupStuffINeedToRepeat() {
//...
}
Then you can simply call doSetupStuffINeedToRepeat() anytime you want. The reason that you shouldn't call awakeFromNib yourself is that, if done properly, it should call super.awakeFromNib, and doing this in the middle of execution may (will) really mess things up...
When I create a MSStickerBrowserViewController subclass by embedding it in a container view (using a storyboard) as the documentation suggests, I appear to have no opportunity to set the stickerSize. The Sticker Browser VC is initialized with init(coder:), and I have no way that I can see to override the get-only property stickerSize. I can only get stickerSize = MSStickerSize.regular.
I don't see any control in interface builder to configure the property either, though the documentation says "You can also customize the size of the stickers inside the browser."
Thanks to shallowThought, I see that an initializer is available init(stickerSize: MSStickerSize) on MSStickerBrowserViewController, but so far I have been unable to find a way to trigger that initializer when using a storyboard and overriding the required init(coder:) initializer.
Am i missing something?
Thanks!
This worked for me: I added stickerSize to the "User Defined Runtime Attributes" section of the Identity Inspector.
In this example, PetStickerBrowserViewController is a subclass of MSBrowserStickerViewController. stickerSize is set to 0, which corresponds to MSStickerSize.small.
At runtime, when the view controller is instantiated from the storyboard, its stickerSize property is set to .small. Use 1 for .regular (the default), and 2 for .large.
You can set it when initializing.
From Apples documentation.
StickerBroweserView
init(frame: CGRect, stickerSize: MSStickerSize)
Creates a new sticker browser containing stickers of the specified size.
StickerBroweserViewController
init(stickerSize: MSStickerSize)
Creates a new sticker browser view controller with stickers of the provided size.
To subclass it, implement the corresponding initializers.
I can not see a way to call init(stickerSize: MSStickerSize) by somehow chaining init calls from required init?(coder aDecoder: NSCoder), so you might have to instantiate the viewControllers programmatically.
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.
Is there an easy way to know when all the subivews have loaded?
Right now I'm doing:
if([[self subviews] count] == 10) {
//do stuff
}
If there isn't an event/method for this, is there at least a way to dynamically know what the child count is GOING to be?
edit
I re-read this just now and realize it's a bit asinine. Let me clarify:
I'm loading this UIView from a XIB file and I wanted to know when the NIB has officially loaded (with all of it's children). So I dare say the correct answer would be awakeFromNib
If you're calling this from a viewController, just use
-(void)viewDidLoad;
Which is called after the view and all its subviews are loaded. If you're doing it from one of the views inside the nib, use:
-(void)awakeFromNib;
Which is called after the view's subviews have been loaded.
If you are adding subviews programmatically (e.g. [myView addSubview:anotherView]), then of course there is no way to know; the program could add more subviews at any time, if you write it that way.
If you are loading the view from a nib, you are probably looking for the awakeFromNib method. From the NSObject UIKit Additions Reference:
The nib-loading infrastructure sends an awakeFromNib message to each object recreated from a nib archive, but only after all the objects in the archive have been loaded and initialized. When an object receives an awakeFromNib message, it is guaranteed to have all its outlet and action connections already established.