Is there any viewDidAppear method for UIView (not UIViewController)? - ios

I need to understand when UIView appears on the screen, so I need an analogue of the viewDidAppear method.
I found a UIView lifecycle:
willMoveToSuperview
invalidateIntrinsicContentSize
didMoveToSuperview
awakeFromNib
willMoveToWindow
needsUpdateConstraints
didMoveToWindow
setNeedsLayout
updateConstraints
layoutSubviews
drawRect
I tried all of these methods, but I didn't get an answer.

No there is no viewDidAppear in UIView. you may override func drawRect to do any UI changes that you need on UIView inherited View.
SideNote - In case you want get drawrect to update at later times, Call setNeedsDisplay. setNeedsDisplaywill not immediately call drawRect but marks the receiver’s entire bounds rectangle as needing to be redrawn.
In other words - You should never call drawRect yourself. Instead, you tell the system that drawing needs to be done by using the setNeedsDisplay method, which marks the view as dirty. And the drawRect method of the subclass would then be called during the next update cycle.
As per the queries from OP(#Alexander), he just need to set some variable so it advisable to use any of the following override functions, depending on action need to be performed
-(void)didMoveToSuperview - sent immediately after the view is
inserted into a view hierarchy.
-(void)didMoveToWindow - sent immediately after the view gets its
window property set.
-(void)willMoveToSuperview:(UIView *)newSuperview - sent
immediately before the view is added as a subview to another view;
newSuperview may be nil when you remove the view from its
superview.
-(void)willMoveToWindow:(UIWindow *)newWindow - sent immediately
before the view (or its superview) is added to a window; newWindow
may be nil when you remove the view from a window.

Look, viewDidAppear is method of UIViewController which represents moment when view of ViewController did appear and allows you to do declare what should happen.
UIView has no method like this. This comes from MVC pattern: controller is in this case UIViewController which controls changes, actions, etc., and view is just what controller shows.

Related

Button to trigger drawRect to redraw a view

I have a VC with a subview. At the subview level I have a button. I want the button to result in the view being updated.
The IBAction code is in the VC. All it currently does is toggle a BOOL value.
Back in the subview drawRect: method is the code that tests the BOOL and changes the view accordingly.
My observation is that when the button is pressed, the IBAction code runs, toggles the BOOL, but the drawRect: method doesn't get called.
I tried calling drawRect manually (not good practice and didn't work). I tried calling setNeedsDisplay but that too didn't work (couldn't figure out how to reference the subview, which is created programmatically).
Suggestions welcome.
IBAction code is trivial but included below:
- (IBAction)rotateWindowButtonWasPressed:(id)sender
{
// just flip the boolean here. Need to make the code change in drawRect: in top2View.m
if ([myGlobals gWinVertical])
[myGlobals setGWinVertical:NO];
else
[myGlobals setGWinVertical:YES];
// Hoping this would force a redraw, but bounds have to change...:(
//_top2ViewOutlet.contentMode = UIViewContentModeRedraw;
}
to issue a drawRect call, call setNeedsDisplay on the view you changed.. the OS will call drawRect on it and affected overlapping views automatically.
in your case I think
[self.view setNeedsDisplay]
Not sure what happened - there was an answer above that solved my issue but now it's gone? Anyway - the answer is to create the button programmatically and then I can include the method at the subview level where drawRect: is, and using setNeedsDisplay to repaint the screen. That vs. using IB and having the IBAction code at the VC level.

UITabBarController's tabs' rotation and willRotateToInterfaceOrientation propagartion

My app has four tabs: A, B, C and D. Their UIViewController are managed by UITabBarController. The app supports rotation, and so each view controller returns YES to shouldAutorotateToInterfaceOrientation.
Using springs and struts, most of the rotation is done automatically by iOS. However, tab A also requires further positioning, and it is done in its VC's willRotateToInterfaceOrientation method.
When the VC for tab A is selected and the screen is rotated, that VC receives a willRotateToInterfaceOrientation message (propagated by iOS from UITabBarController), and the resulting rotation is correct.
However, when the selected tab is B and the screen is rotated, A's willRotateToInterfaceOrientation is not called. Makes sense. But if I then select tab A, I get only the results of applying its springs and struts, without the post-processing done by its willRotateToInterfaceOrientation.
After struggling with this for a while, and after failing to find a solution online, I came up with the following. I subclassed UITabBarController and in its willRotateToInterfaceOrientation I call all the VCs' willRotateToInterfaceOrientation regardless of which one is the selectedViewController:
- (void) willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
if (self.viewControllers != nil) {
for (UIViewController *v in self.viewControllers)
[v willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
}
}
It works, but it looks like a hack, and my question is whether I was doing the right thing. Is there a way to tell iOS to always call a VC's willRotateToInterfaceOrientation before displaying it for the first time after a screen rotation?
The best way to handle custom layout is by subclassing UIView and overriding the layoutSubviews method. The system sends layoutSubviews to a view whenever its size is changed (and at other times). So when your view A is about to appear on screen with a different size (because the interface was rotated while view B was on screen), the system sends view A a layoutSubviews message, even though it doesn't send view controller A a willRotateToInterfaceOrientation: message.
If you are targeting iOS 5.0 or later, you can override the viewDidLayoutSubviews method of your UIViewController subclass and do your layout there, instead of subclassing UIView. I prefer to do it in my view's layoutSubviews, to keep my view-specific logic separate from my control logic.
It's also a bad idea to do layout in willRotateToInterfaceOrientation: because the system sends that message before actually changing the size of the view, and before the rotation animation block. It sends the willAnimateRotationToInterfaceOrientation:duration:, layoutSubviews, and viewDidLayoutSubviews messages inside the rotation animation block, so the repositioning of your subviews will be animated if the view is on screen during the rotation.

How to fix view retain cycle causing subview's dealloc to not be called?

I have a view controller which instantiates a bunch of UIButton subclasses and adds them to its self.view and also to a mutable array. These subclasses in turn have a retain property which points to another view. In most cases, the view property points back to the UIButton subclass' superview (the view controller's self.view to which they were added). But not always and not necessarily, which is why I am using this property and not the inherited superview one.
The problem I'm having is that when the view controller's dealloc does:
- (void)dealloc
{
[UIBUttonSubClassesArray release];
[super dealloc];
}
the UIButton subclasses' dealloc is not being called. So the additional release for the view property in these subclasses doesn't get called and, even when the view controller is dealloced, I'm leaking the view controller's view once for each of these UIButton subclasses.
But, if instead I make the subclasses' view property an assign, so that I need not call release in their dealloc, their dealloc does get called and even when there's no code referencing the view now, the app crashes.
Any ideas?
If your viewcontroller is not in the retain cycle, you can break the cycle in its dealloc (and in its viewDidUnload too) by setting your extra retain properties in your buttons to nil.
Although it is always preferable not to create retain cycles in the first place, I don't see enough clue in your question on how they should be avoided in your case.

Custom UIViewController viewDidUnload never gets called

I have a custom UIViewController subclass that handles all the view initialization by itself (it doesn't use nib). There is also another UIViewController subclass loaded from nib. Both are contained by UITabBarController.
When a memory warning comes, the first controller does receive notification, but viewDidUnload doesn't get called. The second controller also receives notification and it's viewDidUnload does get called.
I checked in didReceiveMemoryWarning, self.isViewLoaded is TRUE and self.view.superview is null.
Both controllers (their tabs) are invisible at the time the notification appears.
Is there something special a custom view controller should do to be unloaded in a result of memory warnings?
If you are subclassing UIViewContoller and you do not initialize it from a NIB, you need to subclass the -loadView method. Otherwise iOS assumes that the view cannot be unloaded / reloaded.
It would be sufficient to just add the following to your implementation:
- (void)loadView {
[super loadView];
}
I will try to find a documentation quote for that.
The documentation is unfortunately not very clear on this matter.
I would check out the documentation on the View Controller Lifecycle. Specifically, check out the section on what happens when the memory warning is received. If your custom view controller's view is the view on screen, the OS will not attempt to release this view. Is this view on screen when you're getting the memory warning? In the simulator, navigate to the nib-loaded view and simulate a memory warning, see if your custom view gets released then. Also, see if viewWillUnload is being called. And make sure that in any of these methods that you're overriding that you call super.

Looking to understand the iOS UIViewController lifecycle

Could you explain me the correct manner to manage the UIViewController lifecycle?
In particular, I would like to know how to use Initialize, ViewDidLoad, ViewWillAppear, ViewDidAppear, ViewWillDisappear, ViewDidDisappear, ViewDidUnload and Dispose methods in Mono Touch for a UIViewController class.
All these commands are called automatically at the appropriate times by iOS when you load/present/hide the view controller. It's important to note that these methods are attached to UIViewController and not to UIViews themselves. You won't get any of these features just using a UIView.
There's great documentation on Apple's site here. Putting in simply though:
ViewDidLoad - Called when you create the class and load from xib. Great for initial setup and one-time-only work.
ViewWillAppear - Called right before your view appears, good for hiding/showing fields or any operations that you want to happen every time before the view is visible. Because you might be going back and forth between views, this will be called every time your view is about to appear on the screen.
ViewDidAppear - Called after the view appears - great place to start an animations or the loading of external data from an API.
ViewWillDisappear/DidDisappear - Same idea as ViewWillAppear/ViewDidAppear.
ViewDidUnload/ViewDidDispose - In Objective-C, this is where you do your clean-up and release of stuff, but this is handled automatically so not much you really need to do here.
UPDATE: ViewDidUnload was deprecated in iOS 6, so updated the answer accordingly.
The UIViewController lifecycle is diagrammed here:
The advantage of using Xamarin Native/Mono Touch, is that it uses the native APIs, and so it follows the same ViewController lifecycle as you would find in Apple's Documentation.
This is for latest iOS Versions(Modified with Xcode 9.3, Swift 4.1). Below are all the stages which makes the lifecycle of a UIViewController complete.
loadView()
loadViewIfNeeded()
viewDidLoad()
viewWillAppear(_ animated: Bool)
viewWillLayoutSubviews()
viewDidLayoutSubviews()
viewDidAppear(_ animated: Bool)
viewWillDisappear(_ animated: Bool)
viewDidDisappear(_ animated: Bool)
Let me explain all those stages.
1. loadView
This event creates/loads the view that the controller manages. It can load from an associated nib file or an empty UIView if null was found.
This makes it a good place to create your views in code programmatically.
This is where subclasses should create their custom view hierarchy if they aren't using a nib.
Should never be called directly.
Only override this method when you programmatically create views and assign the root view to the view property
Don't call super method when you override
loadView
2. loadViewIfNeeded
If incase the view of current viewController has not been set yet then this method will load the view but remember, this is only available in iOS >=9.0. So if you are supporting iOS <9.0 then don't expect it to come into the picture.
Loads the view controller's view if it has not already been set.
3. viewDidLoad
The viewDidLoad event is only called when the view is created and loaded into memory but the bounds for the view are not defined yet. This is a good place to initialise the objects that the view controller is going to use.
Called after the view has been loaded. For view controllers created in code, this is after -loadView.
For view controllers unarchived from a nib, this is after the view is set.
4. viewWillAppear
This event notifies the viewController whenever the view appears on the screen. In this step the view has bounds that are defined but the orientation is not set.
Called when the view is about to made visible. Default does nothing.
5. viewWillLayoutSubviews
This is the first step in the lifecycle where the bounds are finalised. If you are not using constraints or Auto Layout you probably want to update the subviews here. This is only available in iOS >=5.0. So if you are supporting iOS <5.0 then don't expect it to come into the picture.
Called just before the view controller's view's layoutSubviews method is invoked.
Subclasses can implement as necessary. The default is a nop.
6. viewDidLayoutSubviews
This event notifies the view controller that the subviews have been setup. It is a good place to make any changes to the subviews after they have been set. This is only available in iOS >=5.0. So if you are supporting iOS <5.0 then don't expect it to come into the picture.
Called just after the view controller's view's layoutSubviews method is invoked.
Subclasses can implement as necessary. The default is a nop.
7. viewDidAppear
The viewDidAppear event fires after the view is presented on the screen. Which makes it a good place to get data from a backend service or database.
Called when the view has been fully transitioned onto the screen.
Default does nothing
8. viewWillDisappear
The viewWillDisappear event fires when the view of presented viewController is about to disappear, dismiss, cover or hide behind other viewController. This is a good place where you can restrict your network calls, invalidate timer or release objects which is bound to that viewController.
Called when the view is dismissed, covered or otherwise hidden.
9. viewDidDisappear
This is the last step of the lifecycle that anyone can address as this event fires just after the view of presented viewController has been disappeared, dismissed, covered or hidden.
Called after the view was dismissed, covered or otherwise hidden.
Default does nothing
Now as per Apple when you are implementing this methods you should remember to call super implementation of that specific method.
If you subclass UIViewController, you must call the super implementation of this method, even if you aren't using a NIB. (As a convenience, the default init method will do this for you, and specify nil for both of this methods arguments.) In the specified NIB, the File's Owner proxy should have its class set to your view controller subclass, with the view outlet connected to the main view. If you invoke this method with a nil nib name, then this class' -loadView method will attempt to load a NIB whose name is the same as your view controller's class. If no such NIB in fact exists then you must either call -setView: before -view is invoked, or override the -loadView method to set up your views programatically.
Hope this helped.
Thanks.
UPDATE - As #ThomasW pointed inside comment viewWillLayoutSubviews and viewDidLayoutSubviews will also be called at other times when subviews of the main view are loaded, for example when cells of a table view or collection view are loaded.
UPDATE - As #Maria pointed inside comment, description of loadView was updated
iOS 10,11 (Swift 3.1,Swift 4.0)
According to UIViewController in UIKit developers,
1. loadView()
This is where subclasses should create their custom view hierarchy if they aren't using a nib. Should never be called directly.
2. loadViewIfNeeded()
Loads the view controller's view if it has not already been set.
3. viewDidLoad()
Called after the view has been loaded. For view controllers created in code, this is after -loadView. For view controllers unarchived from a nib, this is after the view is set.
4. viewWillAppear(_ animated: Bool)
Called when the view is about to made visible. Default does nothing
5. viewWillLayoutSubviews()
Called just before the view controller's view's layoutSubviews method is invoked. Subclasses can implement as necessary. Default does nothing.
6. viewDidLayoutSubviews()
Called just after the view controller's view's layoutSubviews method is invoked. Subclasses can implement as necessary. Default does nothing.
7. viewDidAppear(_ animated: Bool)
Called when the view has been fully transitioned onto the screen. Default does nothing
8. viewWillDisappear(_ animated: Bool)
Called when the view is dismissed, covered or otherwise hidden. Default does nothing
9. viewDidDisappear(_ animated: Bool)
Called after the view was dismissed, covered or otherwise hidden. Default does nothing
10. viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator)
Called when the view is Transitioning.
11. willMove(toParentViewController parent: UIViewController?)
12. didMove(toParentViewController parent: UIViewController?)
These two methods are public for container subclasses to call when transitioning between child controllers. If they are overridden, the overrides should ensure to call the super.
The parent argument in both of these methods is nil when a child is being removed from its parent; otherwise it is equal to the new parent view controller.
13. didReceiveMemoryWarning()
Called when the parent application receives a memory warning. On iOS 6.0 it will no longer clear the view by default.
As of iOS 6 and onward. The new diagram is as follows:
Let's concentrate on methods, which are responsible for the UIViewController's lifecycle:
Creation:
- (void)init
- (void)initWithNibName:
View creation:
- (BOOL)isViewLoaded
- (void)loadView
- (void)viewDidLoad
- (UIView *)initWithFrame:(CGRect)frame
- (UIView *)initWithCoder:(NSCoder *)coder
Handling of view state changing:
- (void)viewDidLoad
- (void)viewWillAppear:(BOOL)animated
- (void)viewDidAppear:(BOOL)animated
- (void)viewWillDisappear:(BOOL)animated
- (void)viewDidDisappear:(BOOL)animated
- (void)viewDidUnload
Memory warning handling:
- (void)didReceiveMemoryWarning
Deallocation
- (void)viewDidUnload
- (void)dealloc
For more information please take a look on UIViewController Class Reference.
The methods viewWillLayoutSubviews and viewDidLayoutSubviews aren't mentioned in the diagrams, but these are called between viewWillAppear and viewDidAppear. They can be called multiple times.
Haider's answer is correct for pre-iOS 6. However, as of iOS 6 viewDidUnload and viewWillUnload are never called. The docs state: "Views are no longer purged under low-memory conditions and so this method is never called."
There's a lot of outdated and incomplete information here. For iOS 6 and newer only:
loadView[a]
viewDidLoad[a]
viewWillAppear
viewWillLayoutSubviews is the first time bounds are finalized
viewDidLayoutSubviews
viewDidAppear
* viewWillLayoutSubviews[b]
* viewDidLayoutSubviews[b]
Footnotes:
(a) - If you manually nil out your view during didReceiveMemoryWarning, loadView and viewDidLoad will be called again. That is, by default loadView and viewDidLoad only gets called once per view controller instance.
(b) May be called an additional 0 or more times.
Explaining State Transitions in the official doc: https://developer.apple.com/library/ios/documentation/uikit/reference/UIViewController_Class/index.html
This image shows the valid state transitions between various view ‘will’ and ‘did’ callback methods
Valid State Transitions:
Taken from: https://developer.apple.com/library/ios/documentation/uikit/reference/UIViewController_Class/Art/UIViewController Class Reference_2x.png
As per Apple's doc — Start Developing iOS Apps (Swift)
— Work with View Controllers — Understand the View Controller Lifecycle
viewDidLoad()—Called when the view controller’s content view (the top of its view hierarchy) is created and loaded from a storyboard.
…
Use this method to perform any additional setup required by your view controller.
viewWillAppear()—Called just before the view controller’s content view is added to the app’s view hierarchy. Use this method to trigger any operations that need to occur before the content view is presented onscreen
viewDidAppear()—Called just after the view controller’s content view has been added to the app’s view hierarchy. Use this method to trigger any operations that need to occur as soon as the view is presented onscreen, such as fetching data or showing an animation.
viewWillDisappear()—Called just before the view controller’s content view is removed from the app’s view hierarchy. Use this method to perform cleanup tasks like committing changes or resigning the first responder status.
viewDidDisappear()—Called just after the view controller’s content view has been removed from the app’s view hierarchy. Use this method to perform additional teardown activities.

Resources