clipToBounds and masksToBounds performance issue - objective-c

I have UIScrollView and number of objects (UIView compositions) with UIImageViews inside them. Some of UIImageViews has round border (I use myImageView.layer.masksToBounds = YES; for this). Other has rectangle borders and part of image in them (I use Clip subviews property in Interface Builder for this).
The issue is that I found that clip properties strongly affect the performance while scrolling:
For iPod touch (4th generation) results of profiling:
with enabled clip properties (both or one of them) I have around 30 fps while scrolling
with disabled clip properties I have all 60 fps while scrolling
I really need to clip some images to round bounds and other to rectangle bounds (to show part of image). So, here is my question: what ways there are to improve performance? May be there are low level ways to do it (drawRect: or something), or may be it would be useful to play around alfa masking or I just do something wrong?

When you have graphically intensive masks and things, a simple and easy way to improve performance (often times dramatically) is to set shouldRasterize to YES on the layer for that item:
#import <QuartzCore/QuartzCore.h>
// ...
view.layer.shouldRasterize = YES;
This will rastersize the view into a buffer, so it isn't constantly re-rendered. This will take up a extra memory for each view, so you should really try and recycle/reuse views as you scroll, similar to how a table view does.
For correct behaviour on retina display you also need to set an appropriate value for rasterizationScale:
view.layer.rasterizationScale = view.window.screen.scale; // or [UIScreen mainScreen]
I've had great success with this for things like scrolling photo galleries, where each item had rounded corners, shadows, etc.

Related

UIImageViews Jump around when a label changes - Xcode

I have falling UIImageViews falling on the screen and going back to the top in my game. I have another two image views on the bottom of the screen that their images change every so often. When those two bottom images change, the falling UIImageViews jump around the screen to their original position of when the game app opens, then they fall again. The same thing happens when the score label changes.
I have constraints on almost everything so im thinking that might have something to do with it but I don't know why.
Thank you very much for your help
OK so I'm sure you're aware of layout constraints and the NSLayoutConstraint class. (If not read this). Basically Apple uses these classes which are represented in the IB to position and resize subviews e.t.c.
Because of this you need to adjust the layout constraints of any view you want to move. To animate a UIView it used to be a case of changing the frame property and then getting the superview to redraw it's subviews; however, now you need to change the constant property of a view's layout constraints and then call layoutIfNeeded on the superview like so:
myImageViewConstraint.constant += 100;
[UIView animateWithDuration:1 animations:^{
[superview layoutIfNeeded];
}];
Then because you have changed the constraint, the view won't jump around.
Note: you should research this before jumping on Stack Overflow because this is readily available in the Apple Developer Resources.

Storyboard editor layout confusion

I am having layout problems with the storyboard editor with a fairly simple screen. I have a UIViewController to which I have added a 320x440 UIScrollView at 0,0 followed by a 320x20 UIProgressBar at 0,440. It looks fine in Storyboard editor. I'm not entirely sure how the 20 pixel status bar at the top of the screen is accommodated given the CGRect frame coordinates that Storyboard calculates.
On loading ( in -(void)viewDidLoad ), the UIScrollView frame seems to be set to 320x460 pixels at 0,0 but the UIProgressBar is still 320x20 at 0,440.
When I add subviews to the UIScrollView, (UIImageViews in particular), they get stretched and get clipped on the screen because although the UIScrollView thinks it is 460 pixels high, it only has 440 pixels of screen to display in.
Can anyone point me to a solution?
Thanks
OK - I have identified what was going on - there were a number of issues, nearly all to do with mutually incompatible settings in storyboard attributes on various view controllers.
In summary, the main view controller containing the UIScrollView had the 'wants full screen' checkbox ticked - goodness knows how, but it appears that I had then gone through other views trying to compensate for that initial error by clipping, resizing, setting layout constraints etc. which resulted in rather confusing outcomes.
My advice would be to not touch ANYTHING in storyboard editor unless you know what it's effect will be - it is a dangerous place. I found the issue by going back to basics and creating a trivially simple replication of my app and then observing the differences between that app and my own. Sorry if I wasted anybody's time researching an answer.
Thanks

What is the best way to create a composite scrollable view on iOS

I need to create a scrollable composite view on iOS. That is to say, the view will contain at least one image, possibly a button, and some text (that we may wish to format with bold fonts, etc). The amount of data, and particularly the amount of text, is variable, from maybe 4 lines to maybe 100. The data is "variable" to a degree, and in particular the image and text do not come joined at the hip.
This all needs to fit in a "pane" of about 280h x 115w pixels in a portrait-only layout.
A single UITextView doesn't provide the facilities to display an image or format the text.
A UIWebView provides the ability to display the image and formatted text, but the button is a problem (not even sure if it's doable).
A UIScrollView would easily allow the image and button, and then a UIWebView could be embedded in the scroll view for the text, but then scrolling becomes a problem -- I'd like the entire view to scroll as one, without having to resize the web view to contain it's content, and without the confusion of a scrollable within a scrollable (the doc warns of "unexpected behavior").
(I'm guessing your thoughts at this point are that I want too much.)
So, any suggestions? What's the best way to get close to what I need here?
In iOS5 the UIWebView has a scrollView property, which is a normal UIScrollView. You should be able to add a UIButton as a subview of the scrollView to achieve what you want, although positioning it correctly may be a challenge. Prior to iOS5 you could cycle through the subviews of the UIWebView to find the UIScrollView with isKindOfClass...
I suggest testing with a UIWebView inside your UIScrollView. I don't see any interference in the iOS 5.0 simulator. I don't know if there are problems in iOS 4.0.
If you find that there is interference, you can prevent it by setting the web view's userInteractionEnabled property to NO, either in the nib or in code. This will prevent the web view from receiving any touches, so the user also won't be able to pinch-zoom it, follow links in it, or copy text from it.
In the web view's delegate, implement webViewDidFinishLoad: to set the web view's size and the scroll view's contentSize. For example:
- (void)webViewDidFinishLoad:(UIWebView *)webView {
CGRect frame = self.webView.frame;
frame.size = [self.webView sizeThatFits:CGSizeMake(frame.size.width, HUGE_VALF)];
self.webView.frame = frame;
self.scrollView.contentSize = CGSizeMake(CGRectGetMaxX(frame), CGRectGetMaxY(frame));
}
When I did a similar thing, I had a dozen of views which I added to the UIScrollView and then calculated the frames of all the views. Granted, it was an extremely tedious work, given that some views could get hidden under various conditions. The layout code was actually pretty simple, laying out views from top to bottom, but ugly. The upshot is that it works like a charm, fast and reliably. You can even trivially wrap it in an animation block.

Best approach for drawing a graph of almost infinite size in a UIScrollView on iPad

I'm currently working on an app which needs to draw s.th. like a network graph. Unfortunately this graph can become very big with thousands of movable objects.
I tried to put a giant UIView inside a UIScrollView but soon noticed this won't work because of memory limitations.
So I tried another approach: currently I have a UIView which has exactly the size of the visible part of the UIScrollView. The scrollview is set to not handle the scrolling (only the pinching). Instead I handle the scrolling in the UIView. Everytime a user scrolls, all graphic objects (those graphic objects are currently just subclasses of NSObject, which contain custom drawing code) are moved, so it seems like the view is scrolling. In the drawRect I only draw the graphics that are currently visible.
Also I constantly add and remove sublayers if they are moved out/in the visible frame
This works very smooth even with thousands of objects.
Unfortunately this approach has some drawbacks:
I can't zoom out to see all the objects in the graph, instead the user can only see a part of it
I don't get the inertial scrolling the UIScrollView offers
Other approaches I tried, like the CATiledLayer, don't work either because all the objects in the graph are draggable by the user and it looks really ugly if I use a CATiledLayer...
Swapping out the UIView with other UIViews while the user scrolls may help with the inertial scrolling, but it makes everything more complicated and zooming out completely still won't work :-(
Do you know of any best practices to draw graphs that can be very big?
//edit: I ended up with a uiscrollview which has a subview which has a cascrolllayer which has many sublayers. While zooming in and out the frame of the uiscrollviews subview is constantly changed to the uiscrollviews bounds by view.frame = scrollview.bounds. While dragging the scrollview the cascrolllayer is always forced to scroll to the current offset of the scrollview.
I needed to subclass the uiscrollview and hack around in order to make the zooming work nicely, but it's working well now. This approach works very well and allows very big graphs with lots of draggable elements.
//edit: see my other answer below, the approach above didn't work out as well as I initially thought, especially the zooming part
CATiledLayer is definitely what you should use here—there’s not really another solution that’ll let you use Quartz/UIKit drawing on a huge zoomable canvas. For anything that needs to be interactive (dragged or animated or whatever), you can disable its display in the main tiled layer and overlay another view or layer on top of it that just contains the object being interacted with.

How can I scroll a UIView of indefinite size within a UIScrollView

I'm trying to draw a graph that is indefinitely large horizontally, and the same height as the screen. I've added a UIScrollView, and a subclass of a UIView within it, which implements the -drawRect: method. In the simulator, everything works fine, but on the device, it can't seem to draw the graph after it reaches a certain size.
I'm already caching pretty much everything I can, and basically only calling CGContextAddLineToPoint in the -drawRect: section. I'm only drawing what's visible on the screen. I have a delegate to the UIScrollView which listens for -scrollViewDidScroll: which then tells the graph to redraw itself ([graphView setNeedsDisplay]).
I found one method that tells me to override the +layerClass method and return [CATiledLayer class]. This does allow the graph to actually draw on the device, but it functions very poorly. It's incredibly slow to actually draw, and the fade in that occurs is undesirable.
Any suggestions?
Well, here's my answer: I basically did something similar to how the UITableView works with cells: I have an NSMutableSet of GraphView objects which store unused graphs. When a section of the scroll view becomes visible, I take a graph view from that set (or make a new one if the set is empty). It already had a scrollX property to determine which part of it was supposed to draw. I set the scrollX property to the correct value and, instead of using the screen width, I gave it an arbitrary width to draw. When it goes out of the scroll view, it is removed from the UIScrollView and added to the set.
I wonder though if I really even need to remove them when they go outof the view? It may be prudent to try leaving them in and remove the ones not on screen only if I get a low memory warning? This might get rid of the pause whenever it needs to redraw a section of graph that hasn't changed.
My saving grace here was that my GraphView already was set up to draw only a portion of the graph. All I needed to do then was just make more than one of them.
I think this is a limitation of the iPhone graphics hardware. Through experimentation, I have seen that the iPhone will refuse to draw a frame that is bigger than 2000 pixels in either height or width. It probably has something to do with limited size for frame buffers in hardware.
Watch the 2011 WWDC session video entitled "Session 104 - Advanced Scroll View Techniques".
Thanks, that's helpful. One question -- what did you use for the contentSize of the UIScrollView? Does UIScrollView tolerate large content sizes (over 2000 px) as long as you're not creating buffers to fill the entire space in your content view? Or are you keeping the UIScrollView a constant size (say, 2 screen widths) and resting the UIScrollView contentOffset property each time you draw (using scrollX instead of the contentOffset to store your position)?
I think I answered my own question (the latter seems like a better alternative), heh, but I'll go ahead and post this in case other people need clarification.