I want to use a UIScrollView to display multiple UIViews as the user scrolls through the UIScrollView control. I'm not worried about showing Pagination just yet.
I already managed to implement some of it, but is not working the way I want it to.
Currently:
I have 3 ViewControllers with different nib files. The root view controller is the one with the UIScrollView, and it to load the rest of the view controllers.
NSMutableArray *controllers = [[NSMutableArray alloc] init];
for (unsigned i = 0; i < kNumberOfPages; i++) {
[controllers addObject:[NSNull null]];
}
self.viewControllers = controllers;
[controllers release];
scrollView.pagingEnabled = YES;
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * kNumberOfPages, scrollView.frame.size.height);
scrollView.showsHorizontalScrollIndicator = NO;
scrollView.showsVerticalScrollIndicator = NO;
scrollView.scrollsToTop = NO;
scrollView.delegate = self;
pageControl.numberOfPages = kNumberOfPages;
pageControl.currentPage = 0;
[self loadScrollViewWithPage:0];
- (void)loadScrollViewWithPage:(int)page {
if (page == 0) {
PageTwo *controller2 = [viewControllers objectAtIndex:page];
if ((NSNull *)controller2 == [NSNull null]) {
controller2 = [[PageTwo alloc] initWithPageNumber:page];
[viewControllers replaceObjectAtIndex:page withObject:controller2];
[controller2 release];
}
// add the controller's view to the scroll view
if (nil == controller2.view.superview) {
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0;
controller2.view.frame = frame;
[scrollView addSubview:controller2.view];
}
}
if (page == 1) {
// replace the placeholder if necessary
NewController *controller = [viewControllers objectAtIndex:page];
if ((NSNull *)controller == [NSNull null]) {
controller = [[NewController alloc] initWithPageNumber:page];
[viewControllers replaceObjectAtIndex:page withObject:controller];
[controller release];
}
// add the controller's view to the scroll view
if (nil == controller.view.superview) {
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0;
controller.view.frame = frame;
[scrollView addSubview:controller.view];
}
}
That's all fine... However, what I want it to do is to load the root nib first. But I'm not sure how to go about this. I know I have to increase the number of pages to 3, but when I initialize the controllers I don't know how to tell it that page == 0 should be the current view.
Any ideas?
Update:
Sigh, I overlooked something.. Didn't notice that if don't specify a page at level 0 it just shows the current view hah!
Silly me.
I overlooked something.. Didn't notice that if don't specify a page at level 0 it just shows the current view hah!
Related
I have a StoryBoard. In that there is a UIViewController - on that I have put UIScrollView and UIPageControl. And on each page (say I have 3) I am trying to load a UIViewController (that shows different labels and table values).
Problem - I am able to load everything up but the label and table is not visible. Thou when I try to change the background color for each page - it does gets changed. I have researched various posts and code but nothing seems to work / show UIScrollView with StoryBoard (with UIViewController displaying labels and table).
I am thou able to do this with loading a separate XIB file but then the whole segue gets interrupted.
Please see my code below.
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad
{
[super viewDidLoad];
//ScrollView and PageControl
// Create view controllers placeholder array
NSMutableArray *controllers = [[NSMutableArray alloc] init];
for (int i = 0; i < numberOfPages; i++)
{
[controllers addObject:[NSNull null]];
}
self.viewControllers = controllers;
// Set Scroll View
scrollView.pagingEnabled = YES;
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * numberOfPages, scrollView.frame.size.height);
scrollView.showsHorizontalScrollIndicator = NO;
scrollView.showsVerticalScrollIndicator = NO;
scrollView.scrollsToTop = NO;
scrollView.delegate = self;
// Set Page Control
pageControl.numberOfPages = numberOfPages;
pageControl.currentPage = 0;
// Load the visible and next page
[self loadScrollViewWithPage:0];
[self loadScrollViewWithPage:1];
}
- (void)scrollViewDidScroll:(UIScrollView *)sender
{
if (pageControlUsed)
{
// To know if scroll is valid - from Page Control
return;
}
// Changes Page Control indicator
CGFloat pageWidth = scrollView.frame.size.width;
int page = floor((scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
pageControl.currentPage = page;
// Load the visible, previous and next pages
[self loadScrollViewWithPage:page - 1];
[self loadScrollViewWithPage:page];
[self loadScrollViewWithPage:page + 1];
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
pageControlUsed = NO;
}
- (void)loadScrollViewWithPage:(int)page
{
if (page < 0) return;
if (page >= numberOfPages) return;
// Load new Controller
ReportsDetailViewController *controller = [viewControllers objectAtIndex:page];
if ((NSNull *)controller == [NSNull null])
{
controller = [[ReportsDetailViewController alloc] init];
controller.pageNumber = [NSNumber numberWithInt:page];
[viewControllers replaceObjectAtIndex:page withObject:controller];
}
if (nil == controller.view.superview)
{
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0;
controller.view.frame = frame;
[scrollView addSubview:controller.view];
}
}
Try to create your ViewControllers like this:
YourViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"YourViewController"];
You can set the identifier of your view controller in the attribute inspector.
I had the same problem today and this did the trick for me.
i want to remove currencyViews (tableViews) that are not more needed. i can change dynamically them, and if i had 6 currencyViews and now are 4 , those 2 (not showed) still exist. how can i remove them ?
- (void)loadScrollViewWithPage:(int)page {
if (page < 0) return;
if (page >= numberOfCurrencyViews) return;
// replace the placeholder if necessary
CurrencyViewController *controller = [self.currencyControllers objectAtIndex:page];
if ((NSNull *)controller == [NSNull null]) {
controller = [[CurrencyViewController alloc] initWithPageNumber:page];
controller.delegate = self;
[self.currencyControllers replaceObjectAtIndex:page withObject:controller];
[controller release];
}
// add the controller's view to the scroll view
if (nil == controller.view.superview)
{
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0;
controller.view.frame = frame;
[scrollView addSubview:controller.view];
}
}
You can remove a subview from a view using something to the extent of:
[controller.view removeFromSuperview];
[myScrollView.subviews makeObjectsPerformSelector:#selector(removeFromSuperview)];
I'm using a scrollview and a pagecontrol to render several images, very similar to the built-in photo app. I'm using this code to setup the controllers when the view loads:
NSMutableArray *controllers = [[NSMutableArray alloc] init];
for (unsigned i = 0; i < [photos count]; i++) {
[controllers addObject:[NSNull null]];
}
self.viewControllers = controllers;
[controllers release];
scrollArea.pagingEnabled = YES;
scrollArea.contentSize = CGSizeMake(newW * [photos count], newH);
scrollArea.showsHorizontalScrollIndicator = NO;
scrollArea.showsVerticalScrollIndicator = NO;
scrollArea.scrollsToTop = NO;
scrollArea.delegate = self;
pageControl.numberOfPages = [photos count];
newW and newH are just two variables I'm using to keep track of the size when the orientation changes.
My loadScrollViewWithPage method:
- (void) loadScrollViewWithPage:(int)page {
if (page < 0) return;
if (page >= [photos count]) return;
PhotoViewController *controller = [viewControllers objectAtIndex:page];
if ((NSNull *)controller == [NSNull null]) {
controller = [[PhotoViewController alloc] init];
controller.imgPath = (NSString *)[photos objectAtIndex:page];
[viewControllers replaceObjectAtIndex:page withObject:controller];
[controller release];
}
if (nil == controller.view.superview) {
CGRect frame = scrollArea.frame;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0;
controller.view.frame = frame;
[scrollArea addSubview:controller.view];
}
}
And here is my issue. I need to unload controllers/views when I need it. I thought I could use something like this:
for (unsigned i = 0; i < [photos count]; i++) {
[self.viewControllers replaceObjectAtIndex:i withObject:[NSNull null]];
}
I want to do it, so load completely new views into the scrollview.
I also tried something liek this with no luck:
for(UIView *subview in [self.scrollArea subviews]) {
[subview removeFromSuperview];
}
Any ideas?
So you want to make sure the subviews of scrollView stay in sync with the items in self.viewControllers as you update the latter? The easiest thing is to do something like this whenever you replace one of the items:
UIViewController *vc = [self.viewControllers objectAtIndex:i];
if ((NSNull *)vc != [NSNull null] && [vc isViewLoaded]) [vc.view removeFromSuperview];
[self.viewControllers replaceObjectAtIndex:i withObject:foo];
I have this page control code same from the apple sample. Here i have a subview (controller.view) which contains a ImageView. Now problem is with memory management. All works fine. But when i scroll 5-10 pages. RAM gets filled.
I tried to release the view+controller but did not find any proper place/way that work. I want to release the views which are not currently visible. (except current,previous & next view)
- (void)applicationDidFinishLaunching:(UIApplication *)application {
NSMutableArray *controllers = [[NSMutableArray alloc] init];
for (unsigned i = 0; i < kNumberOfPages; i++) {
[controllers addObject:[NSNull null]];
}
self.viewControllers = controllers;
[controllers release];
scrollView.pagingEnabled = YES;
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * kNumberOfPages, scrollView.frame.size.height);
scrollView.showsHorizontalScrollIndicator = NO;
scrollView.showsVerticalScrollIndicator = NO;
scrollView.scrollsToTop = NO;
scrollView.delegate = self;
pageControl.numberOfPages = kNumberOfPages;
pageControl.currentPage = 0;
[self loadScrollViewWithPage:0];
[self loadScrollViewWithPage:1];
}
- (void)loadScrollViewWithPage:(int)page {
if (page < 0) return;
if (page >= kNumberOfPages) return;
PageControlExampleViewControl *controller = [viewControllers objectAtIndex:page];
if ((NSNull *)controller == [NSNull null]) {
controller = [[PageControlExampleViewControl alloc] initWithPageNumber:page];
[viewControllers replaceObjectAtIndex:page withObject:controller];
[controller release];
}
if (nil == controller.view.superview) {
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0;
controller.view.frame = frame;
[scrollView addSubview:controller.view];
}
}
- (void)scrollViewDidScroll:(UIScrollView *)sender {
if (pageControlUsed) {
return;
}
CGFloat pageWidth = scrollView.frame.size.width;
int page = floor((scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
pageControl.currentPage = page;
[self loadScrollViewWithPage:page - 1];
[self loadScrollViewWithPage:page];
[self loadScrollViewWithPage:page + 1];
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
pageControlUsed = NO;
}
- (IBAction)changePage:(id)sender {
int page = pageControl.currentPage;
[self loadScrollViewWithPage:page - 1];
[self loadScrollViewWithPage:page];
[self loadScrollViewWithPage:page + 1];
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0;
[scrollView scrollRectToVisible:frame animated:YES];
pageControlUsed = YES;
}
I tried for several hours the code of Deepmist. It did the job but I received memory warnings and my app crashed every 25-30 pages scrolled (I am using big images in the pages). In Instruments I noticed a very large use of memory: even if the unnecessary views were removed time by time from superview and the relative viewControllers were replaced with NSNulls, Instruments showed that real memory increased on every pagescroll of 4-5MB!
Searching on the web I found that this is a common problem. If you also have this problem, you should try the following checks:
1) in each view, be sure to use imageWithContentsOfFile instead of imageNamed. As documented imageNamed cache images and increase memory size.
2) in the Deepmist code, after:
[controller.view removeFromSuperview];
you also have to set to nil the view:
controller.view=nil;
This trick solved the memory consumption which now is stable for the only three views loaded (current, current-1 and current+1 to avoid flashing in the pagescroll).
Hope this helps!
I didn't test this but I imagine you could write a method that does the opposite of loading like so:
- (void)unloadScrollViewWithPage:(int)page {
if (page < 0) return;
if (page >= kNumberOfPages) return;
PageControlExampleViewControl *controller = [viewControllers objectAtIndex:page];
if ((NSNull *)controller != [NSNull null]) {
if (nil != controller.view.superview)
[controller.view removeFromSuperview];
[viewControllers replaceObjectAtIndex:page withObject:[NSNull null]];
}
}
Then add some code to your didScroll method like so:
[self unloadScrollViewWithPage:page - 2];
[self loadScrollViewWithPage:page - 1];
[self loadScrollViewWithPage:page];
[self loadScrollViewWithPage:page + 1];
[self unloadScrollViewWithPage:page + 2];
in loadScrollViewWithPage method, just before if (nil == controller.view.superview), cycle trough all views and remove them all except current -1, current and current + 1 but only if method was called with current index view. Also, don't forget to replace those view controllers in viewControllers array with NSNulls.
try this VSScrollview , it reuses its views like UITableview reuses its cell.
Ok, so I am trying to a pagescrollView with two views each with a different view controller so I can work on each different view. I got apple's sample code and it seems that they created a lot of different view "lazily as they said" how can this code be altered so that I can have the first page be one view controller and the second page be another view controller?
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// view controllers are created lazily
// in the meantime, load the array with placeholders which will be replaced on demand
NSMutableArray *controllers = [[NSMutableArray alloc] init];
for (unsigned i = 0; i < kNumberOfPages; i++) {
[controllers addObject:[NSNull null]];
}
self.viewControllers = controllers;
[controllers release];
// a page is the width of the scroll view
scrollView.pagingEnabled = YES;
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * kNumberOfPages, scrollView.frame.size.height);
scrollView.showsHorizontalScrollIndicator = NO;
scrollView.showsVerticalScrollIndicator = NO;
scrollView.scrollsToTop = NO;
scrollView.delegate = self;
pageControl.numberOfPages = kNumberOfPages;
pageControl.currentPage = 0;
// pages are created on demand
// load the visible page
// load the page on either side to avoid flashes when the user starts scrolling
[self loadScrollViewWithPage:0];
[self loadScrollViewWithPage:1];
}
- (void)loadScrollViewWithPage:(int)page {
if (page < 0) return;
if (page >= kNumberOfPages) return;
// replace the placeholder if necessary
MyViewController *controller = [viewControllers objectAtIndex:page];
if ((NSNull *)controller == [NSNull null]) {
controller = [[MyViewController alloc] initWithPageNumber:page];
[viewControllers replaceObjectAtIndex:page withObject:controller];
[controller release];
}
// add the controller's view to the scroll view
if (nil == controller.view.superview) {
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0;
controller.view.frame = frame;
[scrollView addSubview:controller.view];
}
}
- (void)scrollViewDidScroll:(UIScrollView *)sender {
// We don't want a "feedback loop" between the UIPageControl and the scroll delegate in
// which a scroll event generated from the user hitting the page control triggers updates from
// the delegate method. We use a boolean to disable the delegate logic when the page control is used.
if (pageControlUsed) {
// do nothing - the scroll was initiated from the page control, not the user dragging
return;
}
// Switch the indicator when more than 50% of the previous/next page is visible
CGFloat pageWidth = scrollView.frame.size.width;
int page = floor((scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
pageControl.currentPage = page;
// load the visible page and the page on either side of it (to avoid flashes when the user starts scrolling)
[self loadScrollViewWithPage:page - 1];
[self loadScrollViewWithPage:page];
[self loadScrollViewWithPage:page + 1];
// A possible optimization would be to unload the views+controllers which are no longer visible
}
// At the end of scroll animation, reset the boolean used when scrolls originate from the UIPageControl
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
pageControlUsed = NO;
}
- (IBAction)changePage:(id)sender {
int page = pageControl.currentPage;
// load the visible page and the page on either side of it (to avoid flashes when the user starts scrolling)
[self loadScrollViewWithPage:page - 1];
[self loadScrollViewWithPage:page];
[self loadScrollViewWithPage:page + 1];
// update the scroll view to the appropriate page
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0;
[scrollView scrollRectToVisible:frame animated:YES];
// Set the boolean used when scrolls originate from the UIPageControl. See scrollViewDidScroll: above.
pageControlUsed = YES;
}
Thanks if anyone knows how to do this.
To load the pages with different view controllers all you have to change is this segment of code
MyViewController *controller = [viewControllers objectAtIndex:page];
if ((NSNull *)controller == [NSNull null])
{ controller = [[MyViewController alloc] initWithPageNumber:page];
[viewControllers replaceObjectAtIndex:page withObject:controller];
[controller release];
}
to initialize each view controller to the one you want instead of the same one each time, so the code would look something like
if(page==0)
{
MyViewControllerZero *controller = [viewControllers objectAtIndex:page];
if ((NSNull *)controller == [NSNull null])
{ controller = [[MyViewControllerZero alloc] initWithPageNumber:page];
[viewControllers replaceObjectAtIndex:page withObject:controller];
[controller release];
}
}
if(page==1)
{
MyViewControllerOne *controller = [viewControllers objectAtIndex:page];
if ((NSNull *)controller == [NSNull null])
{ controller = [[MyViewControllerOne alloc] initWithPageNumber:page];
[viewControllers replaceObjectAtIndex:page withObject:controller];
[controller release];
}
}
//and so on ...
This is the Correct Code!!!
- (void)loadScrollViewWithPage:(int)page {
if (page < 0) return;
if (page >= kNumberOfPages) return;
// replace the placeholder if necessary
if (page == 0){
MyViewController *controller = [viewControllers objectAtIndex:page];
if ((NSNull *)controller == [NSNull null]) {
controller = [[MyViewController alloc] initWithPageNumber:page];
[viewControllers replaceObjectAtIndex:page withObject:controller];
}
if (nil == controller.view.superview) {
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0;
controller.view.frame = frame;
[scrollView addSubview:controller.view];
}
}
if (page == 1){
PageOne *controller = [viewControllers objectAtIndex:page];
if ((NSNull *)controller == [NSNull null]) {
controller = [[PageOne alloc] initWithPageNumber:page];
[viewControllers replaceObjectAtIndex:page withObject:controller];
}
if (nil == controller.view.superview) {
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0;
controller.view.frame = frame;
[scrollView addSubview:controller.view];
}
// add the controller's view to the scroll view
}
}
THX For the first answewr she open my mind!!!