How to control when UIView should display updates - ios

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];

Related

UIView subviews updating visually but interact as though in previous state

I'm creating a UIViewController that houses some of RosyWriter's functionality, which I'm reworking to create a video recorder. The screen contains a title, a clipped-to-bounds subview that contains the AVCaptureVideoPreviewLayer (so the sublayer CALayer of the video content is added into that subview, so I think it's quite deep-nested), and two buttons acting like a toggle button - in the form of Start and Stop buttons placed in the Storyboard for the UIViewController.
The Start button works fine, even though the video's preview layer is on screen and showing the camera. When I start recording, though, I switch the buttons round, making the Start button hidden and the Stop button hidden=false.
The start button works - this is pressed when the video preview is on-screen and updating, but the actual recording (grabbing samples in buffers and writing them to a file - nothing UIKit related as far as I can see) has not started.
When the video recording is active, with the Stop button showing and the Start button hidden, the visible stop button isn't pressable, but the hidden start button can still be pressed.
I've tried moving the Stop button above the UIView containing the video, in case the CALayer or something else stretches outwith the clipped UIView bounds. This doesn't help. The stop button always acts as though it's not enabled - but it is enabled, and nothing appears to overlap the button. The button works fine if the UIView containing the video (which, I'll reiterate, is lower than the broken button) is never shown.
Can anyone think why this'd happen? I immediately thought about setNeedsLayout and setNeedsDisplay and tried just throwing some of those in, because it's almost as though the view had updated with my request to hide or show buttons, but an interaction layer hadn't updated.
Thanks
I now think this is due to my ignorant use (or misuse) of dispatch queues. I'm still not sure how I was able to interact with buttons that aren't showing - perhaps it's because their own state change responds in part, immediately, and the rest can't take place except on the main queue (the visual refresh, for example).
So I solved the problem, in a sense, by forcing a particular asynchronous delegate method to be called on the main queue. This doesn't affect the operation of this particular step in my recording process.

Prepare UIImage with lots of layers on background thread

I have the problem that I have some really big UIScrollView and tons of images loaded on it as user scrolls. Images are stored on the device, however I receive information from server what to display on particular part of UIScrollView. When user scrolls a bit I need to show images at new position as I cannot afford to draw whole UIScrollView with images at startup. For the background I had one relatively small image which I move throughout the View. But the problem is that on top of that background I should draw a lot of UIImage objects(about 300-400) which are not particulary bih however are separeted on layers(one image on top of other on top of other etc.). Blocking scrolling while drawing is NOT an option.
Now I'm trying to decide which approach will suite my best:
Add all needed images to UIView on background thread and then just add UIView to ScrollView on main thread(which hopefully wont take long). Here when scroll somewhere I will need to calculate and create new UIView with objects and position it next to existing and eventualy to remove first UIView with all objects and layers when user continues to scroll in some direction.
Combine all layers in image with CoreGraphics and present them as objects with already decided layers. In this way I can remove specific object(image) from scroll view. When user scrolls I just create new objects and add them to view as full objects, and can remove objects when user scrolls enough instead of removing whole view. The problem here is adding multiple objects to UIScrollView on main thread, however when they are combined they won't be more than 15-20 objects.
My biggest concerns are performance and threading. As I said I cannot block main thread(or let's say cannot do this for a time that user will notice) and cannot combine images at my graphics department as they have tons of variatons which are decided at runtime. That's why I'm thinking of a way to prepare data on background thread and just really fast add it on main thread instead of preparing and adding it on main thread(which will block UI).
Every help will be greatly appriciated!
Regards,
hris.to
Look at using CATiledLayer for a UIView backing. It is was designed for this.
I have a map that has one UIView in a UIScrollView and the UIView is sized to the full size of the entire map. The CATiledLayer handles when to draw each tile of the view.
Ok, so I'm writing here just to let you know how I fix my issue.
Main problem was that I was moving a background picture while scrolling(so I don't load an enormous file) and while doing that I was fetching information from server and try to draw on the same tiles which makes system crash with well known crash:
CALayer was muted while enumerated
I was depending on performSelector method along with #synchronized but it turns out that this is not effective and sometimes two threads(main UI thread and background thread) were trying to change same tiles on screen. So basically what I did is to change background fetching and drawing from:
[self performSelectorOnBackgroundThread]
to using concrete background thread, to which I store reference:
#property(nonatomic, strong) NSThread* backgroundThread;
And now each time I need to load new tiles or I need to move background, I'm cancelling this thread(and make sure it's cancelled) before start any other operation. Also there was a little problem when switching between view with this thread and other views, as my thread hangs, and I needed to set a timeout:
[_backgroundThread cancel];
int currentAttempts = 0;
while( [_backgroundThread isExecuting] )
{
if( currentAttempts == MAX_ATTEMPTS_TO_CANCEL_THREAD )
{
//make sure we dont hang and force background
_backgroundThread = nil;
return;
}
[_backgroundThread cancel];
currentAttempts++;
}
In my 'scrollViewDidScroll' however, I didn't use the recursion as this results in slight UI blocks while scrolling on older devices(such as iPhone 4) which is unacceptable. So there I basically just cancel the thread and hope to get cancelled quick enough(which with dozens of tests appears to be working):
- (void)scrollViewDidScroll:(UIScrollView*)scrollView
{
[_backgroundThread cancel];
//move background around
[self moveBackground];
}
Downside of this approach is that you need lots of check in your background thread as calling 'cancel' to it won't actually cancel anything. As per apple docs it'll only change isCancelled state of your thread and you are responsible to make this thread quit in basically the same way as it'll quit normally(so the system has a chance to cleanup after your thread):
if( [_backgroundThread isCancelled] )
{
return;
}
Regards,
hris.to

How to emulate the timing of highlighting the background image on a UIButton

I've created a custom button because I wanted to be able to compose it of multiple different images. It actually subclasses UIControl instead of UIButton. This led to the issue of highlighting the images while it was being tapped.
So, I followed the advice in this question by creating a category on UIImage to emulate the highlighting of a standard UIButton: How to implement highlighting on UIImage like UIButton does when tapped?
I'm triggering the image tinting based on the UIControlEventTouchDown, UIControlEventTouchUpInside, and UIControlEventTouchUpOutside events.
This mostly works, except the timing is a bit off. With a standard UIButton, no matter how long or short the user taps down, the highlighting always happens, but with my implementation, if the user taps very quickly (which is most times), the highlighting doesn't happen.
I'm assuming this may be because the screen isn't getting re-drawn between the time the user taps down and up, but I'm not totally sure.
What I've tried:
Calling setNeedsDisplay right after swapping out the image - doesn't help
Over-riding touchesBegan and touchesEnded and putting the image swapping code there - doesn't help
Executing the image swapping code asynchronously inside a dispatch_async call - doesn't help
At this point, the only other thing I can think of is to set up a timer that manually fires off the image change after a slight delay if it detects that the user hasn't pressed down for longer than a certain time period.
This feels wrong, and I'm wondering if there's a better way to achieve this. Is there some other event I should be over-riding?

Detect when UIView/UILabel redraws itself?

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.
}

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