I have an infinite scrollview in which I add images as the user scrolls. Those images have varying heights and I've been trying to come up with the best way of finding a clear space inside the current bounds of the view that would allow me to add the image view.
Is there anything built-in that would make my search more efficient?
The problem is I want the images to be sort of glued to one another with no blank space between them. Making the search through 320x480 pixels tends to be quite a CPU hog. Does anyone know an efficient method to do it?
Thanks!
It seems that you're scrolling this thing vertically (you mentioned varying image heights).
There's nothing built in to UIScrollView that will do this for you. You'll have to track your UIImageView subviews manually. You could simply maintain the max y coordinate occupied by you images as you add them.
You might consider using UITableView instead, and implementing a very customized tableView:heightForRowAtIndexPath: in your delegate. You would probably need to do something special with the actual cells as well, but it would seem to make your job a little easier.
Also, for what it's worth, you might find a way to avoid making your solution infinite. Be careful about your memory footprint! iOS will shut your app off if things get out of hand.
UPDATE
Ok, now I understand what you're going for. I had imagined that you were presenting photographs or something rectangular like that. If I were trying to cover a scroll view with UILeafs (wah wah) I would take a statistical approach. I would 'paint' leaves randomly along horizontal/vertical strips as the user scrolls. Perhaps that's what you're doing already? Whatever you're doing I think it looks good.
Now I guess that the reason you're asking is to prevent the little random white spots that show through - is that right? If I may suggest a different solution: try to color the background of your scroll view to something earthy that looks good if it shows through here and there.
Also, it occurred to me that you could use a larger template image -- something that already has a nice distribution of leaves -- with transparency all along the outside outline of the leaves but nowhere else. Then you could tile these, but with overlap, so that the alpha just shows through to the leaves below. You could have a number of these images so that it doesn't look obvious. This would take away all of the uncertainty and make your retiling very efficient.
Also, consider learning about CoreAnimation (CALayer in particular) and CoreGraphics/Quartz 2D ). Proper use of these libraries will probably yield great improvements in rendering speed.
UPDATE 2:
If your images are all 150px wide, then split your scrollview into columns and add/remove based on those (as discussed in chat).
Good luck!
Related
Anybody know how to make progress indicator of doing something like on a screenshot?
There are mainly two way to do this:
Create and save many images with the progress indicator in various positions. Then use UIImageView to display it, changing the image accordingly to the current progress. This is a easy solution, however it requires you to create (manually) the various images. Besides it's not continuos (even if it can be smooth enough, according to the number of images you save). However, it can give a good effect (according to your needs).
Draw the indicator at runtime. In your case it should not be so hard since your progress indicator is very simple. Take a look at CGContext, it will provide all the method to draw a circle, draw lines and fill a path with a color. This will give a better result than the previous solution, however it requires the indicator to be redrawn every time.
Check out TKAProgressCircleView. Does exactly what you need, I use it myself in couple of apps.
This is somewhat of a hypothetical question, but I can imagine this situation coming up at some point in the future: Let's assume for a moment that I have a freakishly complicated hierarchy of UIViews that I'd need to render only once. Let's also assume that, once drawn, I have no further use for the information (UIImages, labels, custom views, coords, etc.) beneath the parent view. Rather than retaining them, the idea is to free all the extra memory they use while avoiding a redraw. The result would be the same as drawing to an offscreen buffer and then pushing it onscreen. Is it possible to achieve this using UIView/CGLayer right out of the box, or is the only option to convert the content of the parent CGLayer into a UIImage?
Just curious. I'd imagine in most situations the overhead of keeping a few extra views around is negligible, but memory is memory, and I haven't been able to find anything on it in the official docs beyond allocating bitmaps.
Thanks!
This strikes me as something very risky. Things beyond your control may happened that require the view to be redrawn, such as a notification appearing or the view being effected by the phone call active green bar thingy at the top of the screen.
I suggest that if you have that many views in your hierarchy there will be so many other performance problems that come into play that saving a few bytes after redraw will be the least of your problems.
I can think about overriding -(void)drawRect:(CGRect)rect; method, drawing only once to offscreen before moving that image to the screen according to some boolean flag, but it may lead to a monstrous bugs and should be desined properly. Another thing is that it may lead to overriding also the -(void)layoutSubviews;, thought it may be overhead if you've overriden the firs one...
I have a bit gantt chart that i want to be visible on an iphone.
It is 7200 x 1800px large, and consists of ~600 bars, each of which is a UILabel.
It is to look like this:
Now i've gotten it to work. And at ~100 bars, i can make it run quite smoothly by simply adding them all to the scroll view. However, with the full 600 (or more eventually) it simply crashes when i instantiate all those uilabels and add them all to the scroll view as subviews.
So what i've done is made it create only the uilabels for the currently visible rows, and as the user scrolls up and down it removes the invisible uilabels and adds the newly visible ones.
However, this jerks quite noticeably as you scroll vertically as it crosses each row boundary, and has to render another row and remove the old row.
Does anyone have any suggestions to solve this? Any ideas what is the slow part? Instantiating the uilabels, or adding them as subviews, or anything?
All help will be greatly appreciated.
Apple has some really good demo code that shows how to do this. Check out TiledScrollView.m especially the layoutSubviews method.
Other things you might consider:
If you labels are quite long horizontally you may need to break them into smaller chunks. Quite long in this context is wider than the screen.
Make sure your UILabels are opaque. Scrolling things that require compositing adds extra overhead which may account for some of your issues.
Looking at your screen shot the row and column headers are not opaque and are using alphas. Whereas this is a nice effect it may be worth temporarily making them opaque too just to see if this is contributing to your problems. I don't think this is contributing too much to your problems; the area being composited is quite small.
Just a thought, but could the issue be that even though you are caching and reusing the labels, is the scroll view still retaining them, so even though you may only have a few labels, each is being retained hundreds of times. If this is so then I would think that the scroll view is still effectively trying to manage those hundreds of rows.
So as #Nathon S asked - are you moving them? i.e. building a finite set of labels and just moving them around on the scroll view to match the viewing area. If you are hiding and re-adding to the scroll view then I would suspect a massive set of retains being the slow down. I would think that with a moving label design, you would not need to do any hides and adds after the the initial display. Which should make it very fast and lightweight.
I'm trying to make a level-of-detail line chart, where the user can zoom in/out horizontally by using two fingers, and grow the contentSize attribute of the the UIScrollView field. They can also scroll horizontally to shift left or right and see more of the chart (check any stock on Google Finance charts to get an idea of what I'm talking about). Potentially, the scroll view could grow to up to 100x its original size, as the user is zooming in.
My questions are:
- Has anyone had any experience with UIScrollViews that have such large contentSize restrictions? Will it work?
- The view for the scroll view could potentially be really huge, since the user is zooming in. How is this handled in memory?
- Just a thought, but would it be possible to use UITableViewCells, oriented to scroll horizontally, to page in/out the data?
This is kind of an open ended question right now - I'm still brainstorming myself. If anyone has any ideas or has implemented such a thing before, please respond with your experience. Thanks!
This is quite an old topic, but still I want to share some my experiences.
Using such a large UIView (100x than its origin size) in UIScrollView could cause Memory Warning. You should avoid render the entire UIView at once.
A better way to implement this is to render the only area which you can see and the area just around it. So, UIViewScroll can scroll within this area smoothly. But what if user scrolled out of the area that has been rendered? Use delegate to get notified when user scroll out of the pre-rendered area and try to render the new area which is going to be showed.
The basic idea under this implementation is to use 9 UIViews (or more) to tile a bigger area, when user scrolled (or moved) from old position to new position. Just move some UIViews to new place to make sure that one of UIView is the main view which you can see mostly, and other 8 UIViews are just around it.
Hope it is useful.
I have something similar, although probably not to the size your talking about. The UIScrollView isn't a problem. The problem is that if you're drawing UIViews on it (rather than drawing lines yourself) UIViews that are well, well off the screen continue to exist in memory. If you're actually drawing the lines by creating your own UIView and responding to drawRect, it's fine.
Assuming that you're a reasonably experienced programmer, getting a big scroll view working that draws pars of the chart is only a days work, so my recommendation would be to create a prototype for it, and run the prototype under the object allocations tool and see if that indicates any problems.
Sorry for the vagueness of my answer; it's a brainstorming question
But still, this approach (in the example above) is not good enough in some cases. Cause we only rendered a limited area in the UIScrollView.
User can use different gestures in UIScrollView: drag or fling. With drag, the pre-rendered 8 small UIViews is enough for covering the scrolling area in most of the case. But with flinging, UIScrollView could scroll over a very large area when user made a quick movement, and this area is totally blank (cause we didn't render it) while scrolling. Even we can display the right content after the UIScrollView stops scrolling, the blank during scrolling isn't very UI friendly to user.
For some apps, this is Ok, for example Google map. Since the data couldn't be downloaded immediately. Waiting before downloading is reasonable.
But if the data is local, we should eliminate this blank area as possible as we can. So, pre-render the area that is going to be scrolled is crucial. Unlike UITableView, UIScrollView doesn't have the ability to tell us which cell is going to be displayed and which cell is going to be recycled. So, we have to do it ourselves. Method [UIScrollViewDelegate scrollViewWillEndDragging:withVelocity:targetContentOffset:] will be called when UIScrollView starts to decelerating (actually, scrollViewWillBeginDecelerating is the method been called before decelerating, but in this method we don't know the information about what content will be displayed or scrolled). So based on the UIScrollView.contentOffset.x and parameter targetContentOffset, we can know exactly where the UIScrollView starts and where the UIScrollView will stop, then pre-render this area to makes the scrolling more smoothly.
Please pardon my lack of Photoshop skills, but I'm curious what type of strategy Apps like Facebook and AP Mobile News are using for the 'label slider' in their applications. Here's a quick snippet outlining what I'm talking about as I'm sure the name I'm labeling the utility as is being butchered: http://dl-client.getdropbox.com/u/57676/slider.jpg
Essentially the user can touch the label and glide it along the X axis. It has a smooth bounce effect also once it hits the edges. This gives quite a bit more real estate if you need to present more on the screen than what your portrait mode allows for and is thus very valuable.
Is it a matter of just creating a UILabel that's wider than the screen with a bit of Touch API + Core Animation? Would love insight on how to start tackling this thing.
You'll most likely want to use a UIScrollView, with a UILabel as its content view. Size the label appropriately to your content, and then set the contentSize property of the scrollview to that size.
I created a similar control, and it's much easier than you think. It's just a UIScrollView with a series of UIButtons added to it. You could use labels instead of buttons - just depends on the feel you want. I think Facebook is probably using labels for theirs.
In any case, you'll probably want to use a series of components rather than one component (which is what Ben suggested) in the event that you want to, say, style the "selected" label differently from the others. It also makes hit detection a little easier.
You get the bounce effect for free by default - you may have noticed that most scroll views in iPhone apps do the same thing. It can be turned off as well.