Are there any techniques to cause a UIWebView to redraw itself? I've tried setNeedsDisplay and setNeedsLayout on the UIWebView and its UIScrollView, but neither have worked.
Literally found the answer right after asking. The key was to tell the subviews of UIWebView's scrollView to redraw themselves - particularly the UIWebBrowserView.
- (void) forceRedrawInWebView:(UIWebView*)webView {
NSArray *views = webView.scrollView.subviews;
for(int i = 0; i<views.count; i++){
UIView *view = views[i];
//if([NSStringFromClass([view class]) isEqualToString:#"UIWebBrowserView"]){
[view setNeedsDisplayInRect:webView.bounds]; // Webkit Repaint, usually fast
[view setNeedsLayout]; // Webkit Relayout (slower than repaint)
// Causes redraw & relayout of *entire* UIWebView, onscreen and off, usually intensive
[view setNeedsDisplay];
[view setNeedsLayout];
// break; // glass in case of if statement (thanks Jake)
//}
}
}
I've commented out the if statement to be safe and avoid reliance on UIWebBrowserView's class name not changing. Without it, it hits all UIViews that are in the scrollview, which isn't really a problem at this point (no significant overhead incurred) but could always change.
EDIT:
In some cases, the following snippet of JavaScript will accomplish the same/similar thing:
window.scrollBy(1, 1); window.scrollBy(-1, -1);
You'd think UIScrollView's contentOffset would do this too, but that's not always the case in my experience - for some reason window.scrollTo is special in this regard.
Gist: https://gist.github.com/matt-curtis/5843862
This save my life:
self.wkWebView.evaluateJavaScript("window.scrollBy(1, 1);window.scrollBy(-1, -1);", completionHandler: nil)
Add this line on webview did load delegate event or wkwebview did finish navigation
Thanks MAN!!!!
Related
This is my first time working with UICollectionView.
I've got everything set up and laid out as it should be (as far as I know). I've had a little bit of difficulty with the way the dequeue function works for the UICollectionView, but I think I've gotten past that. It was tricky setting up my custom cell classes when I didn't know if initWithFrame would be called or prepareForReuse.
I'm pretty sure the problem lies within the prepareForReuse function, but where is the question.
What happens is, the cells will apparently randomly draw at the top-left of the collection view and some cells will not be where they belong in the grid. (see image attached)
When bouncing, scrolling, and zooming (so as to cause reuse to occur), the problem happens. Randomly a slide will appear in the top left, and other slides will randomly disappear from the grid.
( I need more rep to post an image. Ugh. :| If you can help me, I'll email you the image. bmantzey#mac.com )
-(UICollectionViewCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
Slide* thisSlide = [_presentation.slidesInEffect objectAtIndex:indexPath.row];
[BuilderSlide prepareWithSlide:thisSlide];
BuilderSlide* cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"PlainSlide" forIndexPath:indexPath];
return cell;}
I'm using a static method to set the Slide object, which contains the data necessary to either prepare the asynchronous download or retrieve the image from disk cache.
It's simply:
+(void)prepareWithSlide:(Slide*)slide{
if(s_slide)
[s_slide release];
s_slide = [slide retain];}
I'm not sure if it's a big no-no to do this but in my custom Cell class, I'm calling prepareForReuse in the initWithFrame block because I need that setup code to be the same:
-(id)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if(self)
{
[self prepareForReuse];
}
return self;}
Here's the prepareForReuse function:
-(void)prepareForReuse{
CGSize size = [SpringboardLayout currentSlideSize];
[self setFrame:CGRectMake(0, 0, size.width, size.height)];
self.size = size;
// First remove any previous view, so as not to stack them.
if(_builderSlideView)
{
if(_builderSlideView.slide.slideID == s_slide.slideID)
return;
[_builderSlideView release];
}
for(UIView* aView in self.contentView.subviews)
{
if([aView isKindOfClass:[BuilderSlideView class]])
{
[aView removeFromSuperview];
break;
}
}
// Then setup the new view.
_builderSlideView = [[BuilderSlideView alloc] initWithSlide:s_slide];
self.builderCellView = _builderSlideView;
[s_slide release];
s_slide = nil;
[self.contentView addSubview:_builderSlideView];
if([SlideCache isImageCached:_builderSlideView.slide.slideID forPresentation:_builderSlideView.slide.presentationID asThumbnail:YES])
{
[_builderSlideView loadImageFromCache];
}
else
{
[_builderSlideView loadView];
}}
Finally, when the slide image has been downloaded, a Notification is posted (I plan on changing this to a delegate call). The notification simply reloads the cell that has received an update. Here's the notification code:
-(void)didLoadBuilderCellView:(NSNotification*)note{
BuilderCellView* cellView = [[note userInfo] objectForKey:#"cell"];
BuilderSlideView* slideView = (BuilderSlideView*)cellView;
NSIndexPath* indexPath = [self indexPathForSlide:slideView.slide];
if(indexPath)
[self.collectionView reloadItemsAtIndexPaths:[NSArray arrayWithObject:indexPath]];}
Note that the slide objects exist in the model.
Any ideas as to what may be causing this problem? Thanks in advance!
The problem that was causing the cells to draw top left and/or disappear was caused by infinite recursion on a background thread. Plain and simply, I wasn't implementing the lazy loading correctly and safely. To solve this problem, I went back to the drawing board and tried again. Proper implementation of a lazy loading algorithm did the trick.
I have an project using autolayout,
And I notice that after viewWillAppear, viewWillLayoutSubViews and viewDidLayoutSubViews pair will be called several times on iOS 8, for my case, it is 2-3 times usually.
The fist viewDidLayoutSubViews will get incorrect frame size, so I have to avoid for first viewDidLayoutSubViews, and init my views afterwards.
However, when I tested it on iOS 7, I found that only ONE viewWillLayoutSubViews and viewDidLayoutSubViews pair got called, so my code broke again.
My question is, what is changed on iOS 8 for this behaviour?
EDIT:
I have pasted my demo code here:
In the code, _pieChart will be added to self.ChartViewCanvas, and self.ChartViewCanvas is using autolayout. _pieChart is from old project code, which is drawn without auto layout.
I was required to draw the pie chart before viewDidAppear, because drawing in viewDidAppear will have a 1 sec delay compare to other views in storyboard. This is not allowed for me.
Is there any way to know when is the final viewDidLayoutSubViews? Calling [self.ChartViewCanvas addSubview:_pieChart]; multiple times will lead to lower performance, and sometimes _pieChart's drawInRect will not be called every time, so the chart is not update.
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
_pieChart.delegate = self;
if (!_pieChart) {
_pieChart = [[PieChartView alloc] initWithFrame:CGRectMake(0, 0, pieRadius * 2, pieRadius * 2)];
}else {
[_pieChart setFrame:CGRectMake(0, 0, pieRadius * 2, pieRadius * 2)];
}
//_pieChart.translatesAutoresizingMaskIntoConstraints = NO;
if ([_pieChart superview]) {
[_pieChart removeFromSuperview];
}
[self.ChartViewCanvas addSubview:_pieChart];
}
Probably only Apple knows, but I won't deal with that too much if everything is working fine. In iOS8 Apple changed a lot view controllers (again) in they way they are presented from containers VC as for rotation and UITraitCollections.
For instance UIAlertView is now a view controller, when you show one you trigger all the mechanism related to present a VC.
If this fact is creating an issue it must be said that you should not rely on how many times those methods are called because they were always be unpredictable there are too many variables to be taken into account.
A quick and dirty solution could be wrap your code in a dispatch_once if you want that it will be called only one time.
If you add your view using auto layout correctly you won't see any sort of bug.
[EDIT]
Here is a little snippet about how it might look your viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
//.. your stuff
//We don't need any frame autolayout wil take care of calculating it on its pass
_pieChart = [[PieChartView alloc]initWithFrame:CGRectZero];
_pieChart.delegate = self;
_pieChart.translatesAutoresizingMaskIntoConstraints = NO;
[self.ChartViewCanvas addSubview:_pieChart];
NSDictionary *bindings = NSDictionaryOfVariableBindings(_pieChart);
// We create constraints to tell the view that it needs to sctretch its bounds to the superview
NSString *formatTemplate = #"%#:|[_pieChart]|";
for (NSString * axis in #[#"H",#"V"]) {
NSString * format = [NSString stringWithFormat:formatTemplate,axis];
NSArray * constraints = [NSLayoutConstraint constraintsWithVisualFormat:format options:0 metrics:nil views:bindings];
[_pieChart.superview addConstraints:constraints];
}
// Do any additional setup after loading the view.
}
Of course that is going to call drawRect:, draw rect is called when a view is marked as dirty in the display pass, but before display is usually called the autolayout engine to calculate frames of views in needs for layout.
I tried this out on my application and found the same as you: 1 call on iOS7 and 3 on iOS8. From the stack traces this seems to be down to doing double layout after viewWillAppear and an extra layout following viewDidAppear not seen on iOS7.
My suggestion would be that you add any views in viewDidLoad (or viewWillAppear), then only do layout adjustments in the layout subview runs. Based on your updated post something like:
- (void)viewDidLoad{
[super viewDidLoad];
_pieChart = [[PieChartView alloc] initWithFrame:CGRectMake(0, 0, pieRadius * 2, pieRadius * 2)];
[self.ChartViewCanvas addSubview:_pieChart];
_pieChart.delegate = self;
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
[_pieChart setFrame:CGRectMake(0, 0, pieRadius * 2, pieRadius * 2)];
}
For interest the difference between iOS7 and 8 calling sequence was:
iOS7
i) viewWillAppear is called.
ii) layout of subviews is called. From the stack this seems to relate to the navigation bar and animation.
ii) viewDidAppear is called.
iOS8
i) viewWillAppear is called.
ii) layout of subviews is called. From the stack this seems to relate to the navigation bar and animation.
iii) exact same layout with exact same stack is called again. So something in the stack must request a rerun from some point.
iv) viewDidAppear is called.
v) An extra layout of subviews is called. This seems driven from a transaction pushed onto the run loop.
am trying to place a ScrollView in my app that has 1000,000 record, this scrollView will load when the app launches, so the app is not running until the million 1000 000 record which takes a lot of time, i was wondering is there any way to show the app and the scrollView while records are loading (show the scrollView while adding its records), below the code am using:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self loadIt];
}
- (void)loadIt{
float startX = 0;
float startY = 0;
[_bigScroll setContentSize:CGSizeMake(320, 312500)];
_bigScroll.pagingEnabled = NO;
for (counter=0; counter<999999; counter++)
{
UIButton *tester=[[UIButton alloc] initWithFrame:CGRectMake(startX, startY, 10, 10)];
if (counter % 2 == 0) {
[tester setBackgroundColor:[UIColor whiteColor]];
}
else
{
[tester setBackgroundColor:[UIColor grayColor]];
}
[_bigScroll addSubview:tester];
[tester release];
if (startX == 320) {
startX = 0;
startY += 10;
}
else
startX += 10;
NSLog(#"counter = %d", counter);
}
}
Please advice.
Is there any way to show the app and the scrollView while records are loading ?
Try to use [self performSelector:#selector(loadIt) withObject:nil]; or
[self performSelector:#selector(loadIt) withObject:nil afterDelay:0.2];
It will not block your UI until the execution of this method.
You are loading lots of records. Actually you should not load all records at at time. You should use mechanism something like tableview is using i.e.load only those record which are in visible area of scrollview. Don't load new rows until the scroll and you should reuse row or views so speedup the scrolling.
Apple's documentation for UIScrollView is very clear that the scrolled view should be tiled, with your application providing tiles as the view scrolls.
The object that manages the drawing of content displayed in a scroll view should tile the content’s subviews so that no view exceeds the size of the screen. As users scroll in the scroll view, this object should add and remove subviews as necessary.
This is necessary both for performance and memory usage: the scrollable view is backed by a CALayer, which in turn is backed by a bitmap. The same is true for each of the UIButton objects created.
Whilst it is not surprising that this takes a long time, it's more of a mystery that your app hasn't been terminated for using too much memory.
Both UITableView and UICollectionView are examples of views that tile their content. You may find you can use one of these to implement you requirements, and if not, follow the model they use.
You don't need to create 1000,000 views . You can create views dynamically and remove the previous views those are not visible at the screen space. So at the time of scrolling you can create new views and remove the views those are out of visible area of screen.
This will help you to save memory otherwise whether you are using ARC in your project if you load that much number of views in memory there will surely a chance of crash , ARC will not help you in that case.
once try this Change the code in the
-viewdidload()
{
[self loadIt];//change this to
[self performSelectorInBackground:#selector(loadIt) withObject:nil];
}
I'm trying to increase the scrolling performance of my UIScrollView. I have a lot of UIButtons on it (they could be hundreds): every button has a png image set as background.
If I try to load the entire scroll when it appears, it takes too much time. Searching on the web, I've found a way to optimize it (loading and unloading pages while scrolling), but there's a little pause in scrolling everytime I have to load a new page.
Do you have any advice to make it scroll smoothly?
Below you can find my code.
- (void)scrollViewDidScroll:(UIScrollView *)tmpScrollView {
CGPoint offset = tmpScrollView.contentOffset;
//322 is the height of 2*2 buttons (a page for me)
int currentPage=(int)(offset.y / 322.0f);
if(lastContentOffset>offset.y){
pageToRemove = currentPage+3;
pageToAdd = currentPage-3;
}
else{
pageToRemove = currentPage-3;
pageToAdd = currentPage+3;
}
//remove the buttons outside the range of the visible pages
if(pageToRemove>=0 && pageToRemove<=numberOfPages && currentPage<=numberOfPages){
for (UIView *view in scrollView.subviews)
{
if ([view isKindOfClass:[UIButton class]]){
if(lastContentOffset<offset.y && view.frame.origin.y<pageToRemove*322){
[view removeFromSuperview];
}
else if(lastContentOffset>offset.y && view.frame.origin.y>pageToRemove*322){
[view removeFromSuperview];
}
}
}
}
if(((lastContentOffset<offset.y && lastPageToAdd+1==pageToAdd) || (lastContentOffset>offset.y && lastPageToAdd-1==pageToAdd)) && pageToAdd>=0 && pageToAdd<=numberOfPages){
int tmpPage=0;
if((lastContentOffset<offset.y && lastPageToAdd+1==pageToAdd)){
tmpPage=pageToAdd-1;
}
else{
tmpPage=pageToAdd;
}
//the images are inside the application folder
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
for(int i=0;i<4;i++){
UIButton* addButton=[[UIButton alloc] init];
addButton.layer.cornerRadius=10.0;
if(i + (tmpPage*4)<[imagesCatalogList count]){
UIImage* image=[UIImage imageWithContentsOfFile:[NSString stringWithFormat: #"%#/%#",docDir,[imagesCatalogList objectAtIndex:i + (tmpPage*4)]]];
if(image.size.width>image.size.height){
image=[image scaleToSize:CGSizeMake(image.size.width/(image.size.height/200), 200.0)];
CGImageRef ref = CGImageCreateWithImageInRect(image.CGImage, CGRectMake((image.size.width-159.5)/2,(image.size.height-159.5)/2, 159.5, 159.5));
image = [UIImage imageWithCGImage:ref];
}
else if(image.size.width<image.size.height){
image=[image scaleToSize:CGSizeMake(200.0, image.size.height/(image.size.width/200))];
CGImageRef ref = CGImageCreateWithImageInRect(image.CGImage, CGRectMake((image.size.width-159.5)/2, (image.size.height-159.5)/2, 159.5, 159.5));
image = [UIImage imageWithCGImage:ref];
}
else{
image=[image scaleToSize:CGSizeMake(159.5, 159.5)];
}
[addButton setBackgroundImage:image forState:UIControlStateNormal];
image=nil;
addButton.frame=CGRectMake(width, height, 159.5, 159.5);
NSLog(#"width %i height %i", width, height);
addButton.tag=i + (tmpPage*4);
[addButton addTarget:self action:#selector(modifyImage:) forControlEvents:UIControlEventTouchUpInside];
[tmpScrollView addSubview:addButton];
addButton=nil;
photos++;
}
}
}
lastPageToAdd=pageToAdd;
lastContentOffset=offset.y;
}
Here's a few recommendations:
1) First, understand that scrollViewDidScroll: will get called continuously, as the user scrolls. Not just once per page. So, I would make sure that you have logic that ensures that the real work involved in your loading is only triggered once per page.
Typically, I will keep a class ivar like int lastPage. Then, as scrollViewDidScroll: is called, I calculate the new current page. Only if it differs from the ivar do I trigger loading. Of course, then you need to save the dynamically calculated index (currentPage in your code) in your ivar.
2) The other thing is that I try not to do all the intensive work in the scrollViewDidScroll: method. I only trigger it there.
So, for example, if you take most of the code you posted and put it in a method called loadAndReleasePages, then you could do this in the scrollViewDidScroll: method, which defers the execution until after scrollViewDidScroll: finishes:
- (void)scrollViewDidScroll:(UIScrollView *)tmpScrollView {
CGPoint offset = tmpScrollView.contentOffset;
//322 is the height of 2*2 buttons (a page for me)
int currentPage = (int)(offset.y / 322.0f);
if (currentPage != lastPage) {
lastPage = currentPage;
// we've changed pages, so load and release new content ...
// defer execution to keep scrolling responsive
[self performSelector: #selector(loadAndReleasePages) withObject: nil afterDelay:0];
}
}
This is code that I've used since early iOS versions, so you can certainly replace the performSelector: call with an asynchronous GCD method call, too. The point is not to do it inside the scroll view delegate callback.
3) Finally, you might want to experiment with slightly different algorithms for calculating when the scroll view has actually scrolled far enough that you want to load and release content. You currently use:
int currentPage=(int)(offset.y / 322.0f);
which will yield integer page numbers based on the way the / operator, and the float to int cast works. That may be fine. However, you might find that you want a slightly different algorithm, to trigger the loading at a slightly different point. For example, you might want to trigger the content load as the page has scrolled exactly 50% from one page to the next. Or you might want to trigger it only when you're almost completely off the first page (maybe 90%).
I believe that one scrolling intensive app I wrote actually did require me to tune the precise moment in the page scroll when I did the heavy resource loading. So, I used a slightly different rounding function to determine when the current page has changed.
You might play around with that, too.
Edit: after looking at your code a little more, I also see that the work you're doing is loading and scaling images. This is actually also a candidate for a background thread. You can load the UIImage from the filesystem, and do your scaling, on the background thread, and use GCD to finally set the button's background image (to the loaded image) and change its frame back on the UI thread.
UIImage is safe to use in background threads since iOS 4.0.
Don't touch a line of code until you've profiled. Xcode includes excellent tools for exactly this purpose.
First, in Xcode, make sure you are building to a real device, not the simulator
In Xcode, choose Profile from the Product menu
Once Instruments opens, choose the Core Animation instrument
In your app, scroll around in the scroll view you're looking to profile
You'll see the real time FPS at the top, and in the bottom, you'll see a breakdown of all function and method calls based on total time ran. Start drilling down the highest times until you hit methods in your own code. Hit Command + E to see the panel on the right, which will show you full stack traces for each function and method call you click on.
Now all you have to do is eliminate or optimize the calls to the most "expensive" functions and methods and verify your higher FPS.
That way you don't waste time optimizing blind, and potentially making changes that have no real effect on the performance.
My answer is really a more general approach to improving scroll view and table view performance. To address some of your particular concerns, I highly recommend watching this WWDC video on advanced scroll view use: https://developer.apple.com/videos/wwdc/2011/includes/advanced-scrollview-techniques.html#advanced-scrollview-techniques
The line that is likely killing your performance is:
addButton.layer.cornerRadius=10.0;
Why? Turns out the performance for cornerRadius is AWFUL! Take it out... guaranteed huge speedup.
Edit: This answer sums up what you should do quite clearly.
https://stackoverflow.com/a/6254531/537213
My most common solution is to rasterize the Views:
_backgroundView.layer.shouldRasterize = YES;
_backgroundView.layer.rasterizationScale = [[UIScreen mainScreen] scale];
But it works not in every situation.. Just try it
I have implemented a custom split view controller which — in principle — works quite well.
There is, however one aspect that does not work was expected and that is the resize-animation of the toolbar on iOS prior to version 5.1 — if present:
After subclassing UIToolbar to override its layoutSubviews method, animating changes to the width of my main-content area causes the toolbar-items to move as expected. The background of the toolbar — however — does not animate as expected.
Instead, its width changes to the new value immediately, causing the background to be shown while increasing the width.
Here are what I deem the relevant parts of the code I use — all pretty standard stuff, as little magic/hackery as possible:
// From the implementation of my Split Layout View Class:
- (void)setAuxiliaryViewHidden:(BOOL)hide animated:(BOOL)animated completion:(void (^)(BOOL isFinished))completion
{
auxiliaryViewHidden_ = hide;
if (!animated)
{
[self layoutSubviews];
if (completion)
completion(YES);
return;
}
// I've tried it with and without UIViewAnimationOptionsLayoutSubviews -- didn't change anything...
UIViewAnimationOptions easedRelayoutStartingFromCurrentState = UIViewAnimationOptionCurveEaseOut | UIViewAnimationOptionBeginFromCurrentState;
[UIView animateWithDuration:M_1_PI delay:0.0 options:easedRelayoutStartingFromCurrentState animations:^{
[self layoutSubviews];
} completion:completion];
}
- (void)layoutSubviews
{
[super layoutSubviews];
// tedious layout work to calculate the frames for the main- and auxiliary-content views
self.mainContentView.frame = mainContentFrame; // <= This currently has the toolbar, but...
self.auxiliaryContentView.frame = auxiliaryContentFrame; // ...this one could contain one, as well.
}
// The complete implementation of my UIToolbar class:
#implementation AnimatableToolbar
static CGFloat sThresholdSelectorMargin = 30.;
- (void)layoutSubviews
{
[super layoutSubviews];
// walk the subviews looking for the views that represent toolbar items
for (UIView *subview in self.subviews)
{
NSString *className = NSStringFromClass([subview class]);
if (![className hasPrefix:#"UIToolbar"]) // not a toolbar item view
continue;
if (![subview isKindOfClass:[UIControl class]]) // some other private class we don't want to f**k around with…
continue;
CGRect frame = [subview frame];
BOOL isLeftmostItem = frame.origin.x <= sThresholdSelectorMargin;
if (isLeftmostItem)
{
subview.autoresizingMask = UIViewAutoresizingFlexibleRightMargin;
continue;
}
BOOL isRightmostItem = (CGRectGetMaxX(self.bounds) - CGRectGetMaxX(frame)) <= sThresholdSelectorMargin;
if (!isRightmostItem)
{
subview.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
continue;
}
subview.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
}
}
#end
I’ve set the class of the toolbar in InterfaceBuilder and I know for a fact, that this code gets called and, like I said, on iOS 5.1 everything works just fine.
I have to support iOS starting version 4.2, though…
Any help/hints as to what I’m missing are greatly appreciated.
As far as I can see, your approach can only work on iOS SDK > 5. Indeed, iOS SDK 5 introduced the possibility of manipulating the UIToolbar background in an explicit way (see setBackgroundImage:forToolbarPosition:barMetrics and relative getter method).
In iOS SDK 4, an UIToolbar object has no _UIToolbarBackground subview, so you cannot move it around in your layoutSubviews implementation. To verify this, add a trace like this:
for (UIView *subview in self.subviews)
{
NSLog(#"FOUND SUBVIEW: %#", [subview description]);
run the code on both iOS 4 and 5 and you will see what I mean.
All in all, the solution to your problem lays in handling the background in two different ways under iOS 4 and iOS 5. Specifically, on iOS 4 you might give the following approach a try:
add a subview to your custom UIToolbar that acts as a background view:
[toolbar insertSubview:backgroundView atIndex:0];
set:
toolbar.backgroundColor = [UIColor clearColor];
so that the UIToolbar background color does not interfere;
in your layoutSubviews method animate around this background subview together with the others, like you are doing;
Of course, nothing prevents you from using this same background subview also for iOS 5, only thing you should beware is that at step 1, the subview should be inserted at index 1 (i.e, on top of the existing background).
Hope that this helps.
Since I think this is going to be useful for someone else, I’ll just drop my solution here for reference:
Per sergio’s suggestion, I inserted an additional UIImageView into the view hierarchy. But since I wanted this to work with the default toolbar styling, I needed to jump trough a few hoops:
The image needed to be dynamically generated whenever the tintColor changed.
On iOS 5.0.x the toolbar background is an additional view.
To resolve this I ended up…
Implementing +load to set a static BOOL on whether I need to do anything. (Parses -[UIDevice systemVersion] for version prior to 5.1).
Adding a (lazily loaded) property for the image view stretchableBackground. The view will be nilif my static flag is NO. Otherwise the view will be created having twice the width of [UIScreen mainScreen], offset to the left by half that width and resizable in height and right margin and inserted into the toolbar at index 0.
Overriding setTintColor:. Whenever this happens, I call through to super and __updateBackground.
Implemented a method __updateBackground that:
When the toolbar responds to backgroundImageForToolbarPosition:barMetrics: get the first subview that is not our stretchableBackground. Use the contents property of that view’s layer to populate the stretchableBackground’s image property and return.
If the toolbar doesn’t respond to that selector,
use CGBitmapContextCreate() to obtain a 32bit RGBA CGContextRef that is one pixel wide and as high as the toolbar multiplied by the screen’s scale. (Use kCGImageAlphaPremultipliedLast to work with the device RGB color space…)
Translate the CTM by that height and scale it by scale/-scale to transition from UIKit to CG-Coordinates and draw the view’s layer into that context. (If you fail to do this, your image will always be transparent blank…)
Create a UIImage from that context and set it as the stretchableBackground’s image.
Notice that this fix for iOS 5.0.x will not work as expected when using different background images for portrait and landscape or images that do not scale — although that can be tweaked by configuring the image view differently…