page controller - iphone

I have a problem with removing unused pages from an array:
NSMutableArray *controllers = [[NSMutableArray alloc] init];
for (unsigned i = 0; i < [descriptionsList count]; i++) {
[controllers addObject:[NSNull null]];
}
self.viewControllers = controllers;
[controllers release];
[self loadScrollViewWithPage:0];
[self loadScrollViewWithPage:1];
i'm adding objects with:
- (void)loadScrollViewWithPage:(int)page {
if (page < 0) return;
if (page >= [descriptionsList count]) return;
// replace the placeholder if necessary
DetailsView *controller = [viewControllers objectAtIndex:page];
if ((NSNull *)controller == [NSNull null]) {
controller = [[DetailsView alloc] initWithElement:[descriptionsList objectAtIndex:page]
andFrame:CGRectMake(320*page, 0, 320, 420)];
[viewControllers replaceObjectAtIndex:page withObject:controller];
[controller release];
}
// add the controller's view to the scroll view
if (nil == controller.superview) {
[scrollView addSubview:controller];
}
}
and i'm using this to remove and create pages:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
pageControlUsed = NO;
//load the visible page and the page on either side of it (to avoid flashes when the user starts scrolling)
for (unsigned i = 0; i < [descriptionsList count]; i++) {
if (i < pageController.currentPage - 1 || i > pageController.currentPage + 1) {
if ([viewControllers objectAtIndex:i] != nil) {
[[viewControllers objectAtIndex:i] release];
[viewControllers replaceObjectAtIndex:i withObject:[NSNull null]];
}
}
else {
[self loadScrollViewWithPage:i];
}
}
}
My app is crashing big time when i want to view page 3. Any advice on how should this be done? Thanks.

A couple of problems:
NSArrays can't store 'nil' objects, so your check for != nil will always succeed, so you don't need it
You definitely should not be releasing the object in the array; You don't have a corresponding -retain message, and regardless, the array will automatically retain objects put into it, and release them when they're removed
your nomenclature is a little confusing. You have an array called viewControllers, and an objected called controller, but these both appear to be views (since you're initWithFrame'ing them.

This line:
if ([viewControllers objectAtIndex:i] != nil)
will always evaluate as TRUE because the array is populated with NSNULL objects which do not evaluate to nil. The block executes even when there is a view stored at index. This block will populate your entire array with NSNull objects, wiping out all your views. Any subsequent call to the view will crash.
I think you've got a bad design here. You shouldn't be putting views into an Array. Instead, you need to have your data in an array and then populate reusable views based on what data should be displayed at any given time. Look at how 'UITable' displays itself with reusable 'UITableViewCells'.

This approach with lazy loading i've got it from a good book in fact, but the sample was with very very simple views, and without releasing them. The pages initialization was made in the scrollViewDidScroll method, which was a total mess on the device with my content: a photo and 2 texts. And the memory used is crashing my app, that's why i want to keep loaded only 3 pages. Here's the updated code, but i can't release the object nor remove from view, so i get duplicates.
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
for (unsigned i = 0; i < [descriptionsList count]; i++) {
if (i < pageController.currentPage - 1 || i > pageController.currentPage + 1) {
DetailsView *controller = [viewControllers objectAtIndex:i];
if ((NSNull *)controller != [NSNull null]) {
if (nil != controller.superview) {
NSLog(#"remove from superview %d", i);
//[controller.superview removeFromSuperview];
}
[viewControllers removeObjectAtIndex:i];
[viewControllers insertObject:[NSNull null] atIndex:i];
//[viewControllers replaceObjectAtIndex:i withObject:[NSNull null]];
}
}
else {
NSLog(#"allocating %d", i);
[self loadScrollViewWithPage:i];
}
}
}
So, will i be able to create my views in real time without flashes if i'm using only 2 reusable views? I've saw a sample with 2 views but said that the content must exist, and i'm not sure how good is to keep in memory about 15 pngs.

Related

Unloading viewControllers from UIPageViewController

I got a UIPageViewController where I add other viewControllers. Those ViewControllers are in the array viewControllersArray = [[NSMutableArray alloc] init]; I add viewControllers on this array like this:
[viewControllersArray addObject: infoViewController];
After the viewControllers are added on my array:
NSArray *initialViewControllers = [[NSArray alloc] initWithObjects:[viewControllersArray objectAtIndex:0], nil];
[self setViewControllers:initialViewControllers
direction:UIPageViewControllerNavigationDirectionForward
animated:YES
completion:^(BOOL finished){
NSLog(#"call back success");}];
This above code is all done in the viewDidLoad from my UIPageViewController.
For loading this array I got those functions form <UIPageViewControllerDataSource>
which I use like this:
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController {
if ([viewControllersArray containsObject:viewController]) {
NSInteger index = [viewControllersArray indexOfObject:viewController];
if (index < [viewControllersArray count] && index > 0) {
return [viewControllersArray objectAtIndex:(index - 1)];
}
}
return nil;
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController {
if ([viewControllersArray containsObject:viewController]) {
NSInteger index = [viewControllersArray indexOfObject:viewController];
if (index < [viewControllersArray count] - 1) {
return [viewControllersArray objectAtIndex:(index + 1)];
}
}
return nil;
}
Now what the problem is, is that when you swipe, the next viewcontrollers gets load, etc. But they don't get unloaded. So I'm searching for a way to unload the viewcontrollers that you already passed to save memory, and when you swipe back they will get reloaded.
your problem is that you hold on to your view controllers in the viewControllersArray. The array holds a strong reference to each object that you add to it. The easiest way to save memory is to just create the requested view controller on the fly in both the pageViewController:viewControllerBeforeViewController: and the pageViewController:viewControllerAfterViewController: data source methods. That way, only the page view controller will have a strong reference to the view controller and will release (dealloc) it once it is moved off-screen.
If it's too expensive to create them on-the-fly, you might consider just keeping 3 view controllers in the array: the one currently displayed and the one to the immediate left and the immediate right.
Example:
- (UIViewController *)pageViewController:(UIPageViewController *)pvc
viewControllerBeforeViewController:(MyGreatViewController *)vc
{
NSUInteger index = vc.position - 1; //custom property on your VC
if (index > 0) {
return [[MyGreatViewController alloc] initWithPosition:index];
}
return nil;
}
- (UIViewController *)pageViewController:(UIPageViewController *)pvc
viewControllerBeforeViewController:(MyGreatViewController *)vc
{
NSUInteger index = vc.position + 1; //custom property on your VC
if (index <= MAX_POSITION) {
return [[MyGreatViewController alloc] initWithPosition:index];
}
return nil;
}
Enjoy!
You should not unload View Controller. If memory is low, controllers unload their views. In code, use -(void)viewDidUnload and -(void)didReceiveMemoryWarning (iOS 6) to free your resources. When UIPageViewController will show some page, controller recreate unloaded view and call -(void)viewDidLoad (if you customize your controllers manually).

RTL languages uipageviewcontroller animation

I know this question is typique and it was asked many times in the forum, but I still cannot solve my problem, so please if any body can help be that would be GREAT :)
I am creating a book application in the arabic languages and I need to perform the transitions of the uipageviewcontroller from right to left. And thats all I have to say.
One more thing (if I hadn't explain very well my self) I have the exact need as this thread: How to change UIPageViewController direction of paging curl animation but I couldn't manage to make the solution they spoke about, so if someone can explain me or give me a link where I can have how to do it that would be more than enough :)
It can be done in this way
Swap the code of pageViewController's datasource from viewControllerBeforeViewController to viewControllerAfterViewController
Change UIPageViewControllerSpineLocationMin to UIPageViewControllerSpineLocationMax
To check that, start Page-Based Application template as Universal and change the following in ModelController.m
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
NSUInteger index = [self indexOfViewController:(DataViewController *)viewController];
if (index == NSNotFound) {
return nil;
}
index++;
if (index == [self.pageData count]) {
return nil;
}
return [self viewControllerAtIndex:index storyboard:viewController.storyboard];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
NSUInteger index = [self indexOfViewController:(DataViewController *)viewController];
if ((index == 0) || (index == NSNotFound)) {
return nil;
}
index--;
return [self viewControllerAtIndex:index storyboard:viewController.storyboard];
}
and change UIPageViewControllerSpineLocationMin to UIPageViewControllerSpineLocationMax and swipe the condition of (indexOfCurrentViewController % 2 == 0) in "RootViewController.m"
- (UIPageViewControllerSpineLocation)pageViewController:(UIPageViewController *)pageViewController spineLocationForInterfaceOrientation:(UIInterfaceOrientation)orientation
{
if (UIInterfaceOrientationIsPortrait(orientation) || ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)) {
UIViewController *currentViewController = [self.pageViewController.viewControllers objectAtIndex:0];
NSArray *viewControllers = [NSArray arrayWithObject:currentViewController];
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:NULL];
self.pageViewController.doubleSided = NO;
return UIPageViewControllerSpineLocationMax;
}
DataViewController *currentViewController = [self.pageViewController.viewControllers objectAtIndex:0];
NSArray *viewControllers = nil;
NSUInteger indexOfCurrentViewController = [self.modelController indexOfViewController:currentViewController];
if (indexOfCurrentViewController == 0 || indexOfCurrentViewController % 2 == 0) {
UIViewController *previousViewController = [self.modelController pageViewController:self.pageViewController viewControllerBeforeViewController:currentViewController];
viewControllers = [NSArray arrayWithObjects:previousViewController, currentViewController, nil];
} else {
UIViewController *nextViewController = [self.modelController pageViewController:self.pageViewController viewControllerAfterViewController:currentViewController];
viewControllers = [NSArray arrayWithObjects:currentViewController, nextViewController, nil];
}
[self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:NULL];
return UIPageViewControllerSpineLocationMid;
}
Source: PageViewControllers Apple Doc
You should dive into core graphics and core animation.
Make 2 layers (a previous/next and current)
When doing a 'pangesture' (see the Event Handling Guide ) you need to see if it's a swipe left or swipe right (previous/next page)
Then perform a 3D Rotation on the layer(s)
here's a nice example about flipping pages
Madev, Do you not want to use the "PageBased Application" template?
Simply start a new xCode Project:
Under iOS > Application , look for Page-Based Application. Hit Okay, Choose you Options. All you have to do is supply your content (via the "DataSource").
Now that is the complicated part...But here's some tips
http://www.techotopia.com/index.php/Implementing_a_Page_based_iOS_5_iPhone_Application_using_UIPageViewController
http://www.techotopia.com/index.php/An_Example_iOS_5_iPhone_UIPageViewController_Application
You can set the semanticContentAttribute on your UIPageViewController's viewDidLoad like:
if <You app language is Arabic/RTL based> {
self.view.semanticContentAttribute = .forceRightToLeft
}else {
self.view.semanticContentAttribute = .forceLeftToRight
}
Keep coding......... :)

How to count subviews in Super View

I have one class in which i have two method _addView() and _removeView().
And these method i am using in another class for adding view and removing view but its not working.If i am using in the same then its working.
For Remove-
- (id)deleteBoxAtIndex:(int)boxIndex{
for (int i = 0; i < [[self subviews] count]; i++ ) {
[[[self subviews] objectAtIndex:boxIndex] removeFromSuperview];
}
return self;
}
Then how i count the subviews in that class or remove from that class.
You correct in trying to use [self.subviews count] to count the number of subviews, but there is an elegant way to remove all subviews from a view in Objective-C. Try this:
[self.subviews makeObjectsPerformSelector:#selector(removeFromSuperView)];
You should pass a pointer to the UIView instance (the one that has got the subviews) to the other object so that you can call:
myView.subviews
I don't know if this could work for you:
- (id)deleteBoxAtIndex:(int)boxIndex fromView:(UIView*)view {
for (int i = 0; i < [[view subviews] count]; i++ ) {
[[[view subviews] objectAtIndex:boxIndex] removeFromSuperview];
}
return self;
}
If you give more detail about the relationship between the two classes you mention (basically, the names and how one interact with the other), I could give you more hints.
Your problem not in retrieving the count but in fact that your relay on a count that changes dynamically inside of your for loop logic execution. Do cleanup of subviews in the following way:
while([[self subviews] count] > 0) {
UIView *view = [[self subviews] objectAtIndex:0];
[view removeFromSuperview];
}
You can use the superview property:
[[self superview] subviews]
to do the similar loop you are doing right row. However I'd strongly encourage you to use
[[self superview] viewWithTag:boxIndex]
instead of objectAtIndex: method
Just do a loop of all subViews u want to remove:
for (UIView *subs in [self.view subviews]){
[subs removeFromSuperview];
}

Possible to explain code flow in this example ?? Memory leak? Where?

Referring to this PageControl example could somebody please explain the code flow? Instruments is giving me a leak here so looking for some help.
Re: this tutorial:
http://www.edumobile.org/iphone/iphone-programming-tutorials/pagecontrol-example-in-iphone/
We init an array to Null objects in our AppDidFinishLaunching method...
NSMutableArray *controllers = [[NSMutableArray alloc] init];
for (unsigned i = 0; i < kNumberOfPages; i++) {
[controllers addObject:[NSNull null]];
}
self.viewControllers = controllers;
[controllers release];
and then call:
[self loadScrollViewWithPage:0];
[self loadScrollViewWithPage:1];
Here is the implementation for loadScrollViewWithPage:
- (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];
}
}
Instruments is giving me a leak in this implementation on the following line:
if (nil == controller.view.superview) {
Anyone know why this would be a reported leak in Instruments? My code is identical.
Also after the initial call [self loadScrollViewWithPage:0];, on the first pass through and creating the object, BOTH if clauses are passed and entered.
How is this possible? If we enter the first if clause, we alloc and create our controller and end by **releasing* it ([controller release]).
Shouldn't the next line (if (nil == controller.view.superview)) produce an EXC_BAD_ACCESS error seeing as we just RELEASED controller above?
SCREENSHOT FROM INSTRUMENTS:
I don't know why Instruments would be reporting a leak on that line, unless it's just noticing that controller.view was allocated by that line (accessing a UIViewController's view property automatically loads the view if necessary) and not yet freed (which it shouldn't be as long as scrollView exists and controller.view remains as a subview of it).
It is correct that it goes through both if clauses. The first if checks whether a view controller actually exists for that page index, and if not it creates one (but does not add it to the scrollView). The second checks if the view for the view controller for the page index has already been added to the scrollView, and if not it adds it.
The reason it does not crash is because [viewControllers replaceObjectAtIndex:page withObject:controller] adds the controller to an NSMutableArray, which retains the controller. It might be slightly less confusing to do it like this instead:
if ((NSNull *)controller == [NSNull null]) {
controller = [[[PageControlExampleViewControl alloc] initWithPageNumber:page] autorelease];
[viewControllers replaceObjectAtIndex:page withObject:controller];
}
It seems to me that you are not properly releasing the scrollView.
How is this possible? If we enter the first if clause, we alloc and create our controller and end by *releasing it ([controller release]).
Shouldn't the next line (if (nil == controller.view.superview)) produce an EXC_BAD_ACCESS error seeing as we just RELEASED controller above?
look at the line between alloc and release.
[viewControllers replaceObjectAtIndex:page withObject:controller];
the viewControllers array will retain the controller.
But imho it's no good code. for exactly the reason you've stated. Not very clear at first sight.

Tab bar Controller raising NSInternalInconsistencyException

In my SDK 3.0 core data based app, I have a tab bar controller managing 4 tabs. From time to time, apparently randomly, when I launch the app, it crashes with the following message:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Directly modifying a tab bar managed by a tab bar controller is not allowed.'
However, I am not modifying in my code any of the tabs except for the following. In practice, all of the navigation controllers or controllers in the tabs have been setup using IB, and in my code I have declared two of them as outlets, since I need to access them in my app delegate applicationDidFinishLaunching() method to setup their core data managedObjectContext as follows:
[self managedObjectContext];
[self managedObjectModel];
[self persistentStoreCoordinator];
[rootViewController retain];
rootViewController.managedObjectContext = self.managedObjectContext;
Is this not correct? If so, why?
The only reference to the problem I have seen on the web is here:
http://discussions.apple.com/thread.jspa?messageID=9716886
However, the problem still persists even after deleting and recreating the tab bar controller from scratch in IB.
Any clue?
Thanks in advance.
I've had this problem too. Do you have an outlet to the UITabBar itself (not the UITabBarController) in the nib? When I removed that, I stopped having problems.
Sorry this isn't a 100% reliable explanation, but this workaround cleared the problem up for me.
I've gotten this exception a few times, especially when changing things with localizations. Cleaning the targets and then rebuilding seems to work around the issue.
I quickly wrote the following class and showing/hiding tab views from UITabBarController worked like magic:
TabBarDesigner.h
#import <Foundation/Foundation.h>
#interface TabBarDesigner : NSObject
{
}
+(void) setTabBarController:(UITabBarController *)tabBarController
items:(NSArray *)tabBarItems
viewControllers:(NSArray *)viewControllers;
+(void) removeItemsInRange:(NSRange) range;
#end
TabBarDesigner.m
#import "TabBarDesigner.h"
static NSArray *_tabBarItems = NULL;
static NSArray *_viewControllers = NULL;
static UITabBarController *_tabBarController = NULL;
#implementation TabBarDesigner
+(void) setTabBarController:(UITabBarController *)tabBarController
items:(NSArray *)tabBarItems
viewControllers:(NSArray *)viewControllers
{
if (tabBarItems && viewControllers && tabBarController)
{
if ([tabBarItems count] == [viewControllers count])
{
[_tabBarItems release];
[_viewControllers release];
_tabBarItems = [tabBarItems copy];
_viewControllers = [viewControllers copy];
_tabBarController = tabBarController;
}
}
}
+(void) removeItemsInRange:(NSRange) range
{
if (_tabBarController)
{
if ( range.location < ([_tabBarItems count] - 1) )
{
if ( (range.length + range.location) < [_tabBarItems count] )
{
NSMutableArray *tabBarItems = [_tabBarItems mutableCopy];
[tabBarItems removeObjectsInRange:range];
NSMutableArray *viewControllers = [_viewControllers mutableCopy];
[viewControllers removeObjectsInRange:range];
[_tabBarController setViewControllers:viewControllers];
NSUInteger i;
for (i = 0; i< [viewControllers count]; i++)
{
UIViewController *vC = [viewControllers objectAtIndex:i];
vC.tabBarItem.image = [[tabBarItems objectAtIndex:i] image];
vC.tabBarItem.title = [[tabBarItems objectAtIndex:i] title];
vC.tabBarItem.tag = [[tabBarItems objectAtIndex:i] tag];
}
[tabBarItems release];
[viewControllers release];
}
}
}
}
#end
A sample of how to use this class:
In your MyAppDelegate.m
#import "TabBarDesigner.h"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[TabBarDesigner setTabBarController:_tabBarController
items:[_tabBarController.tabBar items]
viewControllers:[_tabBarController viewControllers]];
// remove the first 3 tabs
[TabBarDesigner removeItemsInRange:NSMakeRange(0,3)];
// show all tabs
[TabBarDesigner removeItemsInRange:NSMakeRange(0,0)];
// continue with your code
}
Cheers!