Detect when UIView/UILabel redraws itself? - ios

I have an app that fetches calendar events and displays data to the user. I'm getting some weird behavior when trying to update my labels.
I can fetch the calendar data just fine but when that gets done, my problem is that according to NSLog my label.text property has already changed, but it's another 4-8 seconds before the view gets redrawn.
Therefore, I'm trying to detect when the label gets redrawn, not when it's .text property changes so I can hide a progress view at the same time the data is populated in the labels.
I have already tried setNeedsDisplay and setNeedsLayout on self.view and the labels themselves. after the .text property of the labels has changed - doesn't work.
So unless I'm completely missing something about using setNeedsDisplay (which I understand only updates on the next redraw anyway), my question is, how do I detect when the UILabel and/or the UIView redraws itself?
How my app is setup:
I've been stuck on this for about 3 weeks.

Make sure setNeedsDisplay is being called on the main thread, using performSelectorOnMainThread:withObject:waitUntilDone:, for example:
[view performSelectorOnMainThread:#selector(setNeedsDisplay)
withObject:nil
waitUntilDone:NO];

Quote apple develop document :
The view is not actually redrawn until the next drawing cycle, at which point all invalidated views are updated.
maybe your main thread are blocking by other things , such as deal with many complex calculations
eg:
- (void)testMethod
{
myLabel.mytext = #"aaaa";
[myLabel setNeedsDisplay];
// some complex calculations
// the quickest , it will run finish the method then redraw.
}

Related

How to control when UIView should display updates

I have a UIButton with an image as a background.
Because of the way i’ve structured the flows in my program, there’s a certain moment when I have to update a couple of properties sequentially (including the background image) which causes the button to blink for a millisecond.
I want to get rid of that blinking of course, and that’s the part that's bugging my brain since a couple of days:
I don’t know how to prevent these updates from displaying until I’m ready to display them.
Ideally I'd like to do something like:
// the view "freezes" in its current state
[UIView stopDisplayingUpdates];
// many updates happen here
// without being displayed
// now everything is ready and the view should be repainted
[UIView refreshDisplay];

If I call setneedsdisplay frequently, how often will the drawrect be called? why is that?

In my case, the drawRect: will not be called immediately after every single setNeedsDisplay is called. For example, I think the following code is same as my case.
for (int i = 0; i < 100; ++i)
{
[self setNeedsDisplay];
}
From the documentation:
When the actual content of your view changes, it is your responsibility to notify the system that your view needs to be redrawn. You do this by calling your view’s setNeedsDisplay or setNeedsDisplayInRect: method of the view. These methods let the system know that it should update the view during the next drawing cycle. Because it waits until the next drawing cycle to update the view, you can call these methods on multiple views to update them at the same time.
drawRect: will only be called in time for the next frame to be drawn, which means your entire loop will result in drawRect: only being called once at the next rendering iteration. This saves unnecessary computation as it avoids drawing frames that will never be displayed on the screen. It also lets you make multiple changes in separate places in your code, each time notifying the view that a refresh is needed, without losing performance, since calling setNeedsDisplay only tells the drawing system that a redraw is needed in the next frame; it doesn't force the immediate rendering of a frame that might never be displayed on the screen.
setNeedsDisplay only marks the view as needing to be displayed again. The actual drawing call is done in the next runloop iteration of the main thread, once. This allows the drawing system to do some optimizations and "combine" repeated calls to setNeedsDisplay.

iOS UIView derived view instance drawRect being called but not updating on screen?

Was hoping someone could help with this please? I've scanned through the forum and apple docs and can't find anything that matches my problem exactly.
I'm trying to animate a custom 'Composite' UIView derived class (with overidden drawRect) by changing a custom colour property (not one of the standard view properties like centre, alpha, backgroundColour etc), calling setNeedsDisplay and redrawing the view using drawRect over X seconds duration to produce a slow colour change from say, red to blue.
At the moment, all Composite::drawRect does internally is clear the context (the view has a default background colour set to clear) and fill the view using the colour provided. This works fine the first time it's called - the view appears on screen in the correct red colour.
My problem is that when I try to animate the view by updating the colour property, even though drawRect is being called everytime I call setNeedsDisplay and the colour being fed into the view instance is correct the view doesn't update on screen until I stop calling setNeedsDisplay (or rather, stop updating the custom view colour property). It's like every call to setNeedsDisplay pushes the view to the back of a list so it doesn't get updated on screen until it stops getting called (even though drawRect is being called).
For example - trying to change the colour from red to blue over 10 seconds, the view stays red for 10 seconds, then turns to blue in a single frame at the end of the property changing (and presumably when I stop calling setNeedsDisplay) rather than a slow fade between the two over the time duration.
Any ideas what's going on? I'm guessing I need to animate this using apple's own animation property stuff but I'd really prefer not to - I'm trying to make this cross platform if possible and I don't see why my own method shouldn't work.
The basics:
In case it matters, my composite view instance is a subview of an UIImageView. (I know imageview drawRect doesn't get called but I can't imagine this is a problem for it's subviews - after all, my Composite::drawRect is definitely being called).
I'm setting the right colour on my view instance - I've debug printed this to be sure - so the colour provided to the view goes from red to blue over however many seconds / frames.
I'm setting setNeedsDispay and drawRect is being called every time - again, debug printing confirms this.
This is happening over multiple frames and the app goes through the drawing update at the end of every frame - I.e I'm not blocking the render thread or anything - the app runs fine, it just doesn't update the view on screen even though its drawRect is being called, until I stop manipulating the property. Someone mentioned a similar problem where they were blocking the render thread in a single frame but I'm definitely not doing anything like that.
I've tried messing around with the following but they all produced the same results (i.e. view changed from red to blue in a single frame rather than a slow change over).
Updating the colour every frame but only calling setNeedsDisplay on my view every Xth frame (5, 10, 20, 30 etc)
Updating the colour every frame but not calling setNeedsDisplay unless the corresponding drawRect call from the last setNeedsDisplay has happened (i.e. set a flag to false in SetColour func, set same flag to true in drawRect, test against it when decided to call setNeedsDisplay) - same results.
setting the view bounds with ContentMode set to redraw to force an update - same results
tried calling setNeedsDisplay on the parent image view too - no change.
I'm guessing that this just isn't possible - can anyone explain why its not updating the v view onscreen even though drawRect is being called? Any ideas how I should do this? I don't think I would be able to do what I want to do if I tried animated one of the default view properties using apple's internal animation system but if I've got to use that....
Thanks!
:-)

When do I need to call setNeedsDisplay in iOS?

When creating an iOS app, I'm confused as to when exactly I need to call setNeedsDisplay? I know that it has something to do with updating/redrawing the UI; however, do I need to call this every time I change any of my views?
For example, do I need to call it:
After programatically changing the text in a text field
When changing the background of a view?
When I make changes in viewDidLoad?
How about in viewDidAppear?
Could someone give me some general guidelines regarding when to use this method?
You should only be calling setNeedsDisplay if you override drawRect in a subclass of UIView which is basically a custom view drawing something on the screen, like lines, images, or shapes like a rectangle.
So you should call setNeedsDisplay when you make changes to few variables on which this drawing depends and for view to represent that change , you need to call this method which internally will give a call to drawRect and redraw the components.
When you add an imageView or a UIButton as a subview or make changes to any subview, you need not call this method.
Example:
You have a view that shows a moving circle, either you touch and move it, or may be timer based animation.
Now for this, you will need a custom view that draws a circle at given center and with given radius.
These are kept as instance variables which are modified to move the circle by changing its center or make it bigger by increasing radius of it.
Now in this case either you will modify these variables(centre or radius) in a loop and timer Or may be by your fingers in touchesEnded and touchesMoved methods.
To reflect the change in this property you need to redraw this view for which you will call setNeedsDisplay.
You only really need to call -setNeedsDisplay on UIView subclasses that draw their contents with -drawRect:.
For labels and other standard controls, changing the text will automatically cause the label to redraw so you don't need to do this yourself.
setNeedsDisplay: should be called when you want to refresh your view explicitly. It just sets an internal flag, and the iOS UI system will call drawRect: at an appropriate time later.
It sounds like it should be always called when you updating any property which may change the presentation. But it's not. Almost all the standard UI controls already handled that. I believe whenever you modify the properties of standard UI components (views), setNeedsDisplay: would be triggered internally, and the affected region will be redrawn. (In all the situations you listed)
However, if you create your own view, implement its own drawRect:, and want to update that when something has been changed, you must call setNeedsDisplay: explicitly.
I think #Amogh Talpallikar make it clear. And I just wanna discuss one thing more.
In the fact that, you should avoid override drawRectunless you really need it because it can cause bad performance. You can refer this https://yalantis.com/blog/mastering-uikit-performance/
If you only wanna change frame, position of buttons, labels, ... you can call setNeedLayout or layoutIfNeeded
You will call setNeedDisplay when you are changing the property on which your view custom drawing depends. It will explicitly call drawRect: method forcefully.

UIViewController viewDidLoad vs. viewWillAppear: What is the proper division of labor?

I have always been a bit unclear on the type of tasks that should be assigned to viewDidLoad vs. viewWillAppear: in a UIViewController subclass.
e.g. I am doing an app where I have a UIViewController subclass hitting a server, getting data, feeding it to a view and then displaying that view. What are the pros and cons of doing this in viewDidLoad vs. viewWillAppear?
viewDidLoad is things you have to do once. viewWillAppear gets called every time the view appears. You should do things that you only have to do once in viewDidLoad - like setting your UILabel texts. However, you may want to modify a specific part of the view every time the user gets to view it, e.g. the iPod application scrolls the lyrics back to the top every time you go to the "Now Playing" view.
However, when you are loading things from a server, you also have to think about latency. If you pack all of your network communication into viewDidLoad or viewWillAppear, they will be executed before the user gets to see the view - possibly resulting a short freeze of your app. It may be good idea to first show the user an unpopulated view with an activity indicator of some sort. When you are done with your networking, which may take a second or two (or may even fail - who knows?), you can populate the view with your data. Good examples on how this could be done can be seen in various twitter clients. For example, when you view the author detail page in Twitterrific, the view only says "Loading..." until the network queries have completed.
It's important to note that using viewDidLoad for positioning is a bit risky and should be avoided since the bounds are not set. this may cause unexpected results (I had a variety of issues...)
This post describes quite well the different methods and what happens in each of them.
currently for one-time init and positioning I'm thinking of using viewDidAppear with a flag, if anyone has any other recommendation please let me know.
Initially used only ViewDidLoad with tableView. On testing with loss of Wifi, by setting device to airplane mode, realized that the table did not refresh with return of Wifi. In fact, there appears to be no way to refresh tableView on the device even by hitting the home button with background mode set to YES in -Info.plist.
My solution:
-(void) viewWillAppear: (BOOL) animated { [self.tableView reloadData];}
Depends, Do you need the data to be loaded each time you open the view? or only once?
Red : They don't require to change every time. Once they are loaded they stay as how they were.
Purple: They need to change over time or after you load each time. You don't want to see the same 3 suggested users to follow, it needs to be reloaded every time you come back to the screen. Their photos may get updated... you don't want to see a photo from 5 years ago...
viewDidLoad: Whatever processing you have that needs to be done once.
viewWilLAppear: Whatever processing that needs to change every time the page is loaded.
Labels, icons, button titles or most dataInputedByDeveloper usually don't change.
Names, photos, links, button status, lists (input Arrays for your tableViews or collectionView) or most dataInputedByUser usually do change.

Resources