欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

iOS开发之路--仿网易抽屉效果

程序员文章站 2022-06-16 13:23:16
最终效果图: mainstoryboard示意图: beyondviewcontroller.h // // beyondviewcontr...

最终效果图:

iOS开发之路--仿网易抽屉效果

mainstoryboard示意图:

iOS开发之路--仿网易抽屉效果

beyondviewcontroller.h

//
// beyondviewcontroller.h
// 19_抽屉效果_仿网易
//
// created by beyond on 14-8-1.
// copyright (c) 2014年 com.beyond. all rights reserved.
//

#import <uikit/uikit.h>
#import "lefttableviewcontrollerdelegate.h"



@interface beyondviewcontroller : uiviewcontroller
// 左半边 (显示 的是栏目列表 )
@property (weak, nonatomic) iboutlet uiview *leftview;
// 右半边 (显示 的是个人信息设置视图)
@property (weak, nonatomic) iboutlet uiview *rightview;
// 最上面,最大的全屏的是主视图
@property (weak, nonatomic) iboutlet uiview *mainview;



// 上面标题状态栏视图中的标题按钮 (网易的logo图片和栏目的名称 水平排列)
@property (weak, nonatomic) iboutlet uibutton *titlebtn;

// mainview的下半部分 是 正文的view,显示子栏目的view
@property (weak, nonatomic) iboutlet uiview *contentview;



// pan 拽 手势处理
- (ibaction)pangesture:(uipangesturerecognizer *)sender;

// mainview的上半部分 标题状态栏视图中的左,右按钮
- (ibaction)btnclick:(uibutton *)sender;


@end

beyondviewcontroller.m

//
// beyondviewcontroller.m
// 19_抽屉效果_仿网易
//
// created by beyond on 14-8-1.
// copyright (c) 2014年 com.beyond. all rights reserved.
//

#import "beyondviewcontroller.h"
#import "lefttableviewcontroller.h"
#import "rightviewcontroller.h"
#import "column.h"
#import <quartzcore/quartzcore.h>
// 手势结束时的x
#define kendx frame.origin.x
// 左view的宽度
#define kleftwidth _leftview.frame.size.width

// 右view的宽度
#define krightwidth _rightview.frame.size.width

// 对协议进行提前声明
@protocol lefttableviewcontrollerdelegate ;

@interface beyondviewcontroller ()<lefttableviewcontrollerdelegate>
{
  // 手指按下的时候,记住,mainview的起始x
  cgfloat _startx;
  
  // 成员变量,记住左边控制器的实例
  lefttableviewcontroller *_leftvc;
  // 成员变量,记住右边控制器的实例
  rightviewcontroller *_rightvc;
  
  
  // 字典 ,记住所有实例化了 栏目的子控制器,避免每次都重新创建
  nsmutabledictionary *_columnviewcontrollers;
  
}

@end

@implementation beyondviewcontroller
// 隐藏状态栏
- (bool)prefersstatusbarhidden
{
  return yes;
}
- (void)viewdidload
{
  [super viewdidload];
  
  _titlebtn.backgroundcolor = [uicolor clearcolor];
  
  // 0 字典 ,记住所有实例化了 栏目的子控制器,避免每次都重新创建
  _columnviewcontrollers = [nsmutabledictionary dictionary];
  
  // 0,设置导航条bar的背景 为网 易 红
  //[[uinavigationbar appearance] setbackgroundimage:[uiimage imagenamed:@"bg.png"] forbarmetrics:uibarmetricsdefault];
  // 状态条颜色 改成默认的样式
  //[uiapplication sharedapplication].statusbarstyle = uistatusbarstyledefault;
  
  // 1,添加左边控制器的view到左边的view里面
  _leftvc = [[lefttableviewcontroller alloc]init];
  // 关键代码 为了拿到左边控制器的某一行被点击时候,对应的栏目数据模型对象,主控制器成为了左边控制器的代理,遵守了它定好的协议,实现了协议中的方法,从而拿到左边控制器被点击行号对应的数据模型对象
  _leftvc.delegate = self;
  _leftvc.view.frame = self.leftview.bounds;
  
  [self.leftview addsubview:_leftvc.view];
  
  // 2,同理,添加右边控制器的view到右边的view里面
	_rightvc = [[rightviewcontroller alloc]init];
  
  _rightvc.view.frame = self.rightview.bounds;
  
  [self.rightview addsubview:_rightvc.view];

  // 3,第一次加载时候,就就应该显示新闻 子栏目的控制器到导航控制器,再将导航控制器的view添加到 mainview里面
  [self firstloading];
  
  
}


// 自定义方法,第一次加载时候,就就应该显示新闻 子栏目的控制器到导航控制器,再将导航控制器的view添加到 mainview里面
- (void)firstloading
{
  column * column = [column columnnamed:@"新闻" imgname:@"news.png" classname:@"newsviewcontroller"];
  
  // 仅需手动调用一个 leftviewcontroller的代理 方法,lefttableviewrowclicked,传递一个新闻 子栏目即可
  [self lefttableviewrowclicked:column];
}

// pan 拽 手势处理
- (ibaction)pangesture:(uipangesturerecognizer *)sender
{
  
  // 如果是刚按下的状态,则记住,mainview的起始x
  if (uigesturerecognizerstatebegan == sender.state) {
    _startx = self.mainview.frame.origin.x;
  }
  
  
  // 平移拖动的距离
  cgpoint delta = [sender translationinview:_mainview];
  
  cgrect frame = self.mainview.frame;
  
  // 计算新的x值,并做健壮性判断
  kendx = _startx + delta.x;
  
  // 1,限制最大拖动范围
  
  if (kendx >= kleftwidth) {
    kendx = kleftwidth;
  }
  if (kendx <= - krightwidth) {
    kendx = - krightwidth;
  }
  // 2,由于 左view和右view在重叠,所以要隐藏其中的一个
  if (kendx > 0) {
    // nslog(@"--调用频率相当高--");
    _rightview.hidden = yes;
    _leftview.hidden = no;
  } else {
    _rightview.hidden = no;
    _leftview.hidden = yes;
  }
  
  
  if (uigesturerecognizerstateended == sender.state) {
    
    // 手势结束的时候,需进行robust判断
    
    // 2,分析end松手时候,的位置x,决定展开到什么程度
/*
    // 2.1 如果只向右拖了一点点,小于 1/2 的左view的宽度,则归0
    if (kendx < 0.5*kleftwidth && kendx >= 0) {
      kendx = 0;
    }else if (kendx >= 0.5*kleftwidth && kendx <= kleftwidth) {
      // 2.2 如果向右拖一大半了,大于 1/2 的左view的宽度,虽然还没到位,也可以认为是到位了
      kendx = kleftwidth;
    }else if (kendx > - 0.5*krightwidth && kendx <= 0) {
      // 2.3 如果只向左拖了一点点,小于 1/2 的右view的宽度,则归0
      kendx = 0;
    }else if (kendx <= - 0.5*krightwidth) {
      // 2.4 如果向左拖一大半了,大于 1/2 的右view的宽度,虽然还没到位,也可以认为是到位了
      kendx = - krightwidth;
    }
*/
    
    
    // 第2种判断方式
    // 起始为0,delta.x大于0 代表向右滑动
    if (_startx == 0 && delta.x >0) {
      kendx = kleftwidth;
    }else if (_startx == 0 && delta.x < 0){
      // 起始为0,delta.x小于0 代表向左滑动
      kendx = - krightwidth;
    }else if (_startx == kleftwidth && delta.x < 0){
      // 起始为kleftwidth,delta.x小于0 代表向左滑动
      kendx =0;
    }else if (_startx == - krightwidth && delta.x > 0){
      // 起始为- krightwidth,delta.x大于0 代表向右滑动
      kendx = 0;
    }
    

    
  }
  
  // 最后,才设置mainview的新的frame
  [uiview animatewithduration:0.2 animations:^{
        self.mainview.frame=frame;
  }];
  
  
  
  // 最后,为mainview所在的图层 添加阴影效果
  [self addshadowformainviewwithendx:kendx];
  
}

// 自定义方法,为mainview所在的图层 添加阴影效果 (调用频率相当高)
- (void)addshadowformainviewwithendx:(cgfloat)endx
{
  // 1,点击工程,加号,导入第3方框架 #import <quartzcore/quartzcore.h>
  
  // 2,拿到mainview所在的图层,设置阴影 参数
 
  // nslog(@"调用频率很高---");
  _mainview.layer.shadowcolor = [uicolor blackcolor].cgcolor;
  _mainview.layer.shadowopacity = 0.5;
  if (endx >= 0) {
    _mainview.layer.shadowoffset = cgsizemake(-5, 0);
  } else {
    _mainview.layer.shadowoffset = cgsizemake(5, 0);
  }
  
}

// 单击按钮,也一样可以展开 左右侧边栏
- (ibaction)btnclick:(uibutton *)sender
{
  // 定义一个临时变量
  cgfloat startx = _mainview.frame.origin.x;
  
  
  // 先为mainview所在的图层 添加阴影效果
  [self addshadowformainviewwithendx:sender.tag == 1?1:-1];
  
  
  // 定义一个临时变量
  cgfloat tempendx = 0;
  // 左边的按钮被单击
  if (1 == sender.tag) {
    // 隐藏右半边
    _leftview.hidden = no;
    _rightview.hidden = yes;
    
    if (startx == 0) {
      tempendx = kleftwidth;
    }else if (startx == kleftwidth){
      tempendx = 0;
    }
  } else {
    // 单击右边按钮, 隐藏左半边
    _leftview.hidden = yes;
    _rightview.hidden = no;
    if (startx == 0) {
      tempendx = - krightwidth;
    }else if (startx == - krightwidth){
      tempendx = 0;
    }
  }
  // 最后才设置mainview的x,调用抽取出来的公共代码,设置mainview的x,参数是endx
  [self setmainviewx:tempendx];
  
  

}


// 抽取出来的公共代码,设置mainview的x,参数是endx
- (void)setmainviewx:(cgfloat)endx
{
  cgrect frame = self.mainview.frame;
  frame.origin.x = endx;
  [uiview animatewithduration:0.2 animations:^{
    self.mainview.frame=frame;
  }];
  
}



// 最关键的方法,左边控制器的代理 方法,当前左边控制器中的某一行被点击的时候 会调用
- (void)lefttableviewrowclicked:(id)columnselected
{
  column *column = (column *)columnselected;
  // 1,关闭左边的控制=======================
  // 调用抽取出来的公共代码,设置mainview的x,参数是endx
  [self setmainviewx:0];
  
  
  
  // 2,更改标题按钮上面的文字
  _titlebtn.titlelabel.text = column.columnname;
  
  // 根据栏目数据模型中的类名,实例化对应栏目的控制器,并且将其设置为导航控制器的根控制器,最后将导航控制器的view添加到mainview中,目的是方便设置导航条,以及,各控制器的跳转
  
  
  // 2,从缓存字典中取,如果子控制器字典有曾经创建过的子控制器,直接取出来用
  uiviewcontroller *columnvc = _columnviewcontrollers[column.columnclassname];
  // 如果子控制器字典中没有保存过该栏目的控制器,才要创建子控制器
  if (columnvc == nil) {
    class c = nsclassfromstring(column.columnclassname);
    columnvc = [[c alloc]init];
    // 并且一定要将其放到 子控制器字典里面,存起来
    [_columnviewcontrollers setobject:columnvc forkey:column.columnclassname];
  }
    
  // 4,移除contentview中的正在显示的旧的子view
  if (_contentview.subviews.count > 0) {
    uiview *oldview = [_contentview subviews][0];
    [oldview removefromsuperview];
  }

  // 5,最后将子控制器的view添加到contentview中,显示
  columnvc.view.frame = _contentview.bounds;
  [self.contentview addsubview:columnvc.view];
  nslog(@"%@",self.contentview);
  // 在添加到mainview之前 ,先得到mainview导航控制器的子控制器,并将其移除(如果有的话),然后才将新的栏目对应的子控制器添加到导航控制器容器中,注意,这儿可以用字典 记住 所有的已经实例化出来 的栏目子控制器,这样就避免每次都alloc创建新的栏目子控制器,而是只需要根据类名,从字典取出上一次实例化了的同一栏目的子控制器即可
  
}
@end

栏目数据模型column.h

//
// column.h
// 19_抽屉效果_仿网易
//
// created by beyond on 14-8-1.
// copyright (c) 2014年 com.beyond. all rights reserved.
//

#import <foundation/foundation.h>

// 数据模型 代表一个栏目
@interface column : nsobject
// 栏目名称
@property (nonatomic,copy)nsstring *columnname;
// 栏目图片名称
@property (nonatomic,copy)nsstring *columnimgname;
// 栏目对应的控制器的类名
@property (nonatomic,copy)nsstring *columnclassname;

// ui控件用weak,字符串用copy,其他对象用strong

// 提供一个类方法,即构造函数,返回封装好数据的对象(返回id亦可)
+ (column *)columnnamed:(nsstring *)columnname imgname:(nsstring*)columnimgname classname:(nsstring *)columnclassname;
@end

栏目数据模型column.m

//
// column.m
// 19_抽屉效果_仿网易
//
// created by beyond on 14-8-1.
// copyright (c) 2014年 com.beyond. all rights reserved.
// 数据模型 代表一条栏目

#import "column.h"

@implementation column

// 返回一个包含了 栏目对应控制器名字的 对象实例
+ (column *)columnnamed:(nsstring *)columnname imgname:(nsstring *)columnimgname classname:(nsstring *)columnclassname
{
  // 为了兼容子类 使用self
  column *column = [[self alloc]init];
  column.columnname = columnname;
  column.columnimgname = columnimgname;
  column.columnclassname = columnclassname;
  return column;
}

@end

左边控制器定义好的协议lefttableviewcontrollerdelegate.h

//
// lefttableviewcontrollerdelegate.h
// 19_抽屉效果_仿网易
//
// created by beyond on 14-8-1.
// copyright (c) 2014年 com.beyond. all rights reserved.
//

#import <foundation/foundation.h>
#import "column.h"
// 左边控制器 定义的代理/协议 它通过调用自己的成员属性(即代理)的该方法,将数据传递出去(给它的代理去使用) (其实 是主控制器想要数据,所以主控制器在实例化左边控制器的时候,要设置左边控制器对应的代理 为 主控制器 自身)
@protocol lefttableviewcontrollerdelegate <nsobject>


- (void)lefttableviewrowclicked:(column *)columnselected;
@end

lefttableviewcontroller.h

//
// lefttableviewcontroller.h
// 19_抽屉效果_仿网易
//
// created by beyond on 14-8-1.
// copyright (c) 2014年 com.beyond. all rights reserved.
//

#import <uikit/uikit.h>
// 对协议进行提前声明
@protocol lefttableviewcontrollerdelegate;


@interface lefttableviewcontroller : uitableviewcontroller
// 代理 用weak,防止循环问题,可以是任意类型,但必须遵守协议
@property (nonatomic,weak) id<lefttableviewcontrollerdelegate> delegate;
@end

lefttableviewcontroller.m

//
//  lefttableviewcontroller.m
//  19_抽屉效果_仿网易
//
//  created by beyond on 14-8-1.
//  copyright (c) 2014年 com.beyond. all rights reserved.
//
#import "lefttableviewcontroller.h"
#import "column.h"
#import "lefttableviewcontrollerdelegate.h"
@interface lefttableviewcontroller ()
{
    // 栏目数组,保存的是左边栏目列表中的所有栏目对象
    nsarray *_arr;
}
@end
@implementation lefttableviewcontroller
- (void)viewdidload
{
    [super viewdidload];
    // 新闻 栏目
    column *newscolumn = [column columnnamed:@"新闻" imgname:@"news.png" classname:@"newsviewcontroller"];
    // 图片 栏目
    column *piccolumn = [column columnnamed:@"图片" imgname:@"pic.png" classname:@"picviewcontroller"];
    // 图片 栏目
    column *commentcolumn = [column columnnamed:@"跟帖" imgname:@"comment.png" classname:@"commentviewcontroller"];
    // 以后要添加栏目,只要改这里就可以了
   
   
    // 将栏目对象,一次性全添加到不可变数组中
    _arr = @[newscolumn,piccolumn,commentcolumn];
   
}
- (nsinteger)tableview:(uitableview *)tableview numberofrowsinsection:(nsinteger)section
{
    return _arr.count;
}
- (uitableviewcell *)tableview:(uitableview *)tableview cellforrowatindexpath:(nsindexpath *)indexpath
{
    static nsstring *cellid = @"leftvc";
    // 下面这个dequeue只能用于storyboard或xib中
    // uitableviewcell *cell = [tableview dequeuereusablecellwithidentifier:cellid forindexpath:indexpath];
    //
    uitableviewcell *cell = [tableview dequeuereusablecellwithidentifier:cellid];
   
    if (cell == nil) {
        cell = [[uitableviewcell alloc]initwithstyle:uitableviewcellstyledefault reuseidentifier:cellid];
    }
   
    // 设置独一无二的数据
    column *column = _arr[indexpath.row];
    cell.textlabel.text = column.columnname;
    cell.imageview.image = [uiimage imagenamed:column.columnimgname];
    return cell;
}

// 点击一行时,主控制中的主视图必须展示相应栏目的内容,因此,必须实例化对应点击的行的栏目控制器,并用添加到导航控制器,调用代理 的方法传递数据给代理 使用,此处的代理 其实就是 主控制器
- (void)tableview:(uitableview *)tableview didselectrowatindexpath:(nsindexpath *)indexpath
{
    // 先取消默认的点击 高亮的颜色
    [tableview deselectrowatindexpath:indexpath animated:yes];
    // 取出对应行的数据模型(栏目)
    column *column = _arr[indexpath.row];
   
    if ([self.delegate respondstoselector:@selector(lefttableviewrowclicked:)]) {
       
        // 传递数据给主控制器 beyondviewcontroller,通过代理
        // 关键代码~
        [self.delegate lefttableviewrowclicked:column];
    }
   
   
}
@end

rightviewcontroller.xib

iOS开发之路--仿网易抽屉效果

newsviewcontroller.xib

iOS开发之路--仿网易抽屉效果

picviewcontroller.xib

iOS开发之路--仿网易抽屉效果

commentviewcontroller.xib

iOS开发之路--仿网易抽屉效果