When using UITableView, we can reuse its cells using [[ UITableViewCell alloc] initWithStyle: reuseIdentifier:] and [uiTableViewInstance dequeueReusableCellWithIdentifier:] methods. This helps keep memory in check for huge tables as only a few cells are there in the view at a given instant.
I want to create a UIScrollView that has many subviews. Inserting all the subviews takes up a lot of memory and initial time that I want to avoid. Does Apple API provides ways to reuse such custom components (a UIView or a subclass of it here) just like cell views using a identifier?
I will create one if there is no API, but have some doubts regarding this. For example, for every new subview, I am setting its frame position, after the previos views. How should I go about updating the frame for each subview while recycling? Should I delete and reload the content of every subview as it gets recycled? Should I do all these calculations in another thread to avoid jerky scrolling? In all, I would like to have a smooth scrolling experience like in UITableView with all the reusing stuff.
Here is a sample of code that I have written so far:
int numberOfPages = 0;
int pageWidth = 100;
int pageHeight = 100
UIScrollView *myScrollView = //allocate and initialize a scrollview
//set its size to 100 by 100 (width equal to pageWidth)
//set paging enabled for myScrollView
Adding subviews to it from a method, that is called multiple times
- (void) appendSubViewToScrollView {
UIView *view = //allocate and initialize a view and dump data in it.
CGRect rect = view.frame;
rect.size.height = pageHeight;
rect.size.width = pageWidth;
rect.origin = CGPointMake(pageHeight * numberOfPages, 0);
view.frame = rect;
[myScrollView addSubview:view];
numberOfPages++;
[scrollView setContentSize:CGSizeMake(pageHeight * numberOfPages, pageWidth)];
[view release];
}
Edit:
Some insight into how tableview and its cells achieve this behind the scenes would be useful.
Yes, you should restore each subview content each time, exactly as in the table view. The advantage of recycling subviews is in memory saving for view storage, and time saving for view allocation, but of course content data management is up to you.
So the standard recycling approach requires you to use a number of cells which is equal to the number of views visible at the same time on screen + the number of extra cells you may get when starting scrolling.
Let's say for example you're showing 5 full views at a time (scroll view stable) and then while scrolling you will need one extra view which is partially shown, so at the end you need 5+1=6 views. This is in theory, it is recommended to use 2 more views.
So you need to write two pools: one called "visibleViews" which is made of all views added as subviews to the scrollview, and another one called "availableViews" which is made of all views available for re-use.
Then you create all these views and add them to the scroll view (yes: you need to adjust their frame according to their position in the scrollview, and yes, you need to setup the content again).
Finally you need to track the scroll view movement by setting a delegate. The purpose of this tracking is to calculate which of the visible views is no more visible, then remove it from the visible pool and move to the usable pool. Besides the delegate must understand when a new cell is going to appear but it is still not visible, then getting it from the available pool (or alloc/init it if the pool is empty) and adding to both the visible pool and as subview of the scrollview.
Of course if you want to increase performance you can place more subviews in the scroll view in order to avoid to move cells exactly when they start appearing on screen, that's why I recommended to use a couple of extra views at the sides of the scroll view.
There is a great video from WWDC 2010 (you can access it if you're a registered developer) about usage of scroll views in iOS: it explains this technique.
The Apple's PhotoScroller example code in the XCode documentation does essentially what is stated in the WWDC video and explains this technique.
Related
In the Apple iPhone Clock App, when a user adds a new alarm a modal view pops up. I am trying to create a UI similar to that.
I currently have a UITableViewController as the root view controller of a UINavigationController.
I also have a UIDatePicker added as a subview to the UINavigationController:
[self.navigationController.view addSubview:mydatePicker];
However, I have about 10+ rows in my UITableview (that of the UITableViewController) and as soon as I added the UIDatePicker I cannot scroll to view all the cells.
I realized that the UITableView size is the same size as it was before I added the UIDatePicker, and therefore I would need to change its size in order to be able to scroll to see all the table cells.
I have tried several things in order to change its size, all to no avail. In the code below I arbitrarily chose 50 for the new height.
First, tried changing the bounds height:
CGRect bounds = [self.tableView bounds];
[self.tableView setBounds:CGRectMake(bounds.origin.x,
bounds.origin.y,
bounds.size.width,
50)];
Then tried to change the frame height:
CGRect tvframe = [self.tableView frame];
[self.tableView setFrame:CGRectMake(tvframe.origin.x,
tvframe.origin.y,
tvframe.size.width,
50)];
Then after googling some more i tried changing the contentSize height:
CGSize thesize = self.tableView.contentSize;
thesize.height = 50;
self.tableView.contentSize = thesize;
None of these appeared to have any effect on the size of the UITableView. I still could not scroll to see all the cells.
I later tried some of the same methods as above but on the UINavigationController instead of the UITableView. I didn't have much luck with this either.
As a last resort, I tried to change the size of the UITableView in the Storyboard editor. I could not figure this out either.
Thanks for any help you can provide.
From the UITableViewController class reference, it:
creates an unconfigured UITableView object with the correct dimensions
and autoresize mask
And a quote from the Table View Programming Guide for iOS which specifically addresses this behavior:
Note: You should use a UIViewController subclass rather than a
subclass of UITableViewController to manage a table view if the view
to be managed is composed of multiple subviews, only one of which is a
table view. The default behavior of the UITableViewController class is
to make the table view fill the screen between the navigation bar and
the tab bar (if either are present).
If you don't want the table view controller setting the size of your tableView, then you need to use a UIViewController instead. See the link that I posted above to the Table View Programming Guide for iOS for other things to consider when going this route.
I had same problem as you mentioned.
I was unable to change the height of tableView of UITableViewController. I tried changing frame and centers of self.view and self.tableview. But failed.
Then I solved it by a trick, changing tableview's contentInset.
self.tableView.contentInset = UIEdgeInsetsMake(0, 0, 44.0f, 0); // here you can replace 44.0f with your height
You need to make the UITableView a subview of your main view in order to change its size reliably.
The frame is the correct value to alter once you have made this change.
I dont know what is your real Problem . but for change hight of your UITableView and will Display all rows in UITableView write following code.
self.tblView.frame = CGRectMake(0, 0, 320, 50);
please try it.
Your are taking uitableview controller, in place of that use uiviewcontroller in that add the uitabelview. Now you can the the size of uitableview.
I'm trying to switch from two-stage rotation to one-stage rotation (to avoid the console warning, and because Apple recommend doing so because one-stage is faster).
However I can't figure out how to get the new size of my view (taking into account the navigation bar, status bar, etc) early enough to perform the update of my UI during the animation (rather than simply snapping the items to their new positions at the end as many applications seem to do, which results in a big "jerk" right at the end of the animation).
The sizes I get in the willAnimateRotationToInterfaceOrientation:duration: method are (perhaps obviously) the old sizes.
I can see I should be able calculate it by hand, working out the current bar heights, then inferring the new view frame size by deducted those from the rotated screen dimensions? (which isn't that difficult to do, though is perhaps fragile as it assumes the navigation bar, status bar, etc will be the same height in both orientations, and you'd have to manually take account of the toolbar being different heights in portrait vs landscape - I just want to make sure I've not missed a more straightforward or common way.)
Any feedback on approaches other people have taken would be great!
Thanks
Joseph
I've had the most success using my view's layoutSubviews method for autorotations. When layoutSubviews gets called during an orientation change, the view's bounds are already set to what they will be at the conclusion of the rotation. You can also at this time query the status bar, navigation bar, and toolbar for their sizes and get the correct post-rotation values (although the status bar width and height may be swapped--I just take the smaller value as the height, works great). Using that information you can lay out subviews and they will then be animated as part of the rotation.
It can be annoying to create a UIView subclass for every situation where you want to do this, so I created a special subclass of UIView called DelegatingView, which looks like this:
#protocol DelegatingViewDelegate
- (void)layoutSubviewsForView:(UIView*)view;
#end
#interface DelegatingView : UIView {
id<DelegatingViewDelegate> _delegate;
}
#property (nonatomic,assign) id<DelegatingViewDelegate> delegate;
#end
#implementation DelegatingView
#synthesize delegate = _delegate;
- (void)layoutSubviews {
[super layoutSubviews];
[_delegate layoutSubviewsForView:self];
}
#end
I use this as my base view, add subviews to it, and set my UIViewController as the delegate. Then I can layout subviews from my view controller, having access to the current orientation and so on.
Which OS version are you targeting? Under OS 4.0 (which is what I did a quick test in), [view bounds] within willAnimateRotationToInterfaceOrientation:duration: returns the new, post-rotation bounds.
If you are not seeing that, I’d suggest double-checking that your view has the appropriate auto resize mask set.
If you have complex layout requirements in the view of your view controller, it is worth it to create a subclass of UIView and perform your layout code in -layoutSubviews (the correct place to do view layout). As Hilton Campbell pointed out, if you use this method for layout, you can check frame sizes and subview relationships and set their new positions appropriately.
If your view controller's view has a simple set of subviews, then you should probably just set their autoresizingMask properties appropriately, and let them animate themselves automagically.
I tried two methods to adjust view's size.
1.) notify child views to adjust their size after -(void)viewWillAppear;
The drawback is that it will be executed each time when viewWillAppear.
ex:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self notifyChildViewWillAppear:animated];
}
at child view
- (void)notifyChildViewWillAppear:(BOOL)animated
{
// at this time, the size of superview's bounds
//equals the content view's size
// (no navigationBar's height and toolbar's height.
self.frame = [self superview].bounds;
}
2.) Calculate the necessary size by hand.
I calculate as viewDidLoad.
The sizes of navigationBar and toolBar can be derived by following code.
self.navagationController.navigationBar.view.frame.size.height;
self.tabBarController.tabBar.frame.size.height;
For an app that I am writing I need to display several views (in my case images, yet I guess that should not matter all that much) in a style similar to the way images are displayed in the Fotos app for the iPad. Consider this graphic:
Obviously on portrait view fewer slides can fit in per row than on Landscape view. What I have/want to do now:
Animate the addition of each slide by using a sliding animation
I already have a UIView subclass that represents a slide and that responds to touches the way I need it.
Where I am unsure is as to what is the best way of actually putting the slides onto the screen. I was originally just going to loop through them and place them onto the viewControllers view using the Frame of each slide (then I can do a count and increase the y-value after every 5 slides or so). Yet if I do it like that, I fear that my View will basically only support one screen orientation as I would need to relayout all the slides if the orientation changes, which seems rather a waste of resources to me (?).
How would you solve this problem? Is there maybe any "linebreak on overflow" ability such as with CSS (where a container automatically drops onto the next line if the float property is set and the parent container does not provide enough space)?
I am surely not the first person to think about this :)
Thanks for your answers!
There's no "linebreak on overflow" setting in Cocoa. If you want to make something that does that, you'll need to write code that lays out the slides. Here's a sample of the code I use to do it:
CGRect frame = CGRectMake(margins.left, margins.top, cellSize.width, cellSize.height);
for (UIButton *button in self.buttons) {
button.frame = frame;
// Move frame to next position
frame.origin.x += cellSize.width + cellSpacing;
if (frame.origin.x + frame.size.width > margins.left + contentWidth) {
frame.origin.x = leftMargin;
frame.origin.y += cellSize.height + cellSpacing;
}
}
I'm using UIButtons for each image, hence the self.buttons ivar.
Yes, you will need to relayout when the screen orientation changes. To do that, have a method like this:
- (void)layoutSubviews {
[super layoutSubviews];
[self layoutButtons];
}
Which calls your layout code. In my case, it's layoutButtons. This is in a subclass of UIScrollView, so when the view is autoresized after an autorotation, the layoutSubviews method gets called.
If you're using a UIScrollView, make sure you update the self.contentSize ivar too.
I am trying to create an app with horizontal scrolling, so that one would be able to scroll horizontally through a series of images. I watched the WWDC Session 104 video on this, and while they made an interesting app, they flew through the basics of it very quickly.
I understand using the UIScrollView, and that I have to enable paging. After that they say that I should add more views as subviews of the scrollview, but I am not clear on how to do that. I am also not clear on how I add my images to those views.
As you can probably tell I am pretty new at this so any help would be appreciated.
You want to look into UIImageView. It's a view specifically for holding images.
When you add your images, you want to set their rects (probably using initWithFrame: for each UIImageView) so that:
the first image is at 0,0
the second image is at 320,0
third is at 640,0 (etc)
I.e. each image is 320 pixels right of the previous.
The final step is to set the contentSize for your UIScrollView -- this is a CGSize which describes the total size of the scroll view.
If you have 3 images, you would then set it to (320*3) * 480 using e.g.
myScrollView.contentSize = CGSizeMake(320*3, 480);
A lot of people, when they initialize the scroll view, have a for loop or similar which steps through the images they want to display. These for loops tend to look something like this:
CGFloat scrollWidth = 0.f;
for (UIImage *someImage in someNSArrayWithImages) {
UIImageView *theView = [[UIImageView alloc] initWithFrame:
CGRectMake(scrollWidth, 0, 320.f, 480.f)];
theView.image = someImage;
[myScrollView addSubview:theView];
[theView release];
scrollWidth += 320.f;
}
myScrollView.contentSize = CGSizeMake(scrollWidth, 480.f);
This way you'll get things lined up and you'll get the content size for you at the same time.
If you want to make it so that the scroll view "intelligently" scrolls to each image and stops when people slide left/right, you can do myScrollView.pagingEnabled = YES.
Hope that helps get you going.
Assuming you have "infinite" images, putting them all there at or before launch time in a huge UIScrollView will not be an option. (there is a limit to the size of a UIView)
The way I solved it: Make a UIScrollView covering the whole screen. It's content should be a UIView of 3*320 width and 480 height, extending 320px left and 320px right.
Put 3 UIImageView's in it, left, middle and right. Set paging=YES, so the uiscrollview clips to the 3 "pages" you've created.
Make sure your class is the delegate of the uiscrollview, and listen for
-(void)scrollViewDidEndDragging:(UIScrollView*)sv willDecelerate:(BOOL)notFinished
-(void)scrollViewDidEndDecelerating:(UIScrollView*)sv
-(void)scrollViewDidEndScrollingAnimation:(UIScrollView*)sv
and make the appropriate transitions on hitting paging boundaries; shift images and set ContentOffset so you're looking at the center image again.
I suggest you make this first, and only then read on...
Then you will hit on a bug, documented here UIScrollView - (bounces = NO) seems to override (pagingEnabled = YES) and here http://www.iphonedevsdk.com/forum/iphone-sdk-development/935-paging-uiscrollview.html, which makes that you cannot disable bouncing and have paging enabled at the same time. So enable bouncing, and subclass UIScrollView, overruling setContentOffset in there to prevent bouncing. (having bouncing really enabled will make for a rather unusual user experience)
Have a look at Apple's PageControl sample code. It's fairly short and easy to follow so you'll get the gist of setting up a project where multiple view controllers are loaded as you swipe horizontally.
Once you have this setup then it's the view controller's responsibility to load its own content (in your case, an image). You should make sure you understand how to load images first (using threads, etc) before you tackle paging, etc.
Think of it as two independent tasks. The view control is responsible for loading and displaying an image. The scroll view with paging just tells the appropriate view controller when to load itself (it doesn't care what the view controller does once its loaded)
Good luck!
I need to create a view on the iPhone that scrolls horizontally through a gallery of images. The issue is that this gallery has the potential to have 100s to 1000s of images that needs to be presented, so I would like to avoid loading them all into a single UIScrollView at once and destroying performance. I need to create a view that recycles the view objects (like UITableView) to increase performance and reduce memory overhead, but it needs to scroll in a horizontal fashion.
Any ideas? Is it possible to make UITableView operation horizontally?
Thanks!
You could use a large scrollview, and monitor for the scroll position to change. You can add images as subviews as they are coming into the actual viewable area, and remove them as they are scrolled away.
This way, you only have to have a small number of image views present at any given time, but you give the appearance of them all being "there".
You could even recycle the image views by changing their image and location so you are not creating and destroying complex objects. This is what UITableView does with cells.
It is as simple as appyling a Transform.
Here is the code. Write this in the your tableViewController init:
self.view.frame = CGRectMake(100,-5,250,350); //any Frame of your choice
CGAffineTransform trans = self.view.transform; // get current transform (i.e. portrait)
trans = CGAffineTransformRotate(trans, (M_PI / -2.0)); // rotate 90 degrees to go landscape
self.view.transform = trans; // set current transform (landscape)
But now what you need to realize is that your axis are also swapped. Any changes you make to the height will change the width (and vice versa) and any changes made to the origin.x changes the origin.y (and vice versa)
I agree with mbmcavoy, you can also take a look iPhone/iPad – AppStore like UIScrollView with paging and preview this article explains what you need about UIScrollView as well as provides a good example and source code.
Regards
Is it possible to add a UITableView horizontally? In a different orientation than the screen. Then you can use regular UITableView semantics where each row is an image that scrolls horizontally instead of vertically.
Posting an answer to this old thread because the validated answer is not correct. You may use a UITableViewController and its dequeueReusableCellWithIdentifier built-in features.
The trick is to make your tableview rotate and then make your cell rotate in the opposite direction.
in viewDidLoad you will add:
self.view.transform = CGAffineTransformMakeRotation(-M_PI_2);
And then in cellForRowAtIndexPath you would use:
cell.containerView.transform = CGAffineTransformMakeRotation(M_PI_2);
Make sure that every content of your cell is added to a container view (in this example containerView) so that you only have to rotate the container and not every cell subview.
Also please note that it will work better with cells having square dimensions, cause otherwise you may struggle with height/width computation.
I think I have the answer for you About scrolling an UITableView Horizontally:
create an UIScrollView (and disable
the vertical scroll)
put an UITableView inside the scroll
view
change the table view width as you
wish and update the
UIScrollView.contentSize as the
tableView width