I'd like some assistance to properly remove subviews from a scrollview, to reduce memory usage. Every time the user scrolls, I ask to grab page, page -1 and page+1 to get the views loaded in the scrollview.
Now my issue is that if there's ten views side by side, when you reach the tenth view, all the previous 9 views are still subviews in the scrollview and in memory. I'd like to reverse load these views as well. Here's what I have so far:
- (void)loadScrollViewWithPage:(int)page {
if (page < 0) return;
if (page >= [self favouritesCount]) return;
// replace the placeholder if necessary
MyObject *myObj = (MyObj *)[fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:page inSection:0]];
MyView *controller = [viewControllers objectAtIndex:page];
if ((NSNull *)controller == [NSNull null]) {
controller = [[MyView alloc] initWithObject:myObj];
controller.delegate = self;
[viewControllers replaceObjectAtIndex:page withObject:controller];
}
// add the controller's view to the scroll view
if (nil == controller.superview) {
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0;
controller.frame = frame;
[scrollView addSubview:controller];
}
}
Now I have tried something like:
if (page > 2) {
[(UIView *)[[scrollView subviews] objectAtIndex:(page - 2)] removeFromSuperview];
}
But that just crashes it. It's probably a simple solution, so help is appreciated :)
Did you increment the pageCount?
You can try with this as well:
MyView *controller = [viewControllers objectAtIndex:page];
if ((NSNull *)controller != [NSNull null]) {
[controller removeFromSuperView];
}
Some notes for naming convention: MyView *controller does not make sense. If MyView is subclass of UIView, you should only name if MyView *view
I think if I were you, I would just add "loadData" and "releaseData" methods on my views, and call those when they're coming in and out of view. Sure, you'll still have the view classes loaded, but in those methods you can get rid of (and re-load) the more heavyweight sub-objects that those views contain.
Good luck,
Blake.
I think your problem is in the solution you posted:
[(UIView *)[[scrollView subviews] objectAtIndex:(page - 2)] removeFromSuperview];
If you remove the previously loaded views, you always have 2 views loaded, so you cant access page-2 because you don't have page-2 views loaded in the scrollview. (think for example when page is 5, you will access view 3 from scrollview)
You should consider 2 things:
1.- Use [[[viewControllers objectAtIndex:page-2] view] removeFromSuperview]; instead (take a look that here I use viewcontrollers instead of scroll.subviews)
2.- Remove the view from the viewcontrollers array , because just removing the view from the scroll will not release the view from memory (its retained by the array). For this, use something like:
[viewControllers replaceObjectAtIndex:page-2 withObject:[NSNull null]];
Related
I have UIView in my storyboard with outlet mynewView and i want it display with totally different content. And do it many times.
My UIView contains many objects, i load it from internet. But every time i do
NSArray *viewsToRemove = [view1 mynewView];
for (UIView *v in viewsToRemove) {
[v removeFromSuperview];
}
//Let's try to remove all possible views..
viewsToRemove = [self.view subviews];
for (UIView *v in viewsToRemove) {
[v removeFromSuperview];
}
//here i create mynewView
mynewView = [self createnewRandomView];
//And finally add the View we have created.
[self.view addSubview:mynewView];
i monitor the memory consumption and i see it grows every time i add new UIView. Even when i remove all superviews. I guess they remain in memory.
Before i change the view i remove from superview all possible views, but they still stay in memory.
Xcode is set to use ARC so i can't release it.
Is there any good way to reuse a UIView?
Thanks
[self.view addSubview:mynewView];
Your newView was added as subview of self.view.
Your above code does not use for remove yourVewView in self.view.
Please try this code before adding new view:
for (UIView *view in self.view) {
if([view isKindOfClass:[YourNewViewClassName class]])
{
[view removeFromSuperView];
break;
}
}
As you remove the subviews, set them to nil as you go, like so:
for (UIView *v in viewsToRemove) {
[v removeFromSuperview];
v = nil;
}
For more information, see for example Arc: Setting references to nil, for multiple buttons
I've been scratching my head for a long time now, trying to solve this problem. I've searched StackOverflow, and have found people asking for something that resembles my problem like this question and this question, but none of the answers given have helped me.
To explain the problem in detail:
I've used Apple's Paging example to enable paging between view controllers
The View hierachy is as follows: UIWindow -> UIScrollView (MainController) -> UIViewControllers.
I use this code to create a delegate to the Viewcontroller containing the UISCrollview:
if(page == 0)
{
ContractsViewController *controller = [viewControllers objectAtIndex:page];
if ((NSNull *) controller == [NSNull null])
{
controller = [[ContractsViewController alloc] initWithNibName:#"ContractsView" bundle:nil];
controller.delegate = self;
[viewControllers replaceObjectAtIndex:page withObject:controller];
[controller release];
}
// add the controller's view to the scroll view
if (controller.view.superview == nil)
{
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0;
controller.view.frame = frame;
controller.view.tag = 0;
[scrollView addSubview:controller.view];
}
}
The problem then occurs, when I try to present a modalviewcontroller from my ViewController inside the scrollview using the delegate. It works a few times, but then gives me a EXC_BAD_ACCESS. I've also tried posting a notification, and creating a listener in the MainController, to present it that way, but still the same problem.
When testing in iOS 4.3 everything works like a charm, but in iOS5 I get the issue.
I hope someone can help me get rid of this issue.
Thank you in advance.
As a start thank you for all your answers! I've finally found the solution to the problem.
As mentioned in my question, I used Apple's Pagecontrol sample as a template for my project. This template has a rootViewController which has an XIB with a window containing an UIScrollview and a Pagecontrol.
The problem happened, when I was trying to present the ModalView from a UIViewController inside the scrollview. I wanted to use the delegate, to present it through the rootViewController, but found out that it was using the UIScrollview to present it.
The UIScrollView object was apperently not retained, so after a couple of dismisses I hit the EXC_BAD_ACCESS.
I then figured, that i needed an UIView in the rootViewController, and placed it in the bottom viewhierachy in the XIB, and connected it with the view of the rootViewController. After I did that, I could use [delegate present..] and [delegate dismiss..] in the UIViewController without the EXC_BAD_ACCESS.
Yay! :)
I suggest removing your [controller release]; and adding an autorelease: controller = [[[ContractsViewController alloc] initWithNibName:#"ContractsView" bundle:nil] autorelease];
You don't own controller when it's returned from objectAtIndex:, so that way you could have the same behavior for both controllers, no releases.
I think I see your problem.. in the first if you release controller object and then you try to use it in the second if loop because of which you recieve this. release the controller object after both the if loops have passed.
EDIT:
try this code instead..
if(page == 0)
{
ContractsViewController *controller = [viewControllers objectAtIndex:page];
if ((NSNull *) controller == [NSNull null])
{
controller = [[[ContractsViewController alloc] initWithNibName:#"ContractsView" bundle:nil]autorelease];
controller.delegate = self;
[viewControllers replaceObjectAtIndex:page withObject:controller];
}
// add the controller's view to the scroll view
if (controller.view.superview == nil)
{
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0;
controller.view.frame = frame;
controller.view.tag = 0;
[scrollView addSubview:controller.view];
}
}
Try running the code with NSZombiesEnabled.
My guess is that the viewControllers object is either never initialised or is being released prematurely.
Also, there's this if (page == 0) wierdness:
if(page == 0) // <== only the first page ever gets the init code calling
{
ContractsViewController *controller = [viewControllers objectAtIndex:page];
if ((NSNull *) controller == [NSNull null])
{
// ** SNIP **
[viewControllers replaceObjectAtIndex:page // <<=== always 0
withObject:controller];
[controller release];
}
// ** SNIP **
}
Does anyone know what am I missing in order to add manually my own view controllers to the UIPageViewController methods?
I currently have this and I do not know how to proceed:
NSDictionary *pageViewOptions = [NSDictionary dictionaryWithObjectsAndKeys:UIPageViewControllerOptionSpineLocationKey, [NSNumber numberWithInteger:UIPageViewControllerSpineLocationMin], nil];
self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:pageViewOptions];
BookViewController *pageOne = [self.storyboard instantiateViewControllerWithIdentifier:#"BookViewController"];
AnotherPage *pageTwo = [self.storyboard instantiateViewControllerWithIdentifier:#"AnotherPage"];
PageThree *pageThree = [self.storyboard instantiateViewControllerWithIdentifier:#"PageThree"];
self.pageViewController.delegate = self;
self.pageViewController.dataSource = self;
[self.pageViewController setViewControllers:[NSArray arrayWithObjects:pageOne, pageTwo, pageThree, nil] direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil];
[self addChildViewController:self.pageViewController];
[self.view addSubview:self.pageViewController.view];
self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;
[self.pageViewController didMoveToParentViewController:self];
But it does not seem to work. I only see the RootViewController.
Thanks to all of you who like to help eager newbies like me, in advance.... :)
The way you've set this up isn't quite how UIPageViewController works.
The first thing to understand is that setViewControllers:direction:animated:completion only sets the visible view controllers, not all the view controllers you may wish to display to the user. (To wit, if I try to set an array of three controllers as in your code, I get an exception.)
Secondly, if you have more than one visible page (or two, depending on spine location), you must implement the UIPageViewControllerDataSource protocol to provide the view controllers the page view controller will display.
Here's a short example (implemented as a subclass of UIPageViewController, but would work with a child page view controller, too... just replace self.dataSource = ... with myPageViewController.dataSource = ...)
#implementation MyPageViewController {
// we keep our page view controllers in an array, but we could just as easily
// instantiate them on the fly in the data source methods
NSArray *_pages;
}
- (void)setupPages {
/*
* set up three pages, each with a different background color
*/
UIViewController *a = [[UIViewController alloc] initWithNibName:nil bundle:nil];
a.view.backgroundColor = [UIColor redColor];
UIViewController *b = [[UIViewController alloc] initWithNibName:nil bundle:nil];
b.view.backgroundColor = [UIColor greenColor];
UIViewController *c = [[UIViewController alloc] initWithNibName:nil bundle:nil];
c.view.backgroundColor = [UIColor blueColor];
_pages = #[a, b, c];
}
- (void)viewDidLoad {
[super viewDidLoad];
[self setupPages];
self.dataSource = self;
// set the initially visible page's view controller... if you don't do this
// you won't see anything.
[self setViewControllers:#[_pages[0]] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:^(BOOL finished) {
}];
}
#pragma mark - UIPageViewControllerDataSource
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController {
if (nil == viewController) {
return _pages[0];
}
NSInteger idx = [_pages indexOfObject:viewController];
NSParameterAssert(idx != NSNotFound);
if (idx >= [_pages count] - 1) {
// we're at the end of the _pages array
return nil;
}
// return the next page's view controller
return _pages[idx + 1];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {
if (nil == viewController) {
return _pages[0];
}
NSInteger idx = [_pages indexOfObject:viewController];
NSParameterAssert(idx != NSNotFound);
if (idx <= 0) {
// we're at the end of the _pages array
return nil;
}
// return the previous page's view controller
return _pages[idx - 1];
}
I see a few problems here. I'm a bit of a noob myself, so I don't know if this will fix everything in your code.
You need a UIPageViewController and then views for the individual pages that are instantiated when required. Passing an array of views isn't efficient (uses too much memory).
You need to implement the UIPageViewControllerDataSource protocol in your a class that sub-classes from UIPageViewController. Most (but not all) of the code in your snippet will go here. See the class reference or this tutorial for an explanation.
You're probably doing more work than you need to with the gesture recognizers. If you use IBOutlet, you can do the plumbing through the interface builder.
Hope this helps.
Try this function:
setViewControllers:direction:animated:completion:
Sets the view controllers to be displayed.
- (void)setViewControllers:(NSArray *)viewControllers direction:(UIPageViewControllerNavigationDirection)direction animated:(BOOL)animated completion:(void (^)(BOOL finished))completion
Parameters
viewControllers: The view controller or view controllers to be displayed.
direction: The navigation direction.
animated: A Boolean value that indicates whether the transition is to be animated.
completion: A block to be called when the page-turn animation completes.
The block takes the following parameters:
finished
YES if the animation finished; NO if it was skipped.
I'm looking through an example in the iPhone Beginning programming book and they have code to switch between two views when a button is pressed. Here's the first snippet from their example code:
if (self.yellowViewController.view.superview == nil)
{
if (self.yellowViewController == nil)
{
YellowViewController *yellowController =
[[YellowViewController alloc] initWithNibName:#"YellowView"
bundle:nil];
self.yellowViewController = yellowController;
[yellowController release];
}
[blueViewController.view removeFromSuperview];
[self.view insertSubview:yellowViewController.view atIndex:0];
}
else
{
if (self.blueViewController == nil)
{
BlueViewController *blueController =
[[BlueViewController alloc] initWithNibName:#"BlueView"
bundle:nil];
self.blueViewController = blueController;
[blueController release];
}
[yellowViewController.view removeFromSuperview];
[self.view insertSubview:blueViewController.view atIndex:0];
}
It does make sense to me, but the question I have is, how would you do this with a UISegmentControl that has four views. I know you can check for the selectedSegment and create that view when needed. But how would I know what the last view was in order to remove it from the superview beforing adding my new view as a subview? Thanks!
while creating each view either code or IB set tag value to segmentIndex.so u can get them later by that tag value.this is tricky and simple.
You could check to see which view is allocated or not nil and then remove.
if (yellowController) {
[yellowController.view removeFromSuperView];
[yellowController release];
}
You could go through your four views to determine which one is loaded and then remove the view.
For any UIView the frontmost subview is [[myView subviews] lastObject].
I want to remove the current subview (added atIndex:0) from my root view controller and insert a new view in its place.
Currently I can only find code snippets that show unloading a view using removeFromSuperView, which requires you to know what viewcontrollers view is currently loaded.
The only other thing to note is that I have another subview inserted in the rootcontrollers view which i dont want unloaded. So code that removes any and all subviews is not adequate
Thanks,
m
if(self.firstscr == nil)
{
firstscreen *f = [[firstscreen alloc] initWithNibName:#"firstscreenview" bundle:nil];
self.firstscr = f;
[f release];
}
///This is my attempt at getting to the currently loaded view :P
[[self.view subviews atIndex:0].view removeFromSuperView];
[self.view insertSubview:firstscr.view atIndex:0];
Here is how to do it (i figured it out, Yey me!)
if(self.firstscr == nil)
{
firstscreen *f = [[firstscreen alloc] initWithNibName:#"firstscreenview" bundle:nil];
self.firstscr = f;
[f release];
}
//Remove whatever view is currently loaded at index 0, this index is only to be used by "page" views
UIView *v = [self.view.subviews objectAtIndex:0];
[v removeFromSuperview];
[self.view insertSubview:firstscr.view atIndex:0];