Last time I encountered this issue, it was hard to find the reason. We don’t know much about what actually UI Tests system waits for in such cases. There are rumours over internet that activity on main thread is what it looks for, but it’s not always the case. Let’s go over few tips that may help.
Time Profiler
This one would help if activity on Main Thread prevents app from becoming idle. Even though it’s not always the reason, it probably counts for majority of cases. Just select the region of activity that you expect to be idle, and see all the unexpected calls. “Separate by Thread” Call Tree option may be helpful. Note that sometimes Main Thread is called Unnamed Thread for some reason.

Flash Updated Regions
If main thread is not the reason, next thing that comes to mind is Core Animation, which happens in a separate process. There are probably some Xcode Instruments that may highlight CA activity, but there is also another helpful Xcode feature: Xcode Menu > Debug > View Debugging > Flash Updated Regions.
Flash Updated Regions makes it easy to see what parts of your view are actually being updated.
Apple doc

Option is available only when running on device, it is greyed out when running on device. Once enabled, iOS will blink yellow all the views being updated.

Watch out for “unfinished” animations
If animation is considered not completed, the app considered not idle. I don’t know how they track such stuff, maybe just an integer that increments when animation some animation API called start()
, and decrements when it’s asked finish()
.
As an example, if you use UIViewPropertyAnimator
or UIViewAnimating
, and you use fractionCompleted
without ever stopping and finishing the animation, such animation will be considered active, even if fractionCompleted is set to 1.0.
The doc on fractionCompleted says:
You can update the value of this property only while the animator is paused.
Th doc on pauseAnimation() says:
Calling this method on an inactive animator moves its state to
UIViewAnimatingState.active
… It is a programmer error to call this method while the state of the animator is set toUIViewAnimatingState.stopped
.
So if you want animator’s ability to scrub animation and still expect app to idle, you’ll probably need some tricky hack to achieve that.