Wednesday, September 11, 2013

UIPageViewController In Right to Left format (Arabic/Urdu based books) - iPad

Introduction:

PageViewController in iOS 5 and later is becoming very common and most useful component now a days. It lets the user navigate between pages of content, where each page is managed by its own view controller object. Navigation can be controlled programmatically by your app or directly by the user using gestures. When navigating from page to page, the page view controller uses the transition that you specify to animate the change. It gives you a classical book curl page which gives a very nice view to the user.

We are now creating a Sample Project in which a PageViewController is created manually as a Single View Application.

1. Starting the Project:

Open the Latest XCode with iOS SDK 5 or later and create new Project with Single View application. Enter the name of the Project and then select iPad as devices. Don't forget to Uncheck "Use Storyboard" and Check "Use ARC". Since we are creating the application manually from scratch using the SingleView Application so we don't need to use Storyboard.


2. Setting up the Book Background:

Open the ViewController.xib file and select change the background of the view by selecting view Objects Panel. You can place any ImageView in the view in case if you want to add a custom Image on the background of the Classical Book. In my case I am adding just a background color.


3. Adding Book ContentView Controllers:

Lets add a ContentView Controller and Add an Objective C File in the project with UIViewController as its parent Class and name it OneContentViewController with Target as iPad and with XIB User Interface.


4. Add Book Content and Setup Page

Set the background color of the View inside OneContentViewController xib file. Now drag and drop an UIImageView in the View and set its Size from the Size Inspector on Right Side Panel as (x, y, width, height) (20, 8, 424, 643).


Create an IBOutlet of UIImageView in OneContentViewController.h file and connect that Outlet with the UIImageView you dragged and placed in the OneContentViewController.xib file. Also create one UIImage and NSString property in OneContentViewController.h file and synthesize all the properties in OneContentViewController.m file.

Your OneContentViewController.h file will look something like below.

#import <UIKit/UIKit.h>

@interface OneContentViewController : UIViewController

@property (nonatomic, retain) IBOutlet UIImageView *pageView;
@property (nonatomic, retain) UIImage *page_content;
@property (nonatomic, retain) NSString *page_name;
@end

Now replace the viewDidLoad method in you ContentViewController with the following Code. In this code we just set the UIImageView "pageView" with the UIImage "page_content" so that when we set the UIImage from our MainController (ViewController.m) then it set that UIImage in the UIImageView.

- (void)viewDidLoad
{
    [pageView setImage:page_content];
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.
}


5. Setup MainController (ViewController.h/ViewController.m)

Now add a UIPageViewController property in the ViewController.h file and synthesize the property. Also add one NSMutableArray object in ViewController.h file and synthesize it as well. Then you can add the UIPageViewControllerDataSource and UIPageViewControllerDelegate in the ViewController.h file because we will use some delegates and datasources of UIPageViewController in our ViewController.m file.

Last but on least we would also import OneContentViewController.h in our ViewController.h file.

After 5th Step our ViewController.h file would be look something like following.


#import <UIKit/UIKit.h>
#import "OneContentViewController.h"

@interface ViewController : UIViewController <UIPageViewControllerDataSource, UIPageViewControllerDelegate>

@property (nonatomic, strong) UIPageViewController *pageViewController;
@property (nonatomic, strong) NSMutableArray *modelArray;

@end

6. Add UIPageViewController in  ViewController.m file:

Now we will setup and initialise UIPageViewController  and add that into the subview. Add the following method into the ViewController.m file and call this method from our viewDidLoad method.

#pragma mark - Local Methods
-(void)setupPageViewController{
    
    //Instantiate the model array
    self.modelArray = [[NSMutableArray alloc] init];
    for (int index = 1; index <= 24 ; index++)
    {
        [self.modelArray addObject:[NSString stringWithFormat:@"%d",index]];
    }
    //Step 1:  Instantiate the UIPageViewController.
    self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl
                                                              navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
    
    //Step 2:  Assign the delegate and datasource as self.
    self.pageViewController.delegate = self;
    self.pageViewController.dataSource = self;
    
    //Step 3:   Set the initial view controllers.
    OneContentViewController *contentViewController = [[OneContentViewController alloc] initWithNibName:@"OneContentViewController" bundle:nil];
    contentViewController.page_name = [self.modelArray objectAtIndex:0];
    contentViewController.page_content = [UIImage imageNamed:[NSString stringWithFormat:@"page_%@.jpg",[self.modelArray objectAtIndex:0]]];
    
    //You can add more ContentView Controllers in this array as well. Each ContentViewController can denotes a seperate page. In our case we are using same ContentViewController on each page
    NSArray *viewControllers = [NSArray arrayWithObject:contentViewController];
    [self.pageViewController setViewControllers:viewControllers
                                      direction:UIPageViewControllerNavigationDirectionReverse
                                       animated:NO
                                     completion:nil];
    
    //Step 4:    ViewController containment steps ---- Add the pageViewController as the childViewController
    [self addChildViewController:self.pageViewController];
    
    //Add the view of the pageViewController to the current view
    [self.view addSubview:self.pageViewController.view];
    
    //Call didMoveToParentViewController: of the childViewController, the UIPageViewController instance in our case.
    [self.pageViewController didMoveToParentViewController:self];
    
    //Step 5:
    // set the pageViewController's frame as an inset rect.
    CGRect pageViewRect = self.view.bounds;
    pageViewRect = CGRectInset(pageViewRect, 40.0, 40.0);
    self.pageViewController.view.frame = pageViewRect;
    
    //Step 6:
    //Assign the gestureRecognizers property of our pageViewController to our view's gestureRecognizers property.
    self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;
    
}


7. Setup Delegates and DataSources of UIPageViewController:

Now add the following two UIPageViewController DataSource Methods in the ViewController.m file. These two methods will select the currentIndex of the pages that are being showed right now and get the Image from your resources and set that into the UIImage in ContentViewController.

#pragma mark - UIPageViewControllerDataSource Methods

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController
      viewControllerBeforeViewController:(UIViewController *)viewController
{
    NSUInteger currentIndex = [self.modelArray indexOfObject:[(OneContentViewController *)viewController page_name]];
    if(currentIndex == self.modelArray.count-1)
    {
        return nil;
    }
    OneContentViewController *contentViewController = [[OneContentViewController alloc] init];
    contentViewController.page_name = [self.modelArray objectAtIndex:currentIndex + 1];
    contentViewController.page_content =[UIImage imageNamed:[NSString stringWithFormat:@"page_%@.jpg",[self.modelArray objectAtIndex:currentIndex + 1]]];
    
    return contentViewController;
}

- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController
       viewControllerAfterViewController:(UIViewController *)viewController
{
    
    NSUInteger currentIndex = [self.modelArray indexOfObject:[(OneContentViewController *)viewController page_name]];
    if(currentIndex == 0)
    {
        return nil;
    }
    OneContentViewController *contentViewController = [[OneContentViewController alloc] init];
    contentViewController.page_name = [self.modelArray objectAtIndex:currentIndex - 1];
    contentViewController.page_content = [UIImage imageNamed:[NSString stringWithFormat:@"page_%@.jpg",[self.modelArray objectAtIndex:currentIndex - 1]]];
    return contentViewController;
    
}

Now add the UIPageViewController Delegate method, which would be called when you Curl the page. Now add the following method in the ViewController.m

#pragma mark - UIPageViewControllerDelegate Methods

- (UIPageViewControllerSpineLocation)pageViewController:(UIPageViewController *)pageViewController
                   spineLocationForInterfaceOrientation:(UIInterfaceOrientation)orientation
{

    /* uncomment this if statement if you want to add portrait functionality as well. */
    
//    if(UIInterfaceOrientationIsPortrait(orientation))
//    {
//        //Set the array with only 1 view controller
//        UIViewController *currentViewController = [self.pageViewController.viewControllers objectAtIndex:0];
//        NSArray *viewControllers = [NSArray arrayWithObject:currentViewController];
//        [self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:NULL];
//        
//        //Important- Set the doubleSided property to NO.
//        self.pageViewController.doubleSided = NO;
//        //Return the spine location
//        return UIPageViewControllerSpineLocationMin;
//    }
//    else
//    {
        NSArray *viewControllers = nil;
        OneContentViewController *currentViewController = [self.pageViewController.viewControllers objectAtIndex:0];
        
        NSUInteger currentIndex = [self.modelArray indexOfObject:[(OneContentViewController *)currentViewController page_name]];
        if(currentIndex == 0 || currentIndex %2 == 0)
        {
            UIViewController *previousViewController = [self pageViewController:self.pageViewController viewControllerBeforeViewController:currentViewController];
            viewControllers = [NSArray arrayWithObjects:previousViewController, currentViewController, nil];
        }
        else
        {
            UIViewController *nextViewController = [self pageViewController:self.pageViewController viewControllerAfterViewController:currentViewController];
            viewControllers = [NSArray arrayWithObjects:currentViewController, nextViewController, nil];
        }
        [self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:NULL];
        
        return UIPageViewControllerSpineLocationMid;
//    }
}


8. Add the images into the sources:

Now add the page images into the source folder of the project with the following pattern, page_1, page_2,......,page_15,page_16....

Build and Run the code and you will see a classical book curl type in your simulator. You can download the code as well from here. Open the link and goto File and Click Download. You will get sample images in the source code.

Enjoy Coding!


1 comment:

  1. hi, thank you very much and it is helped me alot but there is a little problem please help me to solve this issue
    if i use UIPageViewControllerTransitionStylePageCurl it is working wevy well but UIPageViewControllerTransitionStylePageScroll it is give me wrong page number

    please help me

    ReplyDelete