Is there anyway to get the setContentOffset animation to happen immediately instead of waiting until the app returns to the main run loop? I tried setting the animation property to NO and nesting inside of an animation block but it still waits until returning to the main run loop. I've also tried using a sub method to perform the animation. My problem is I perform some heavy work after setting the contentOffset so the scroll view waits until this work is complete to animate the setting of the content offset so it appears to lag for a second.
I moved the heavy work to scrollViewDidEndAnimation and it resolved my issue. The timer didn't work because my heavy work relies on the content offset position after the scrolling animation is finished and using the timer couldn't assure an accurate offset.
No drawing will happen until the run loop gets time. If your heavy work does not need to be on the main thread then kick off another thread, otherwise schedule a timer with a time interval of 0 to perform the heavy work after the scroll view draws.
Related
I have a ViewController with a few buttons and it also has a child view that has a sublayer added to it. Its size is equal to the size of the ViewController's main view.
I used self.view.layoutIfNeeded() on viewDidLoad() and also dispatched the sublayer to the main dispatch with DispatchQueue.main.async{...}. While the sublayer gets drawn immediately, the buttons take anywhere from 10-15 seconds to appear.
What gives?
I used self.view.layoutIfNeeded() on viewDidLoad()
Well, don't. The view is not even in the interface yet. Layout will take place at the proper time; don't cause trouble by trying to shortcircuit things.
and also dispatched the sublayer to the main dispatch with DispatchQueue.main.async{...}. While the sublayer gets drawn immediately, the buttons take anywhere from 10-15 seconds to appear.
Despite what you say about the main thread, this sort of delay is always due to a threading issue: you are trying to talk to the interface off the main thread, or doing some sort of synchronous networking. You have shown no code, so no more can be said, but a threading issue is certainly the cause.
I have a UISlider in my table view's reusable cell. As soon as the value changes, a custom method valueChanged() gets called, which triggers the table view's reloadData() method.
The reloadData() calls cellForRowAtIndexPath and as soon as the cell in which the slider was changed gets loaded, it animates two constraints which should make it look as if a button is sliding in from the right of the cell.
Basically, the effect I'm trying to create is that as soon as the user starts sliding the UISlider the animation takes place and the button appears. Here's the catch though: the UISlider's isContinious property is set to true to make sure the animation happens right away, and doesn't get delayed until the user takes his finger of the slider. Setting isContinious to true, however, makes it that reloadData() (which was inside my custom valueChanged()) gets called A LOT OF TIMES with every little slide the user commits. After doing some research, and trying out a lot of debugging tactics, I now know that as soon as reloadData() gets called, any running animations (which are still animating at the time reloadData() gets called) get interrupted and just move to the state it's supposed to be in after the animation is done.
So to recap the scenario:
1) slider moves, 2) valueChanged() triggers reloadData(), 3) animation starts to update layout, 4) reloadData() immediately gets called again ruining the animation which started in step 3.
Everything is happening so fast when isContinious is set to true, so the animation doesn't get the time it needs to complete before reloadData() gets called again. This ruins the animation and makes it look as if the button is just appearing on the screen without animation, even though I know UIView.animate(withDuration:) DOES get called and does its job normally. I was wondering if there's a way to refresh a table without calling reloadData(), so basically an alternative to this method (I've already tried reloadCellAtIndexPath and this has the exact same effect). If not, is there a way to make absolutely sure that an animation finishes without being interrupted by any other layout updating method?
If the problem is the refresh kills the animation you can defer the reloadRowsAtIndexPaths call until the animation has finished executing. Normally I would say call it in the completion handler of your animation block but it seems like your value changed may be firing off more than one animation. Instead when you start the animation also start a timer that will run a block to execute the reload. If you start a new animation, invalidate the old timer and make a new one. the reload will only execute when the final animation is done.
var timer = Timer()
func valueChanged() {
timer.invalidate()
timer = Timer.scheduledTimer(withTimeInterval: animationDuration, repeats: false) { _ in
//Call reload here
}
//Animate here
}
I am optimizing a transition that seems to be slow on my device. I am pushing one UIViewController from another when a UITableView's row is selected. There is a noticeable pause after row selection and before the new view is pushed.
Some logging indicates that all of my code is reasonably quick, from row selection until the pushed controller's viewWillAppear. But then the time between viewWillAppear and viewDidAppear is logged at around 0.7 seconds.
The transition itself (I believe) should only take 0.3 seconds. What could be accounting for the remainder?
I am testing on an iPhone 4, so I'm not expecting the snappiest performance. But I should be able to match the same performance of other similar apps on the same device, no?
I had a similar question a few weeks ago, and I wrote a blog post about what I found:
http://bradbambara.wordpress.com/2014/07/31/object-life-cycle-uiviewcontroller/
The TL;DR version is that iOS will:
perform the layout of your new scene
perform the transition to your new scene (if it's an animated transition)
...so my guess is the delay could be caused by an especially long transition, or if you're doing any performance-intensive work in your layout code.
The transition itself (I believe) should only take 0.3 seconds. What could be accounting for the remainder?
Resources are usually consumed in the following methods: drawRect:, layoutSubviews, viewDidLoad, viewWillAppear:. Also, loading from NIB may require quite much time.
After viewWillAppear:, iOS will make a snapshot of the new (and probably current) view to perform smooth animation between two screens. So make sure that drawing and layout code for both controller views is fast enough.
I'm using a UIScrollView to display a custom UIView. When the user drags a finger across the UIScrollView, there is a noticeable delay before the display begins updating. If the user keeps touching the screen, the UIScrollView becomes very responsive after a short time. Subsequent attempts to scroll result in the same initial delay, followed by high responsiveness. This delay seriously affects the usability of the view and I would like to get rid of it.
In a test project I have written to try to get to the bottom of this issue, I have only been able to partially replicate the behaviour. The first time that the user scrolls is exactly the same - however any subsequent attempts to scroll are responsive straight away.
I have tried both setting delaysContentTouches = NO and subclassing UIScrollView so that touchesShouldBegin returns NO as suggested in multiple places online, but neither has worked.
I'm using MonoTouch on iOS 4.3, but Objective-C answers are fine. I would post code to help illustrate the issue, but since I have been unable to narrow down the problem this would be well over 1000 lines. Hopefully this is enough info to get a solution.
Does anyone know what might be causing this delay, and how I can get rid of it?
Some general suggestions for improving scrolling performance.
Have your scrolling views rasterize offscreen:
myView.layer.shouldRasterize = YES;
Set that property for each sub-view on the scrollview - do not set it for the children of those sub-views or you just eat up memory that way.
If your scrolling views do not need compositing, make sure you turn that blending off:
myView.opaque = YES;
Test using the simulator by leveraging these two features that appear on the Debug menu of the iOS Simulator:
Color Off-screen Rendered
Color Blended Layers
If that doesn't address your problem, and you have implemented UIScrollViewDelegate, double-check to make sure you are not doing anything time consuming in those methods - for example, based on your description, you might be doing something in scrollViewDidScroll, scrollViewWillBeginDragging, or scrollViewWillBeginZooming and if you are, optimize that so it happens before scrolling even begins. Also, make sure you're not doing anything in touchesBegan.
I suspect what is happening is there is some kind of interaction enabled in the content of your scroll view.
The system does not know if the initial touch down is going to be a tap on one of the subviews or a drag on the scroll view, therefore is causing a delay while it waits to see if you are going to lift your finger.
What are the subviews of the UIScroll view?
As an experiment set all the subviews of the UIScrollView to have userInteractionEnabled = NO, this will not be what you want, but its just a test. Is should scroll fine after this, otherwise I am wrong.
I'm using UITableView's selectRowAtIndexPath:animated:scrollPosition to slide some of my UITableViewCells into place when a user selects them. When the animation completes, I want to display some additional metadata over the cell. The problem I'm having is that I can't seem to figure out a way to determine whether or not the UITableView will actually scroll, so that my metadata drawing routine can be called (in scrollViewDidEndScrollingAnimation). If the position of the cell is exactly where it should be when a user selects one, then no animation is needed, and scrollViewDidEndScrollingAnimation never gets called. Is there an easy way to determine if the UITableView doesn't need to scroll, or do I need to do all of these calculations manually at the time when I call selectRowAtIndexPath? I can't help but think that I'm missing some easy way to make this determination.
(Note: One reason I'd prefer not to do the calculations myself is that my rows are of different sizes, and determining if the scroll offset is positioned where it should be is a bit of a hassle)
One way is to use delayed performance. Store the current contentOffset. Do the selection with selectRowAtIndexPath:animated:scrollPosition: and then immediately call performSelector:withObject:afterDelay: with a very small delay (0.1, say). In the method you call with performSelector:..., ask for the current contentOffset again. If it hasn't changed, I think you may assume it isn't going to - we are not going to scroll! So you may proceed with your metadata display now. If it has changed, then do nothing and let your scrollViewDidEndScrollingAnimation take care of it.
I know this seems sort of skanky, but I find myself using delayed performance a lot to work around automatic animations of this sort.