So I know there's a lot of questions regarding this, but so far as I can tell this is a unique situation so I figured I would post it. Hopefully, this will add some info that may finally give us an answer as to why this is happening to us. I'm getting the error: wait_fences: failed to receive reply: 10004003, when my device rotates. The animation of my views are initiated from:
- (void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
I'm only getting the error on the new iPad 3. I've used the exact same program on an original iPad and iPhones as low as a 3GS. They all don't get the wait_fences error and they all rotate faster than the iPad 3 does.
I use Core Graphics almost exclusively to draw the views. I also ensure they're redrawn on a resize so I don't get pixilated views. If I disable the redraw on resize, I don't get this error (but I get stretched views). If I disable core graphics drawing altogether I don't get the error (but, of course, I get black views).
I used the Time Profiler and found out that the hangup was primarily in drawing gradients:
I have altered the code to fill rather than draw gradients and that does alleviate the problem. I would say that the gradients are the problem except I do these animations in other situations (other than in response to rotation) and it works just fine.
I would also like to note that I've paid special attention to making sure I only animate views that are actually on the screen. I know that animating views off screen can sometimes cause this error to occur.
I have not included the animation code
Any ideas as to why this is happening? Especially since it's only happening on the iPad 3?
For those who will ask, this is the code that performs the animation. It will normally be wrapped in a UIView Animation Block.
- (void) setFramesForFocusView:(CustomControl *)focusView atX:(CGFloat)x showInput:(BOOL)showInput{
CGSize bSize = self.bounds.size;
CGRect fRect = focusView.frame;
fRect.size.width = bSize.width;
CGRect iRect;
if (focusView.inputViewIsSystemKeyboard){
if (_keyboardRect.origin.y < 0 || _keyboardRect.origin.y >= CGRectGetMaxY(self.bounds) || CGRectIsEmpty(_keyboardRect) || CGRectGetMaxY(_keyboardRect) > CGRectGetMaxY(self.bounds)) return;
iRect = _keyboardRect;
} else {
iRect = (focusView.inputUIView && showInput) ? CGRectMake(0, bSize.height / 2, bSize.width, bSize.height / 2) : CGRectZero;
}
CGRect iaRect = focusView.inputAccessoryUIView.frame;
CGFloat availableFieldHeight = iRect.origin.y - iaRect.size.height;
iRect.size.width = bSize.width;
iaRect.size.width = bSize.width;
if (!showInput){
iRect.origin.y = bSize.height;
}
iaRect.origin.y = iRect.origin.y - iaRect.size.height;
iRect.origin.x = x;
iaRect.origin.x = x;
focusView.inputUIView.frame = iRect;
focusView.inputAccessoryUIView.frame = iaRect;
if (focusView.expandInput){
fRect.origin.y = 0;
fRect.size.height = availableFieldHeight;
} else {
if (focusView.labelPlacement != LabelPlacementTop && focusView.labelPlacement != LabelPlacementBottom){
fRect.size.height = _currentView.storedFrame.size.height + [focusView.label.text sizeWithFont:focusView.label.font].height;
}
fRect.origin.y = availableFieldHeight - fRect.size.height;
}
if (fRect.size.height > availableFieldHeight){
fRect.origin.y = 0;
fRect.size.height = availableFieldHeight;
}
fRect.origin.x = x;
[focusView setLabelPlacement:LabelPlacementTop toFrame:fRect];
}
Well that was quick. #RobNapier was correct in that it was a timing issue. I commented out my animations and wow there were a lot of other views animating behind there! Even though I was explicitly animating only on-screen views, there was another ViewController receiving the rotation events behind my views without my... uh... knowledge? I mean, I should know right? I wrote the code. I didn't realize at first because my set of views was covering the entire screen. Unfortunately this will require a lot of rewriting. I use Custom Container Controllers and now I see I need to reconsider my implementation. A lot of stuff is getting needlessly rotated/animated. But wow...that answered a lot of performance questions....
Update
So I had thought that the problem I was facing had to do with the extra views being animated by other view controllers. However, while this is technically true, it's not as true as I thought or in the way I thought. I made absolutely sure that no other views were animated by removing the entire root view hierarchy from the window and replacing it with only the view controller I'm wanting to have rotated. This definitely helped, but not completely. Really, it just 'lowered the bar' so that it was less likely for me to get the 'wait_fences' error. I still discovered I was getting the error though in certain situations.
I believe the problem I'm having is my use of a UIScrollView. My implementation has a variable number of of subviews that it manages. The specific view is my own custom implementation of a UIPickerView, so as you can imagine, the number of views it manages can become quite large. I've discovered that if those subviews become too numerous, I start getting the 'wait_fences' error.
So it appears that: If UIScollView is animated, it will animate all of it's subviews, even if those subviews aren't on screen. This is important. I rather suspect that a lot of people who are struggling with this error may not realize this. Each one of those off-screen subviews are pushing you ever closer to hitting the 'wait_fences' error. The solution in my case is "simple": I'm going to convert my UIScrollView to a UITableView. That'll mean rewriting a lot of code, but at least I know that off-screen subviews will be removed from the screen and thus not animated.
I also noted something else: Core-Graphic Gradients hit you hard. I can animate a lot more off-screen views if they don't use gradients. Of course, I love gradients and I'm not willing to give them up (which is why I'm rewriting my PickerView) but it is interesting and important to note.
Update 2
Finished rewriting my UIScrollView as a tableView and that seems to have done the trick. I'm getting no lag and no wait_fences error when I rotate the screen.
Update 2
So yeah, it's a lot easier to hit the wait_fences error on the iPad 3 than any other iPad/iPhone. I've gone through all my code making sure I never animate anything that's not on screen, so that issue's resolved. I still get the wait_fences error on the iPad 3 when I use 'heavy' drawing routines. Stuff I've found that makes me hit it:
Gradients: gradients really make the CPU work on the retina screen.
Transparency: if your view isn't opaque, the CPU works hard to figure out the transparent areas of the view.
Transparent Colors: not the same as view transparency. This is layering transparent colors/gradients over the top of each other to gain an 'effect', like gloss, highlights whatever.
Textures: I find using textures makes it a little more likely to hit the wait_fences error, but nothing near like what gradients/transparency does.
Related
I have been working on an iPad app that performs animations on very large images (full screen images that can be zoomed at 2x and still be retina quality). I have spent a lot of time getting smooth transitions when zooming and panning. When running the app on iOS7 however, the animations become really jerky (slow frame rate).
Further testing shows that it is the zoom animation that causes the problem (panning does not cause a problem). Interestingly, I have been able to fix it by setting the alpha of the image being scaled to 0.995 (instead of 1.0).
I have two questions
What has changed in iOS7 to make this happen?
Why does changing the opacity of the view make a difference?
Further information for the above questions:
Animation Setup
The animations are all pre-defined and are played upon user interaction. The animations are all a mix of pan and zoom. The animations are really simple:
[UIView animateWithDuration:animationDuration delay:animationDelay options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.frame = nextFrame;
//...
} completion:^(BOOL finished) {
//...
}];
To fix the jerky animation, I set the alpha before the animation
self.alpha = 0.99;
Some interesting points:
Setting the alpha inside of the animation works as well
Setting the alpha back to 1.0 after the animation and then doing the reverse animation with a 1.0 alpha does not give a smooth reverse animation.
Opacity fix
I have previously used the opacity fix to make animations smooth when scaling and panning multiple images. For example, I had two large images panning and scaling at different speeds with one on top of the other. When a previously un-rendered part of the lower image (the image on the bottom) became visible, the animation would become jerky (panning as well as scaling). My thought for why alpha helps in this case is, if the top image has a bit of transparency, the bottom image must always be rendered, which means it can be cached before the animation takes place. This thought is backed by doing the reverse animation and not seeing the jerky animation. (I guess I would be interested to know if anyone has different thoughts on this as well).
Having said the above, I don't know how this would have an affect when there is just one image (as in the situation I am describing in my question). Particularly when after getting the jerky animation, the reverse animation is still jerky. Another point of difference between the two situations is that it is only scaling that causes the problem in the current issue, while in the double image issue it was panning as well as scaling.
I hope the above is clear - any insights appreciated.
Look at Group Opacity. iOS 7 has that turned ON by default and this changes the way views/layers are composited:
When the UIViewGroupOpacity key is not present, the default value is
now YES. The default was previously NO.
This means that subviews of a transparent view will first be
composited onto that transparent view, then the precomposited subtree
will be drawn as a whole onto the background. A NO setting results in
less expensive, but also less accurate, compositing: each view in the
transparent subtree is composited onto what’s underneath it, according
to the parent’s opacity, in the normal painter’s algorithm order.
(source: iOS7 Release Notes)
With this setting on, compositing - also during animations - is way more expensive.
Also, have a look at the CoreGraphics Instruments tool to check if you have lots of off-screen images compositing going on.
Are you having any sort of changes going on in the view being animated? That would trigger more discarding of the rendered layer image from the backing store.
Pan works fine for me, but pinch with recognizer code like this does not:
- (void)pinchDetected:(UIPinchGestureRecognizer *)pinchRecognizer
{
CGFloat scale = pinchRecognizer.scale;
self.imageView.transform = CGAffineTransformScale(self.imageView.transform, scale, scale);
pinchRecognizer.scale = 1.0;
}
What happens is that the image view is continuously resetting the image according to its "mode", whether it's center, aspect fit, etc.
I solved my problem: I'm making my first image viewer, and to learn how to pinch and zoom, I naively googled for how to support gestures, which are not enabled by simply adding an image view to a view controller.
Unfortunately, there are many "tutorials" on this, showing how to program with the gesture recognizers, etc. And I spent a few hours going down this route unnecessarily. I kept going because I felt tantalizingly close to getting things working: The pan gesture was flawless and was "just" zoom that was broken.
(Side question: is there some awesome source for current, iOS 6 "best practices"?)
It turns out, this is the wrong path and needlessly complex for basic gesture recognition. All that's needed is to place the image view in a scroll view. 99% of the programming is taken care of. (I was convinced this had to be the case — I couldn't believe that such core functionality wouldn't be provided by cocoa touch.)
I'm working on an app where I have a subclass of a UIView that contains seven subviews with orientations arranged such that they form a 7-sided shape. Each of these subviews has additional subviews in front of them that are oriented the same way as the main view.
Fine so far. Then I tried adding some code to tap into core location services. I used the basic procedure outlined at this tutorial. Using the code as it is presented there, I successfully got the heading to display in the debugger window.
Next step was to tie it into my app. To make sure I was on the right track, I simply added this code:
float radianHeading = newHeading.trueHeading * M_PI / 180;
CGAffineTransform transformTrollCalendar = CGAffineTransformMakeRotation(radianHeading);
[self setTransform:transformTrollCalendar];
There are two issues. The first is that the view is not visibly rotated. I was prepared for that, because I probably have to trigger something to redraw the view. However, I get another problem that has me worried.
When I get a bunch of updates from the compass, the connection between my mac and my iPad gets lost, as evidenced by the stop button turning grey. Moments later, the iPad makes the sound that you get when you connect the cable to the Mac or power. Then I get the slider that you get when you first power up the iPad. I figure this means I'm doing something majorly wrong. However, the app still appears to be running on the iPad, and it is not generating any crash logs.
I resolved my own problem. Apparently, the problem was the result of my putting the code into the wrong method. I don't remember anymore which wrong method I placed the code into, but when I placed it in the correct method, it worked just fine.
I would have updated this sooner, when it was still fresh on my mind, but I've had a trip to Guatemala in the interim that has caused my memories of this problem to fade.
I have several UIButtons on a UIScrollView. I want the buttons to have rounded corners, so I call maskToBounds: on each of them. When I do this and run on the device, the scrolling framerate is pretty bad (it works fine on the simulator). Any ideas on a workaround for this problem?
You're causing the view to be composited offscreen with that call to masksToBounds:, which slows things down quite a bit. Are you rendering custom button images? If so use UIImage -stretchableImageWithLeftCapWidth:topCapHeight: with an image which is the minimum width to encompass it's rounded edges. This allows the GPU to handle stretching the image in the most efficient way possible, while still giving you a button made out of an image. There is a session in the WWDC 2011 videos on Drawing in UIKit - watch that, as it addresses exactly this problem, and a few others you're likely to have.
A few alternative methods:
Tweeties implementation of fast scrolling, by drawing everything manually
Matt Gallaghers implementation of custom drawing. This is the method I use, as it's easy to maintain
I'm trying to use a CAGradientLayer as a background for a UITableView. Everything works well until the overlaying view is scrolled, at which point then the pre-rendered background scrolls up and out of the way along with the original screen of data.
This is code that is being migrated from an iOS 3.1.3 app using a UIImage as a background to something device/resolution independent-looks great, works well, but sniffing the device type and using an alternate png isn't the sort of code that I want to ship, much less maintain.
Any suggestions as to how to do this?
Found the solution to what I was trying to solve, courtesy of Matt Gallagher:
http://cocoawithlove.com/2009/08/adding-shadow-effects-to-uitableview.html
His blog post has quite a few nice touches, including shadowing relevant cells instead of the whole table (mentioned as a performance issue in Noah's response).
You may have to make the table view transparent, and add the gradient layer to the table's superview. Keep in mind that your scrolling performance is probably going to be hideous—Core Animation will have to composite every subview of the table for every frame it displays. You may be able to slightly mitigate this by setting the cells' layers to rasterize themselves, as described here, but expect things to be pretty choppy regardless.