iOS侧滑导航面板

侧滑导航面板偶尔会用到,这里总结一些第三方:


本文将介绍如何创建类似Facebook和Path iOS程序中的滑出式导航面板。


向右滑动


滑出式设计模式可以让开发者在程序中添加常用的导航功能,又不会浪费屏幕上宝贵的空间。用户可以在任意时间滑出导航面板,并且还可以看到当前屏幕上显示的内容。

 

现在,互联网上有些库已经内置滑出式设计模式,比如John-Lluch开发的SWRevealViewController。如果你在寻找更加快捷和简单的方法,那么使用SWRevealViewController库可能是一个很不错的方法。

 

不过,如果你是一名DIY类型的程序员(像我),那么你可能希望自己理解这功能是如何实现的。在本文中,你会看到该功能的实现并不复杂。通过少即是多的方法,并忽略掉复杂大且非必须的代码,就可以轻松的在程序中集成滑出式导航面板技术。

 

开始
那么这里创建的滑出式导航面板的功能具体是什么呢?

 

iOS设计师和开发者Ken Yarmosh的解释比较恰当:“滑出式导航面板拥有一个面板,这个面板从主画面的左边或者右边滑出来,然后在面板中显示一个垂直的、独立的滚动视图(Scroll view),把该视图当作程序的主导航。”

 

注意: Ken在这里的文章中详细的解释了滑出式导航面板的设计模式,并介绍了该模式带来的好处:新的iOS设计模式:滑出式导航面板

 

首先下载本文的启动工程。这是一个ZIP文件,只需要将其保存到本地,并解压一下就可以得到工程。

 

接着在Xcode中打开这个工程,并看看工程的组织结构:

工程被分为3个主要的文件夹:

•Assets: 包含所有的图片文件和其它非代码资源(例如attribution文件)。
•Views: 包含本文涉及到的所有xib文件。
•Classes: 包含Objective-C代码文件

 

 
不要担心Assets中有许多文件,你不需要修改这些内容,所有要用到的资源文件都添加进来了。
 

在Views文件夹中有4个主要的view controller。下面是相关简介:

•MainViewController: 这是主要的一个画面!这个文件需要添加到你自己的工程中(需要一些小的改动)。
•CenterViewController: 这是正中间的面板。该view controller可以替换为你自己的view controller(记住按钮的action也实现了)
•LeftPanelViewController: 左边的面板。该view controller可以替换为你自己的view controller。
•RightPanelViewController: 右边的面板。该view controller可以替换为你自己的view controller


现在打开AppDelegate.m文件。虽然你不需要对这个文件做任何改变,但你应知道MainViewContorller是左,中和右view controller的容器。这个controller的初始化在19行代码中:

1.self.viewController = [[MainViewController alloc] initWithNibName:@"MainViewController" bundle:nil];

 

熟悉了工程的结构, 就正在的开始——从正中间的面板开始。

 

找到中心
本小节中,我将在MainViewConroller中放置一个CenterViewController,将CenterViewController当做MainViewConroller的子view controller。

 

注意:本小节会用到iOS 5中的新增的一个概念:View Controller Containment。如果还不熟悉,先看看iOS 5 by Tutorials中的第22章“UIViewController Containment”。

 

打开MainViewController.m文件,并将下面的import语句添加到文件的顶部:

1.#import "CenterViewController.h"

 

接着,添加一个常量定义:

1.#define CENTER_TAG 1

 

接着在@interface中添加下面的属性,以方便控制center view。

1.@property (nonatomic, strong) CenterViewController *centerViewController;

 

找到setupView并在里面添加如下代码块:

1.self.centerViewController = [[CenterViewController alloc] initWithNibName:@"CenterViewController" bundle:nil];

2.self.centerViewController.view.tag = CENTER_TAG;

3.self.centerViewController.delegate = self;

4.

5.[self.view addSubview:self.centerViewController.view];

6.[self addChildViewController:_centerViewController];

7.

8.[_centerViewController didMoveToParentViewController:self];

 

上面的代码分配了一个新的CenterViewController并将其赋值给centerViewController属性。然后将这个view controller view的tag设置为CENTER_TAG。

 

接着将delegate设置为MainViewController。也就意味着你需要对MainViewController进行修改,以遵循CenterViewControllerDelegate协议——只需要将文件顶部@interface代码行替换为如下即可:

1.@interface MainViewController ()

 

最后,在setupView方法的代码中,使用addSubview:方法将centerViewController的view添加到MainViewController的view中,另外还调用了addChildViewContoller:将_centerViewController添加为MainViewController的子view controller。最后调用了didMoveToParentViewController:方法。

 

编译并运行程序,看到类似如下的画面:


在画面顶部的按钮可以让你切换到小猫(kitties)和小狗(puppies)。有什么更好的理由需要在这里创建一个滑出式的导航面板呢?在这里要想看到不同的小动物,那就开始滑动吧。首先从左边开始!

 

靠向左边
现在已经添加好了center panel,不过要添加left view controller需要一些不同的操作。回到MainViewController.m文件中,并将下面的import语句添加到文件顶部:

1.#import "LeftPanelViewController.h"

 

然后再定义一个常量:

1.#define LEFT_PANEL_TAG 2

 

接着在@interface中添加一些属性,这跟center view类似:

1.@property (nonatomic, strong) LeftPanelViewController *leftPanelViewController;

2.@property (nonatomic, assign) BOOL showingLeftPanel;

现在找到getLeftView方法,删除掉已有的代码并添加如下代码:

1.// init view if it doesn't already exist

2.if (_leftPanelViewController == nil) 

3.{

4. // this is where you define the view for the left panel

5. self.leftPanelViewController = [[LeftPanelViewController alloc] initWithNibName:@"LeftPanelViewController" bundle:nil];

6. self.leftPanelViewController.view.tag = LEFT_PANEL_TAG;

7. self.leftPanelViewController.delegate = _centerViewController;

8.

9. [self.view addSubview:self.leftPanelViewController.view];

10.

11. [self addChildViewController:_leftPanelViewController];

12. [_leftPanelViewController didMoveToParentViewController:self];

13.

14. _leftPanelViewController.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);

15.}

16.

17.self.showingLeftPanel = YES;

18.

19.// set up view shadows

20.[self showCenterViewWithShadow:YES withOffset:-2];

21.

22.UIView *view = self.leftPanelViewController.view;23.return view;

 

上面的代码首先检查一下看看leftPanelViewController属性是否为nil,如果是nil的话,就分配并初始化一个LeftPanelViewController给leftPanelViewController属性。

 

接着是赋值一个tag和delegate——用于图片的选择,以及将新创建的view添加到main view中。

 

然后将showingLeftPanel属性设置为YES,并添加了一些视觉上的处理,在下一节中将介绍相关内容。

 

最后将view返回给调用者。为什么要这样操作——在后面小节中你将看到为什么。


下面处理一下阴影。

 

不要忘记阴影效果
在上面刚刚添加的代码中,你已经看到调用了showCenterViewWithShow:withOffset:方法。该方法使用QuartzCore框架创建并添加一个阴影效果。

 

为了访问该框架的提供的许多精彩功能,将下面的import语句添加到MainViewController.m文件顶部:

1.#import <QuartzCore/QuartzCore.h>

 

同样,在文件顶部定义一个常量,代表圆角。这样,如果你想要修改圆角的话,只需要在一个地方修改即可。

1.#define CORNER_RADIUS 4

 

现在找到showCenterViewWithShadow:withOffset: 方法,并将下面的代码块添加进去:

1.if (value)

2.{       

3. [_centerViewController.view.layer setCornerRadius:CORNER_RADIUS];

4. [_centerViewController.view.layer setShadowColor:[UIColor blackColor].CGColor];

5. [_centerViewController.view.layer setShadowOpacity:0.8];

6. [_centerViewController.view.layer setShadowOffset:CGSizeMake(offset, offset)];

7.

8.}

9.else

10.{

11. [_centerViewController.view.layer setCornerRadius:0.0f];

12. [_centerViewController.view.layer setShadowOffset:CGSizeMake(offset, offset)];

13.}

 

上面的代码中,如果传递过进来的value是非零,就会给center view设置一个圆角和一个阴影。否则就将圆角设置为非圆形。

如果现在运行程序的话,还看不到效果,因为上面的代码还没有用到。


 

回到左边
现在已经获得了滑动导航面板所需的一些材料了,接着继续完成left view controller。一旦完成之后,右边如何移动你也会清楚了。

 

本文中,为了将注意力几种在重要的地方,我已经把IB文件中涉及到的IBAction和IBOutlet连接好了。不过,为了实现你自己的DIY滑出式导航面板,你需要知道这些IB中的按钮是如何配置的。

 

看看下面这个张关于截图CenterViewController.xib文件的截图,注意其中的连接:


如上图中的kitties按钮,已经连接到一个名为leftButton的IBOutlet上了,并且这个按钮的Touch Up Inside事件连接到了一个名为btnMovePanelRight:的IBAction上。这个按钮控制着center panel的滑动,以显示出左边的panel。

 

btnMovePanelRight:现在还是空的,下面我们就来看看如何实现:

 

打开CenterViewController.m文件,并将下面的代码块添加到btnMovePanelRight:方法中:

1.UIButton *button = sender;

2.switch (button.tag) {

3. case 0: {

4.  [_delegate movePanelToOriginalPosition];

5.  break;

6. }

7.

8. case 1: {

9.  [_delegate movePanelRight];

10.  break;

11. }

12.

13. default:

14.  break;

15.}

 

上面的代码使用了一个switch语句,通过判断leftButton的tag属性来确定center panel是需要移动到右边,还是需要将其移动到原来的正中间位置。这里的leftButton是通过sender参数传递过来的。button的tag设置为0表示center panel已经移动到右边了,如果设置为1的话,表示center panel已经在原来的正中间位置。

 

如果你看一下CenterViewController.xib文件,会看到我已经将leftButton的tag默认值设置为1。

 

看到上面的代码中调用了delegate方法吗?如果你还记得的话,之前在配置CenterViewController示例时,已经将它的delegate设置为MainViewController。因此这里的调用就涉及到了MainViewController中的相关方法。

 

在实现这些delegate方法之前,首先看看CenterViewController.h文件中协议CenterViewControllerDelegate的定义:

 

如下图所示,协议中定义了两个optional协议方法,以及一个required协议方法,分别是:movePanelLeft, movePanelRight 和 movePanelToOriginalPosition。


因为CenterViewController的delegate是MainViewController,所以我们在MainViewController中添加这些delegate方法。

 

打开MainViewController.m文件,并添加如下两个常量定义:

1.#define SLIDE_TIMING .25

2.#define PANEL_WIDTH 60

接着找到movePanelRight方法,并将如下代码块添加到里面:

1.UIView *childView = [self getLeftView];

2.[self.view sendSubviewToBack:childView];

3.

4.[UIView animateWithDuration:SLIDE_TIMING delay:0 options:UIViewAnimationOptionBeginFromCurrentState

5.    animations:^{

6.     _centerViewController.view.frame = CGRectMake(self.view.frame.size.width - PANEL_WIDTH, 0, self.view.frame.size.width, self.view.frame.size.height);

7.    }

8.    completion:^(BOOL finished) {

9.     if (finished) {

10.

11.      _centerViewController.leftButton.tag = 0;

12.     }

13.    }];

注意:这个方法是由CenterViewController中的btnMovePanelRight:调用的。更多如何实现delegate相关的信息,请参考:苹果开发者文档

 

上面的代码就是奇迹发生的地方!

 

首先调用getLeftView方法,该方法返回一个view,然后将view推到背后,接着是进入动画处理过程:使用一个animateWithDuration:animations:completion: 块。在动画中使用到的SLIDE_TIMING和PANEL_WIDTH值可以随意调整。其中SLIDE_TIMING是控制动画的速度,而PANEL_WIDTH是控制动画过后,center view留在屏幕中的宽度。

 

另外,记住海的把leftButton的tag属性设置为0。如果你还记得的话,这个tag属性用来跟踪记录center view的当前位置。

 

现在编译并运行一下程序,看看效果如何。

 

当程序启动后,点击kitties按钮,center panel应该会滑动到右边,并显示出left panel。此时,屏幕上显示的效果如下图所示:


注意观察center view左边缘的圆角和阴影——这两个效果是执行showCenterViewWithShadow:withOffset:(之前添加的方法)方法得到的结果。

 

再点击一下kitties按钮——什么事情都没有发生。这是因为还没有实现movePanelToOriginalPosition方法。

 

回到MainViewController.m文件,并将下面的代码块添加到movePanelToOriginalPosition方法中:

1.[UIView animateWithDuration:SLIDE_TIMING delay:0 options:UIViewAnimationOptionBeginFromCurrentState

2.    animations:^{

3.     _centerViewController.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);

4.    }

5.    completion:^(BOOL finished) {

6.     if (finished) {

7.

8.      [self resetMainView];

9.     }

10.    }];

同样,在上面的代码中使用了一个animateWithDuration:animations:completion: 块来处理动画。不过这次是将center view的位置以动画的方式设置为最初的位置。

 

当动画完成的时候,调用了resetMainView方法。目前该方法还没有具体的实现。在该方法中需要重置一下view,下面我们来实现一下吧!

 

找到resetMainView方法,并将下面的代码添加到方法中:

1.// remove left view and reset variables, if needed

2.if (_leftPanelViewController != nil)

3.{

4. [self.leftPanelViewController.view removeFromSuperview];

5. self.leftPanelViewController = nil;

6.

7. _centerViewController.leftButton.tag = 1;

8. self.showingLeftPanel = NO;

9.}

10.

11.// remove view shadows

12.[self showCenterViewWithShadow:NO withOffset:0];

上边的代码将left panel从view中移除,并将kitties按钮重置为1(表示center view目前是在最初的位置),另外还移除了center view的圆角和阴影效果。

 

编译并运行程序,当点击kitties按钮后,再次点击kitties按钮,center view会回到最初的位置,如下图所示:
 


 


现在靠向右边
在MainViewController.m文件中,将下面的import语句添加到文件顶部:
1. #import "RightPanelViewController.h"

然后添加下面的常量定义:
1. #define RIGHT_PANEL_TAG 3

接着在@interface里面添加如下属性,这样就容易获取到right view和它的当前状态:
1.@property (nonatomic, strong) RightPanelViewController *rightPanelViewController;

找到getRightView方法,移除里面已有的代码,把下面的代码添进去:
1. // init view if it doesn't already exist
2. if (_rightPanelViewController == nil)
3. { 
4.          // this is where you define the view for the right panel 
5.          self.rightPanelViewController = [[RightPanelViewController alloc] initWithNibName:@"RightPanelViewController" bundle:nil]; 
6.          self.rightPanelViewController.view.tag = RIGHT_PANEL_TAG; 
7.          self.rightPanelViewController.delegate = _centerViewController;  
8. 
9.          [self.view addSubview:self.rightPanelViewController.view];  
10.
11.         [self addChildViewController:self.rightPanelViewController]; 
12.         [_rightPanelViewController didMoveToParentViewController:self]; 
13. 
14.          _rightPanelViewController.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
15.} 
16.
17.self.showingRightPanel = YES; 
18.
19.// set up view shadows
20.[self showCenterViewWithShadow:YES withOffset:2]; 
21.
22.UIView *view = self.rightPanelViewController.view;
23.return view;

上面的代码是拷贝getLeftView的,只不过其中的类和属性不同。如对上面的代码有疑问,可回头看看之前的解释。

 

跟之前的一样,在xib文件中已经连接好了相关的IBAction和IBOutlet。下面是CenterViewController.xib文件的一个截图,显示出了puppies按钮的连接关系:
 

 
如上图所示,跟kitties按钮类似,puppies按钮连接到的IBOutlet是rightButton,IBAction是btnMovePanelLeft:。这个按钮控制着center panel的滑动以显示出右边的panel。下面我们就来让panel移动起来吧。
 

打开CenterController.m文件,并将下面的代码添加到btnMovePanelLeft:中:
1. UIButton *button = sender;   
2. switch (button.tag) { 
3.         case 0: {  
4.                  [_delegate movePanelToOriginalPosition];  
5.                  break; 
6.            }  
7.
8.            case 1: {  
9.                    [_delegate movePanelLeft];  
10.                   break; 
11.           }  
12.
13.           default:  
14.                  break;
15. }

同样,上面的代码与btnMovePanelRight:方法的实现没有什么不同。可以看到delegate的调用方法几乎是一样的。

 

因为之前已经实现了movePanelToOriginalPostion方法,所以剩下的任务只需要添加movePanelLeft 方法,并修改一下resetMainView以处理right panel即可。

 

将右边显示出来
打开MainViewController.m文件,并将下面的代码添加到movePanelLeft:方法中:

UIView *childView = [self getRightView];

[self.view sendSubviewToBack:childView]; 
[UIView animateWithDuration:SLIDE_TIMING delay:0 options:UIViewAnimationOptionBeginFromCurrentState    animations:^{     
                  _centerViewController.view.frame = CGRectMake(-self.view.frame.size.width + PANEL_WIDTH, 0, self.view.frame.size.width, self.view.frame.size.height);
    }    
                               completion:^(BOOL finished) {     
                                   if (finished) {

      _centerViewController.rightButton.tag = 0;

                                   }


上面的代码与movePanelRight方法中的基本相同,这里就不再做过多的解释。接着找到resetMainView方法,并用下面的代码替换已有的内容:

// remove left and right views, and reset variables, if needed
if (_leftPanelViewController != nil)

         [self.leftPanelViewController.view removeFromSuperview]; 
         self.leftPanelViewController = nil;

   _centerViewController.leftButton.tag = 1;
 self.showingLeftPanel = NO;

if (_rightPanelViewController != nil)

          [self.rightPanelViewController.view removeFromSuperview]; 
          self.rightPanelViewController = nil;  

          _centerViewController.rightButton.tag = 1; 
          self.showingRightPanel = NO;
}

// remove view shadows
[self showCenterViewWithShadow:NO withOffset:0];

上面代码中唯一修改的地方是增加了一个if语句代码块:if(_rightPanelViewController != nil)。该语句判断一下right panel view是否存在,这跟之前检查left panel view一样,并且对_rightPanelViewController做相同的处理!

 

编译并运行程序,点击puppies按钮,将看到如下画面:

 


 

 

在下面一节中,将介绍如何添加手势功能。

 

来回移动你的手指
在程序中添加手势处理非常简单,不要以为太复杂,很容易就能实现的!
 

 
还是在MainViewController.m文件中,找到setupView方法,并在方法的尾部添加如下一行代码:
[self setupGestures];
 

接着,需要让MainViewController遵循UIGestureRecognizerDelegate协议——将UIGestureRecognizerDelegate添加到文件顶部的@interface中,添加后的代码如下:
UIGestureRecognizerDelegate

 

改变后的代码行:
@interface MainViewController () <UIGestureRecognizerDelegate, CenterViewControllerDelegate>


最后,找到setupGestures方法,并将下面的代码块添加进去:

UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(movePanel:)];
[panRecognizer setMinimumNumberOfTouches:1];
[panRecognizer setMaximumNumberOfTouches:1];
[panRecognizer setDelegate:self];

[_centerViewController.view addGestureRecognizer:panRecognizer];

上面的代码定义了一个UIPanGestureRecognizer,并将movePanel:方法赋值给它,当有检测到手势时,就会调用这个方法。(稍后需要实现movePanel:方法。)

 

接着,配置一下panRecognizer:将触摸的最大数目和最小数目设置为1,另外还设置了一下delegate。最后,将刚刚创建好的panRecognizer添加到_centerViewController.view中。

注意:更多关于UIGestureRecognizer类的信息, 请参考苹果官方文档

接着,再做一件事情就可以用手势进行滑动了。

 

现在就来移动View吧
当识别到手势之后会调用movePanel:方法。所以,本文最后一个任务就是来实现一下这个方法。

movePanel:方法使用到两个属性:showPanel 和 preVelocity。在MainViewController.m文件的@interface中添加上这两个属性:
@property (nonatomic, assign) BOOL showPanel;
@property (nonatomic, assign) CGPoint preVelocity;

 

找到movePanel: 并将下面这段代码添加进去(很多!):
[[[(UITapGestureRecognizer*)sender view] layer] removeAllAnimations];

CGPoint translatedPoint = [(UIPanGestureRecognizer*)sender translationInView:self.view];
CGPoint velocity = [(UIPanGestureRecognizer*)sender velocityInView:[sender view]];

if([(UIPanGestureRecognizer*)sender state] == UIGestureRecognizerStateBegan) {        
       UIView *childView = nil;        

           if(velocity.x &gt; 0) {            
               if (!_showingRightPanel) {               
                    childView = [self getLeftView];            
            }        
         } else {            
             if (!_showingLeftPanel) {               
                 childView = [self getRightView];            
         }       

          }       
          // Make sure the view you're working with is front and center.        
          [self.view sendSubviewToBack:childView];

          [[sender view] bringSubviewToFront:[(UIPanGestureRecognizer*)sender view]];
}

if([(UIPanGestureRecognizer*)sender state] == UIGestureRecognizerStateEnded) {  
       if(velocity.x &gt; 0) {            
           // NSLog(@"gesture went right");        
       } else {            
           // NSLog(@"gesture went left");        
       }        

       if (!_showPanel) {            
          [self movePanelToOriginalPosition];       
        } else {            
            if (_showingLeftPanel) {                
                [self movePanelRight];            
            }  else if (_showingRightPanel) {                
                [self movePanelLeft];            
            }       
        }
}

if([(UIPanGestureRecognizer*)sender state] == UIGestureRecognizerStateChanged) {       
       if(velocity.x &gt; 0) {            
            // NSLog(@"gesture went right");        
       } else {            
           // NSLog(@"gesture went left");        
       }        

      // Are you more than halfway? If so, show the panel when done dragging by setting this value to YES (1).
      _showPanel = abs([sender view].center.x - _centerViewController.view.frame.size.width/2) &gt; _centerViewController.view.frame.size.width/2;        

      // Allow dragging only in x-coordinates by only updating the x-coordinate with translation position.        
      [sender view].center = CGPointMake([sender view].center.x + translatedPoint.x, [sender view].center.y);       
      [(UIPanGestureRecognizer*)sender setTranslation:CGPointMake(0,0) inView:self.view];         
      
      // If you needed to check for a change in direction, you could use this code to do so.              if(velocity.x*_preVelocity.x + velocity.y*_preVelocity.y &gt; 0) {            
          // NSLog(@"same direction");        
      } else {            
          // NSLog(@"opposite direction");        
      }        

      _preVelocity = velocity;
}

上面代码中的注视已经有对功能做了不错的解释。下面是一些需要明白的关键信息:

 

需要处理3个状态:

UIGestureRecognizerStateBegan, UIGestureRecognizerStateEnded和 UIGestureRecognizerStateChanged。 
translationInView:返回某个位置在指定view的坐标系中的point,并将这个point赋值给translatedPoint变量, 该变量用来设置view的位置。 
velocityInView: 返回每秒钟手势的移动速率。该变量有助于确定方向的改变。

你可以移动center,left和right view,并结合上面提到的3个状态,就可以确定手势的位置和速率/方向。

 

例如,如果手势方向是向右的,那么就显示出left panel。如果方向是向左,则显示出right panel。通过查看代码和相关的注释,就可以知道每一种状态都发生了什么。

 

再次编译并运行程序。现在应该可以把center panel滑动到左边或者右边了,并显示出center panel下面的panel。



 

何去何从
如果你完全按照本文介绍的方法来操作,那么恭喜你,你已经成为一名滑出式导航面板忍者了!
 

 

希望本文对你有用!这里是本文涉及到的完整示例工程:completed project file

 

之前我提到过,如果你更喜欢已经定义好了的库,而不是DIY,那么请看SWRevealViewController. 看看这里的开发者相关介绍,很容易就能使用它了。

 


源址:http://www.cocoachina.com/industry/20130418/6030.html



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值