原文地址:http://www.ioslearner.com/implementing-uipageviewcontroller-programatically-without-storyboarding/
Recently I tried the new feature introduced in iOS 5.0 – The UIPageViewController. Apple has provided an already built in template for this – Page-Based Application. But the default template uses storyboarding. So, in this tutorial, we will see how to implement a UIPageViewController without storyboarding.
Ok so lets get started!
Initial Project setup:
Bump up your Xcode and create a new Project, and choose Single View Application from the template. Make sure that the device family you choose is iPad and the Use Storyboard option is unchecked as shown below.
We will now make our view controller class which will have the content. In this simple application, we will just have a page with a label denoting the number of that page, and finally our application will look like this:
Make a new UIViewController subclass and name it ContentViewController. Open the ContentViewController.xib and do the following:
(a) Change the background color of the view to gray color.
(b) Add a view from the objects window, and change its color as in the screenshot after point (c). Also set the autoresizing mask of the view as follows:
(c) Add a label to the ContentViewController.xib. Set the autoresizing mask for the label as follows:
Now, Create an outlet for the label by opening the Assistant Editor and Ctrl+Drag from the label onto the ContentViewController.h as follows:
(d) Create an NSString property in ContentViewController.h as follows:
@property (strong, nonatomic) NSString *labelContents; |
Also synthesize the property in the implementation. This NSString property will hold the contents of our label. And so in our viewDidLoad, we write the following:
- (void)viewDidLoad { [super viewDidLoad]; self.myLabel.text = self.labelContents; } |
Ok. So now our project setup is complete. We now start with the UIPageViewController stuff!
UIPageViewController Implementation:
We create two properties in our ViewController.h file as follows:
@property (nonatomic, strong) UIPageViewController *pageViewController; @property (nonatomic, strong) NSMutableArray *modelArray; |
and synthesize them in the implementation.
We will also instantiate our modelArray, which will store the contents that will be displayed in our ContentViewController label.
self.modelArray = [[NSMutableArray alloc] init]; for (int index = 1; index |
To add the UIPageViewController into our app, we will follow these simple steps:
Step 1: Instantiate the UIPageViewController
self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil]; |
Here, for the transitionStyle, we give UIPageViewControllerTransitionStylePageCurl. This is the transition style when we transition between our views.
We give navigationOrientation as UIPageViewControllerNavigationOrientationHorizontal. This specifies that we want Left <-> Right direction in which the navigation of our pages will happen. If we give UIPageViewControllerNavigationOrientationVertical, the navigation will happen Top <-> Bottom, as in a wall calendar.
Last is the options dictionary. Here, we pass in as nil. We can give the default spine location here, if required.
Step 2: Assign the delegate and datasource as self. Also conform to UIPageViewControllerDelegate protocol in the ViewController.h file.
self.pageViewController.delegate = self; self.pageViewController.dataSource = self; |
Step 3: Next, we set the initial view controller as follows:
ContentViewController *contentViewController = [[ContentViewController alloc] initWithNibName:@"ContentViewController" bundle:nil]; contentViewController.labelContents = [self.modelArray objectAtIndex:0]; NSArray *viewControllers = [NSArray arrayWithObject:contentViewController]; [self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:NO completion:nil]; |
Here, we instantiate our ContentViewController and add it to an array. Then we assign that array using this UIPageViewController instance method:
- (void)setViewControllers:(NSArray *)viewControllers direction:(UIPageViewControllerNavigationDirection)direction animated:(BOOL)animated completion:(void (^)(BOOL finished))completion; |
We pass in the direction as UIPageViewControllerNavigationDirectionForward, which is again for Left <-> Right direction. We specify No for animated, because we are setting our initial viewControllers, so we don’t want to animate it. And then we specify nil for the completion block.
Step 4: We now do the ViewController containment stuff.
(a) Add the pageViewController as the childViewController of our ViewController:
[self addChildViewController:self.pageViewController];<span class="Apple-style-span" style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 19px; white-space: normal;"> </span> |
(b) Add the view of the pageViewController to the current view:
[self.view addSubview:self.pageViewController.view]; |
(c) Call didMoveToParentViewController: of the childViewController, the UIPageViewController instance in our case:
[self.pageViewController didMoveToParentViewController:self]; |
Step 5: Though this is not mandatory, we set the pageViewController’s frame as an inset rect, so that we are able to distinguish between our view and the pageViewController’s view.
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. This will enable touch gestures to transition between the pages:
self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;
|
Now, if we run the application, we can see our view controller with But, if we swipe left to right, the views aren’t changing i.e. the gesture recognizers aren’t working.
So to make that happen, we have to conform our class to the UIPageViewControllerDataSource protocol and implement the following two methods:
(1) - (UIViewController *)pageViewController:(UIPageViewController *)pageViewController
viewControllerBeforeViewController:(UIViewController *)viewController
(2) - (UIViewController *)pageViewController:(UIPageViewController *)pageViewController
viewControllerAfterViewController:(UIViewController *)viewController
We write this code in our ViewController.m file:
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController { NSUInteger currentIndex = [self.modelArray indexOfObject:[(ContentViewController *)viewController labelContents]]; if(currentIndex == 0) { return nil; } ContentViewController *contentViewController = [[ContentViewController alloc] init]; contentViewController.labelContents = [self.modelArray objectAtIndex:currentIndex - 1]; return contentViewController; } - (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController { NSUInteger currentIndex = [self.modelArray indexOfObject:[(ContentViewController *)viewController labelContents]]; if(currentIndex == self.modelArray.count-1) { return nil; } ContentViewController *contentViewController = [[ContentViewController alloc] init]; contentViewController.labelContents = [self.modelArray objectAtIndex:currentIndex + 1]; return contentViewController; } |
The above two methods will be called every time the user swipes left or right, and so we have to return the suitable view controller that will be presented. For demo purposes, here we just create an instance of our ContentViewController, see the currentIndex of the contents, and set the labelContents appropriately.
In a real life scenario, this part would differ, and instead of allocating a new instance of our ContentViewController, we could have a better logic, like having an array of say 3 controllers, and reusing them. But to avoid complexity, and with the intent of just demonstrating on how the UIPageViewController works, I have just allocated a new instance every time.
I we run the application now, we see that the transition is now happening between the pages!
But if we rotate to landscape orientation, we don’t see a partition, right. For that, we have to implement the UIPageViewControllerDelegate method:
- (UIPageViewControllerSpineLocation)pageViewController:(UIPageViewController *)pageViewController spineLocationForInterfaceOrientation:(UIInterfaceOrientation)orientation
and return the UIPageViewControllerSpineLocation according to the interface orientation.
The spine is actually a kind of binding we see in a notebook. It can be at three positions:
(a) UIPageViewControllerSpineLocationMin – The binding is on the left side.
(b) UIPageViewControllerSpineLocationMid – The binding is on the mid. This would be foe Landscape orientations, with like pages on both sides
and (c) UIPageViewControllerSpineLocationMax – The binding is on the right side.
We just write this code in our ViewController implementation:
- (UIPageViewControllerSpineLocation)pageViewController:(UIPageViewController *)pageViewController spineLocationForInterfaceOrientation:(UIInterfaceOrientation)orientation { if(UIInterfaceOrientationIsPortrait(orientation)) { // In portrait orientation: Set the spine position to "min" and the page view controller's view controllers array to contain just one view controller. Setting the spine position to 'UIPageViewControllerSpineLocationMid' in landscape orientation sets the doubleSided property to YES, so set it to NO here. 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 UIPageViewControllerSpineLocationMin; } else { // In landscape orientation: Set set the spine location to "mid" and the page view controller's view controllers array to contain two view controllers. If the current page is even, set it to contain the current and next view controllers; if it is odd, set the array to contain the previous and current view controllers. NSArray *viewControllers = nil; ContentViewController *currentViewController = [self.pageViewController.viewControllers objectAtIndex:0]; NSUInteger currentIndex = [self.modelArray indexOfObject:[(ContentViewController *)currentViewController labelContents]]; if(currentIndex == 0 || currentIndex %2 == 0) { UIViewController *nextViewController = [self pageViewController:self.pageViewController viewControllerAfterViewController:currentViewController]; viewControllers = [NSArray arrayWithObjects:currentViewController, nextViewController, nil]; } else { UIViewController *previousViewController = [self pageViewController:self.pageViewController viewControllerBeforeViewController:currentViewController]; viewControllers = [NSArray arrayWithObjects:previousViewController, currentViewController, nil]; } //Now, set the viewControllers property of UIPageViewController [self.pageViewController setViewControllers:viewControllers direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:NULL]; return UIPageViewControllerSpineLocationMid; } } |
and Bingo! When we run the app, in landscape orientation, we see a partition.
So lets scrutinize what we did in the delegate method.
We first set the viewControllers property of our pageViewController according to the interface orientation. We return an array of just one viewController if the orientation is portrait. In case of landscape, we return an array of two view controllers. If the current page is even, we set the array to contain the current and next view controllers; if it is odd, then we set the array to contain the previous and current view controllers.
We then set the spine location appropriately. We set it as UIPageViewControllerSpineLocationMin in portrait and UIPageViewControllerSpineLocationMid in landscape.
An important thing to note: When we set the spine location to UIPageViewControllerSpineLocationMid,the doubleSided property of the pageViewController is automatically set to YES. This means that the content on page front will not partially show through back. But when this property is set to NO, the content on page front will partially show through back, giving the page a translucent kind of effect. So, in the portrait orientation, we have to set the value to NO, otherwise it would result in an exception.
We can also zoom in and out in a UIPageViewController. Please comment and let me know if you need a tutorial on that.
As usual, you can download all the sample code of this tutorial here:
相关推荐
仪表板开关能够打开/关闭参考信号,但如果参考信号在没有用户输入的情况下发生变化,则不会... 在https://www.mathworks.com/matlabcentral/answers/516815-programatically-change-state-of-slider-switch讨论了概念
matlab导入excel代码utl_programatically_execute_excel_macro_using_wps_proc_python 使用wps proc python以编程方式执行excel宏。 关键字:sas sql join合并大数据分析宏oracle teradata mysql sas社区...
A splash screen API for react-native which can programatically hide and show the splash screen. Works on iOS and Android. Content Installation Examples Getting started API Contribution Changes ...
SlidingRootNav The library is a DrawerLayout-like ViewGroup, where a "drawer" ...Create your content_view.xml (example) or construct a View programatically. Set the content view (for example, using setC
dip's user interfaces are testable because a user's actions can be simulated programatically a framework for defining types of storage and data formats for reading and writing application objects a ...
matlab导入excel代码utl_web_scraping_programatically_using_a_web_search_box_and_retrieve_information 使用网络搜索框以编程方式进行网络抓取并检索信息。 关键字:sas sql join合并大数据分析宏oracle teradata...
dip's user interfaces are testable because a user's actions can be simulated programatically a framework for defining types of storage and data formats for reading and writing application objects a ...
Youtube Player without any dependency and without SDK, webview based. Control video and get video information programatically. How to use Add it to your build.gradle with: allprojects { repositories ...
根Android 如何扎根Android手机? 这个仓库就是我该怎么做的。 一些对ROM刷新器很重要的重要站点: Twrp.me ...出色的命令,可在有根设备上运行 pm uninstall -k --user 0 //将卸载任何应用程序 ...
# or use programatically npm install --save standard-json CLI用法 standard | standard-json # exit code will be 1 if any errors are found standard --verbose | standard-json # the ruleId attribute will ...
programatically invoke web services. In addition to that, number of properitory and open source tools are avaialble to test web services automatically. soapUI is one such free and open source testing ...
Either programatically or using xml: API General Size of the widget is controlled by the attribute: Default selection can be set using: To control the current state or get information about it, ...
SegmentedProgressBar Instagram like segmented progress bar for Android....Programatically segmentedProgressBar = (SegmentedProgressBar) findViewById(R.id.segmented_progressbar); // number of segments in
Fixed issue that prevented close button from being show when made visible programatically. 6. When showing a content, prefer to place it in the active pane, otherwise default to the first available
how to efficiently store and retrieve data programatically. ... how to optimize big-data computations. ... how to use index structures to accelerate computations. ... how to safely and efficiently ...
Safely edit and manipulate DWG data programatically, including: Explode an entity into a set of simpler entities. Apply a transformation to an entity. Modify arbitrary properties of database ...
Android屏幕截图检测屏幕截图检测库下载从1.0.1版开始,它将 // build.gradle (project)allprojects { repositories { mavenCentral() /* ...... 因此,您需要自己处理运行时权限。 否则,应用程序将不会崩溃,并且屏幕...
关于生成目录树结构的类 本程序有两文件test.asp 和tree.asp 还有一些图标文件 1。test.asp 调用类生成树 代码如下 <%@ Language=VBScript %> ...' BUILDING A TREE PROGRAMATICALLY '=
快速样式的动画侧面导航,可通过具有...##切换到另一个文件更新“主”分支将“运动布局”用于过渡效果,以编程方式切换到“ with_kotlin_programatically”分支。接触如果您需要任何帮助,请随时连接。 设计贡献者:
选项 ... use CodeDistortion \ Options \ Options ; $ results = Options :: parse ( 'sendAlerts dailyLimit=123.45 !applyDailyLimit currency=AUD plan="Silver Plan"' );...// when used programatically $ options