IOS仿网易新闻客户端左右侧栏

摘要:
左右侧边栏已经是当前APP最流行的布局。许多客户端软件使用左右侧边栏,如网易新闻、人人网、维科等。本博客以当前网易新闻客户端模式为例,模仿了左右侧边栏架构的实现。controller){Classc=NSClassFromString;HRViewController*vc=[[calloc]init];controller=[[UINavigationController.loc]initWithRootViewController:vc];vc.contentText=model.contentText;[controllersDictsetObject:controllerforKey:model.className];}如果{UIView*view=[_mainContentView.subviewsfrstObject];[viewremoveFromSuperview];}controller.view。frame=_ mainContentView。框架[_mainContentViewaddSubview:controller.view];}配置导航栏的内容被省略。在此方法中,还有另一个[_mainContentView.subviewsfrstObject]方法。这种方法是IOS 7独有的。按照以前的习惯写[0]也更好。视图移动动画主要通过仿射变换实现,并通过对帧的操作使用了一些示例。


左右侧栏已经是当前APP最流行的布局,很多客户端软件都使用了左右侧栏,例如网易新闻,人人网,Weico等等。

这篇博客以当前网易新闻客户端的模式为例仿写了一个左右侧栏架构实现。


先看一下Demo的实现效果

IOS仿网易新闻客户端左右侧栏第1张 IOS仿网易新闻客户端左右侧栏第2张 IOS仿网易新闻客户端左右侧栏第3张 


实现主要思路以及细节:

视图控制器有三个视图按不同层次排列,最上层的是主要显示视图_mainContentView,下面的为左右侧栏视图;

点击左侧栏不同按钮压入不同的主视图控制器;

在显示侧栏时点击视图空白区域闭合,利用tap手势;

拖动主页面根据不同的方向和位置进行移动和缩放, 利用pan手势;

向右拖显示左侧栏,向左拖显示右侧栏;


首先,点击左侧栏时,左侧栏将点击的数据模型传给分栏控制器,让其更改主视图内容

模型:

@interface ClassModel : NSObject

@property (strong, nonatomic) NSString *title;
@property (strong, nonatomic) NSString *className;
@property (strong, nonatomic) NSString *contentText;
@property (strong, nonatomic) NSString *imageName;

+ (id)classModelWithTitle:(NSString *)title className:(NSString *)className contentText:(NSString *)text andImageName:(NSString *)imageName;

@end


一个工厂方法,模型存入不同选择下的不同视图控制器的具体内容。

以新闻页为例:

ClassModel *newscm = [ClassModel classModelWithTitle:@"新闻" className:@"NewsViewController" contentText:@"新闻视图内容" andImageName:@"sidebar_nav_news"];


来看主视图切换不同界面的方法:

- (void)showContentControllerWithModel:(ClassModel *)model
{
    [self closeSideBar];
    
    UIViewController *controller = _controllersDict[model.className];
    if (!controller)
    {
        Class c = NSClassFromString(model.className);
        HRViewController *vc = [[c alloc] init];
        controller = [[UINavigationController alloc] initWithRootViewController:vc];
        
        vc.contentText = model.contentText;
        [_controllersDict setObject:controller forKey:model.className];
    }
    
    if (_mainContentView.subviews.count > 0)
    {
        UIView *view = [_mainContentView.subviews firstObject];
        [view removeFromSuperview];
    }
    
    controller.view.frame = _mainContentView.frame;
    [_mainContentView addSubview:controller.view];
}


中间省略了配置导航栏的内容。

这个方法中又一个[_mainContentView.subviews firstObject]方法,这个方法是IOS7才有的,按以前习惯写[0]也好。


实现视图移动动画主要是通过仿射变换来实现的,也有的例子通过操作frame,看习惯了。


一个根据方向返回仿射变换值的私有方法

- (CGAffineTransform)transformWithDirection:(RMoveDirection)direction
{
    CGFloat translateX = 0;
    switch (direction) {
        case RMoveDirectionLeft:
            translateX = -RContentOffset;
            break;
        case RMoveDirectionRight:
            translateX = RContentOffset;
            break;
        default:
            break;
    }
    
    MyLog(@"%.2f",translateX);
    CGAffineTransform transT = CGAffineTransformMakeTranslation(translateX, 0);
    CGAffineTransform scaleT = CGAffineTransformMakeScale(1.0, RContentScale);
    CGAffineTransform conT = CGAffineTransformConcat(transT, scaleT);
    
    return conT;
}


一个根据方向配置页面的阴影效果的私有方法

- (void)configureViewShadowWithDirection:(RMoveDirection)direction
{
    CGFloat shadowW;
    switch (direction)
    {
        case RMoveDirectionLeft:
            shadowW = 2.0f;
            break;
        case RMoveDirectionRight:
            shadowW = -2.0f;
            break;
        default:
            break;
    }
    
    _mainContentView.layer.shadowOffset = CGSizeMake(shadowW, 1.0);
    _mainContentView.layer.shadowColor = [UIColor blackColor].CGColor;
    _mainContentView.layer.shadowOpacity = 0.8f;
}


点击导航栏左侧按钮的展开侧栏方法(右侧类似)

- (void)leftItemClick
{
    CGAffineTransform conT = [self transformWithDirection:RMoveDirectionRight];
    
    [self.view sendSubviewToBack:_rightSideView];
    [self configureViewShadowWithDirection:RMoveDirectionRight];
    
    [UIView animateWithDuration:ROpenDuration
                     animations:^{
                         _mainContentView.transform = conT;
                     }
                     completion:^(BOOL finished) {
                         _tapGestureRec.enabled = YES;
                     }];
}


关闭侧栏返回主页面的方法

- (void)closeSideBar
{
    CGAffineTransform oriT = CGAffineTransformIdentity;
    [UIView animateWithDuration:RCloseDuration
                     animations:^{
                         _mainContentView.transform = oriT;
                     }
                     completion:^(BOOL finished) {
                         _tapGestureRec.enabled = NO;
                     }];
}


最后是最重要的,手势拖动的实现方法


- (void)moveViewWithGesture:(UIPanGestureRecognizer *)panGes
{
    static CGFloat currentTranslateX;
    if (panGes.state == UIGestureRecognizerStateBegan)
    {
        currentTranslateX = _mainContentView.transform.tx;
    }
    if (panGes.state == UIGestureRecognizerStateChanged)
    {
        CGFloat transX = [panGes translationInView:_mainContentView].x;
        transX = transX + currentTranslateX;
        
        CGFloat sca;
        if (transX > 0)
        {
            [self.view sendSubviewToBack:_rightSideView];
            [self configureViewShadowWithDirection:RMoveDirectionRight];
            
            if (_mainContentView.frame.origin.x < RContentOffset)
            {
                sca = 1 - (_mainContentView.frame.origin.x/RContentOffset) * (1-RContentScale);
            }
            else
            {
                sca = RContentScale;
            }
        }
        else    //transX < 0
        {
            [self.view sendSubviewToBack:_leftSideView];
            [self configureViewShadowWithDirection:RMoveDirectionLeft];
            
            if (_mainContentView.frame.origin.x > -RContentOffset)
            {
                sca = 1 - (-_mainContentView.frame.origin.x/RContentOffset) * (1-RContentScale);
            }
            else
            {
                sca = RContentScale;
            }
        }
        CGAffineTransform transS = CGAffineTransformMakeScale(1.0, sca);
        CGAffineTransform transT = CGAffineTransformMakeTranslation(transX, 0);
        
        CGAffineTransform conT = CGAffineTransformConcat(transT, transS);
        
        _mainContentView.transform = conT;
    }
    else if (panGes.state == UIGestureRecognizerStateEnded)
    {
        CGFloat panX = [panGes translationInView:_mainContentView].x;
        CGFloat finalX = currentTranslateX + panX;
        if (finalX > RJudgeOffset)
        {
            CGAffineTransform conT = [self transformWithDirection:RMoveDirectionRight];
            [UIView beginAnimations:nil context:nil];
            _mainContentView.transform = conT;
            [UIView commitAnimations];
            
            _tapGestureRec.enabled = YES;
            return;
        }
        if (finalX < -RJudgeOffset)
        {
            CGAffineTransform conT = [self transformWithDirection:RMoveDirectionLeft];
            [UIView beginAnimations:nil context:nil];
            _mainContentView.transform = conT;
            [UIView commitAnimations];
            
            _tapGestureRec.enabled = YES;
            return;
        }
        else
        {
            CGAffineTransform oriT = CGAffineTransformIdentity;
            [UIView beginAnimations:nil context:nil];
            _mainContentView.transform = oriT;
            [UIView commitAnimations];
            
            _tapGestureRec.enabled = NO;
        }
    }
}


根据不同的状态设置不同状态下的仿射变换值,来赋给_mainContentView,在到达临界状态后,该视图的缩放不再进行。

当拖动手势结束后,根据拖动的位置,来确定视图如何进行移动。

这个例子主要的代码都贴在了上面,这里的素材是随便取的。现在的网易客户端的侧栏背景为深色视图,左侧栏栏目跟这个demo差不多。



Demo源码:点击打开链接



以上就是本篇博客全部内容,欢迎指正和交流。转载注明出处~~

免责声明:文章转载自《IOS仿网易新闻客户端左右侧栏》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇HTML5 Audio/Video 标签,属性,方法,事件汇总 (转)@ControllerAdvice下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

随便看看

CentOS 7 优化TCP链接

在优化服务器配置时,Summary发现服务器端的WAIT连接上有大量的TIME,需要进行优化。Tomcat案例查询与Tomcat对应的端口的tcp链接,发现存在大量TIME_WAIT链接,以及一些其他状态连接,总计400+。...

怎么使用vscode合并分支

1.切换分支到本地开发2.代码完成后提交代码到本地仓库3.切换分支到需要merge的test分支先pull一下,之后再合并分支—我开发的是这个feature,就合并这个分支4.当合并分支后,需要重新提交到远程:点击一下,直接提交...

docker安装MySQL5.7示例!!坑,ERROR 1045 (28000): Access denied for user

处理mysql1045错误1.在/usr/local/mysql/conf中添加一个文件。d目录:mysql文件的内容是:[mysqld]skip-grant-tables2重新启动mysql:dockerstartmysql5.73进入docker:dockerexec-itmysql5.7bash4登录mysql:mysql-uroot-p5将root密...

WinForm 中 comboBox控件之数据绑定

作为列表类型,public class Info{public string Id{get;Name=“Li Si”};infoList.Add(info3);...

Vue跨层级传递slot的方法

但是我需要通过插槽在父组件中指定一个模板,而B组件引用C组件。组件C的部分模板需要在组件A中配置。模板引用A组件:{{node.text}}&lt;模板引用B组件:spanslot=“nodeMenu”slot scope=“{node}”&gt;node=“node”&gt;/span&gt;/div&gt;2.2如...

Nacos开机自启

1.加入玉米片。服务文件vi/lib/systemd/system/nacos.service2.将以下内容写入nacos。服务文件ps:我的nacos路径是/usr/local/nacos[Unit]Description=nacosAfter=network。target[Service]Type=forkingExecStart=/usr/local/...