本文是IOS开发教程系列的第一篇,主要讲诉了如何仿网易新闻客户端实现抽屉效果,全部源代码都分享给大家,希望对大家有所帮助
最终效果图:
MainStoryBoard示意图:
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












