开源项目之Coding-iOS

开源iOS项目

在网上找了一些开源的iOS项目,比如知乎上推荐的GitHub 上都有哪些值得关注学习的 iOS 开源项目,还有github上有人推荐的open-source-ios-apps。这里我找到了一个Coding-iOS的客户端源码,打算研究一下。

Coding-iOS的启动动画

之前看到有人推荐Coding-iOS开源项目,最近自己花了一段时间学习了一下,写一点学习心得。

目录

按以下几部分展开:


应用启动流程

应用启动时会在 application: didFinishLaunchingWithOptions:方法中通过 NSUserDefaults (自定义Login类)读取当前用户的登录状态,根据用户的登录状态(YES/NO)选择是去加载登录后的页面RootTabViewController(高度定制化选项卡控制器,继承RDVTableBarController), 还是应用的引导页面IntroductionViewController(使用了第三方库一个简单的关键帧基础动画框架动画框架-JazzHands)。

引导动画

一共有7张引导页,页面底部放置有两个按钮(“登录”和“注册”),使用了Masonry进行自动布局。

- (void)configureButtonsAndPageControl{
//    Button
    UIColor *darkColor = [UIColor colorWithHexString:@"0x28303b"];
    CGFloat buttonWidth = kScreen_Width * 0.4;
    CGFloat buttonHeight = kScaleFrom_iPhone5_Desgin(38);
    CGFloat paddingToCenter = kScaleFrom_iPhone5_Desgin(10);
    CGFloat paddingToBottom = kScaleFrom_iPhone5_Desgin(20);

    /* 配置按钮的触发方式、回调函数、标题、字体、颜色、边框 */
    self.registerBtn = ({
        UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
        [button addTarget:self action:@selector(registerBtnClicked) forControlEvents:UIControlEventTouchUpInside];

        button.backgroundColor = darkColor;
        button.titleLabel.font = [UIFont boldSystemFontOfSize:20];
        [button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
        [button setTitle:@"注册" forState:UIControlStateNormal];

        button.layer.masksToBounds = YES;
        button.layer.cornerRadius = buttonHeight/2;
        button;
    });
    self.loginBtn = ({
        UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
        [button addTarget:self action:@selector(loginBtnClicked) forControlEvents:UIControlEventTouchUpInside];

        button.backgroundColor = [UIColor clearColor];
        button.titleLabel.font = [UIFont boldSystemFontOfSize:20];
        [button setTitleColor:darkColor forState:UIControlStateNormal];
        [button setTitle:@"登录" forState:UIControlStateNormal];

        button.layer.masksToBounds = YES;
        button.layer.cornerRadius = buttonHeight/2;
        button.layer.borderWidth = 1.0;
        button.layer.borderColor = darkColor.CGColor;
        button;
    });
    /* 注意: 在自动布局前, 一定要先将视图(view)添加到父视图(superview)上 */
    [self.view addSubview:self.registerBtn];
    [self.view addSubview:self.loginBtn];

    /* 使用Masonry进行自动布局, 给按钮添加约束:
     对于注册按钮, size(宽、高)、按钮视图右侧离父视图横向中点的偏移量为负的paddingToCenter、按钮视图下侧离父视图的偏移量为负的paddingToCenter
     可以参考以下链接:http://adad184.com/2014/09/28/use-masonry-to-quick-solve-autolayout/
     */
    [self.registerBtn mas_makeConstraints:^(MASConstraintMaker *make) {
        make.size.mas_equalTo(CGSizeMake(buttonWidth, buttonHeight));
        make.right.equalTo(self.view.mas_centerX).offset(-paddingToCenter);
        make.bottom.equalTo(self.view).offset(-paddingToBottom);
    }];
    [self.loginBtn mas_makeConstraints:^(MASConstraintMaker *make) {
        make.size.mas_equalTo(CGSizeMake(buttonWidth, buttonHeight));
        make.left.equalTo(self.view.mas_centerX).offset(paddingToCenter);
        make.bottom.equalTo(self.view).offset(-paddingToBottom);
    }];

//    PageControl
    UIImage *pageIndicatorImage = [UIImage imageNamed:@"intro_dot_unselected"];
    UIImage *currentPageIndicatorImage = [UIImage imageNamed:@"intro_dot_selected"];

    /* 根据屏幕实际大小与图片设计尺寸的比例, 对图片尺寸进行缩放 */
    if (!kDevice_Is_iPhone6 && !kDevice_Is_iPhone6Plus) {
        CGFloat desginWidth = 375.0;//iPhone6 的设计尺寸
        CGFloat scaleFactor = kScreen_Width/desginWidth; // 缩放因子 = 屏幕的实际宽度/设计宽度
        pageIndicatorImage = [pageIndicatorImage scaleByFactor:scaleFactor];
        currentPageIndicatorImage = [currentPageIndicatorImage scaleByFactor:scaleFactor];
    }

    self.pageControl = ({
        SMPageControl *pageControl = [[SMPageControl alloc] init];
        pageControl.numberOfPages = self.numberOfPages;
        pageControl.userInteractionEnabled = NO;
        pageControl.pageIndicatorImage = pageIndicatorImage;
        pageControl.currentPageIndicatorImage = currentPageIndicatorImage;
        [pageControl sizeToFit];
        pageControl.currentPage = 0;
        pageControl;
    });

    [self.view addSubview:self.pageControl];

    [self.pageControl mas_makeConstraints:^(MASConstraintMaker *make) {
        make.size.mas_equalTo(CGSizeMake(kScreen_Width, kScaleFrom_iPhone5_Desgin(20)));
        make.centerX.equalTo(self.view);
        make.bottom.equalTo(self.registerBtn.mas_top).offset(-kScaleFrom_iPhone5_Desgin(20));
    }];
}

#pragma mark Animations
- (void)configureAnimations{
    [self configureTipAndTitleViewAnimations];
}

- (void)configureTipAndTitleViewAnimations{
    for (int index = 0; index < self.numberOfPages; index++) {
        NSString *viewKey = [self viewKeyForIndex:index];
        UIView *iconView = [self.iconsDict objectForKey:viewKey]; //功能图标
        UIView *tipView = [self.tipsDict objectForKey:viewKey];   //功能提示
        if (iconView) {
            if (index == 0) {
                [self keepView:iconView onPages:@[@(index +1), @(index)] atTimes:@[@(index - 1), @(index)]];
                /* 为icon视图添加布局约束, 设置顶部距离 */
                [iconView mas_makeConstraints:^(MASConstraintMaker *make) {
                    make.top.mas_equalTo(kScreen_Height/7);
                }];
            }else{
                [self keepView:iconView onPage:index];

                [iconView mas_makeConstraints:^(MASConstraintMaker *make) {
                    make.centerY.mas_equalTo(-kScreen_Height/6);
                }];
            }
            //为指定的视图创建一个动画
            IFTTTAlphaAnimation *iconAlphaAnimation = [IFTTTAlphaAnimation animationWithView:iconView];
            /* 添加关键帧, 让icon视图的alpha渐变(淡入、淡出) */
            [iconAlphaAnimation addKeyframeForTime:index -0.5 alpha:0.f]; //alpha为0表示全透明(不可见)
            [iconAlphaAnimation addKeyframeForTime:index alpha:1.f];
            [iconAlphaAnimation addKeyframeForTime:index +0.5 alpha:0.f];
            //注册动画
            [self.animator addAnimation:iconAlphaAnimation];
        }
        if (tipView) {
            [self keepView:tipView onPages:@[@(index +1), @(index), @(index-1)] atTimes:@[@(index - 1), @(index), @(index +1)]];

            IFTTTAlphaAnimation *tipAlphaAnimation = [IFTTTAlphaAnimation animationWithView:tipView];
             /* 添加关键帧, 让tip视图的alpha渐变(淡入、淡出) */
            [tipAlphaAnimation addKeyframeForTime:index -0.5 alpha:0.f];
            [tipAlphaAnimation addKeyframeForTime:index alpha:1.f];
            [tipAlphaAnimation addKeyframeForTime:index +0.5 alpha:0.f];
            [self.animator addAnimation:tipAlphaAnimation];

            [tipView mas_makeConstraints:^(MASConstraintMaker *make) {
                /* 设置tip视图顶部与icon视图底部的距离(为offset值) */
                make.top.equalTo(iconView.mas_bottom).offset(kScaleFrom_iPhone5_Desgin(45));
            }];
        }
    }
}

#pragma mark Action
- (void)registerBtnClicked{
    RegisterViewController *vc = [RegisterViewController vcWithMethodType:RegisterMethodPhone registerObj:nil];
    UINavigationController *nav = [[BaseNavigationController alloc] initWithRootViewController:vc];
    [self presentViewController:nav animated:YES completion:nil];
}

- (void)loginBtnClicked{
    LoginViewController *vc = [[LoginViewController alloc] init];
    vc.showDismissButton = YES;
    /* 导航控制器初始加载LoginViewController */
    UINavigationController *nav = [[BaseNavigationController alloc] initWithRootViewController:vc];
    [self presentViewController:nav animated:YES completion:nil];
}

引导动画

在引导页面后会根据版本号决定是否显示另一个App介绍页面(使用第三方库EAIntroView库
设置页面指示器(小圆点)

+ (UIPageControl *)p_pageControl{
    // 设置页面指示器对应的图片(小圆点), 选中和未选中
    UIImage *pageIndicatorImage = [UIImage imageNamed:@"intro_dot_unselected"];
    UIImage *currentPageIndicatorImage = [UIImage imageNamed:@"intro_dot_selected"];

    // 如果机型不是iPhone6/6Plus, 则需要对图片进行缩放(图片是按照iPhone6/6Plus的尺寸设计的)
    if (!kDevice_Is_iPhone6 && !kDevice_Is_iPhone6Plus) {
        CGFloat desginWidth = 375.0; //iPhone6 的设计尺寸
        // 缩放比例 = 当前屏幕的宽度/设计尺寸的宽度
        CGFloat scaleFactor = kScreen_Width/desginWidth;
        // 对图片进行缩放, 用到了第三方库NYXImagesKit中的scaleByFactor:方法
        pageIndicatorImage = [pageIndicatorImage scaleByFactor:scaleFactor];
        currentPageIndicatorImage = [currentPageIndicatorImage scaleByFactor:scaleFactor];
    }
    // 第三方工具类 SMPageControl(参考github)
    // 自定义UIPageControl的外观(包括大小、形状等), 也可以用图片代替UIPageControl上的小圆点
    SMPageControl *pageControl = [SMPageControl new];
    pageControl.pageIndicatorImage = pageIndicatorImage;
    pageControl.currentPageIndicatorImage = currentPageIndicatorImage;
    [pageControl sizeToFit];

    return (UIPageControl *)pageControl;
}

设置介绍页面的显示内容(图片尺寸需要根据实际机型进行缩放)

+ (EAIntroPage *)p_pageWithIndex:(NSInteger)index{
    NSString *imageName = [NSString stringWithFormat:@"intro_page%ld", (long)index];
    /* 图片尺寸适配 */
    if (kDevice_Is_iPhone6Plus) {
        imageName = [imageName stringByAppendingString:@"_ip6+"];
    }else if (kDevice_Is_iPhone6){
        imageName = [imageName stringByAppendingString:@"_ip6"];
    }else if (kDevice_Is_iPhone5){
        imageName = [imageName stringByAppendingString:@"_ip5"];
    }else{
        imageName = [imageName stringByAppendingString:@"_ip4"];
    }
    // 添加的两种图片位于 Images/intro_pages/ 路径下,图片内容分别是中秋节和冒泡分享
    UIImage *image = [UIImage imageNamed:imageName];
    UIImageView *imageView;
    if (!image) { //如果图片不存在
        imageView = [UIImageView new];
        imageView.backgroundColor = [UIColor randomColor]; //随机颜色(RGB为随机值, 不透明)
    }else{
        imageView = [[UIImageView alloc] initWithImage:image];
    }
    imageView.contentMode = UIViewContentModeScaleAspectFill;
    imageView.clipsToBounds = YES;
    /* 使用自定义View创建Page 
     创建Page的三种方式:
     (1)basic
     [EAIntroPage page];

     (2)custom view
     [EAIntroPage pageWithCustomView:view];

     (3)custom view from nib
     [EAIntroPage pageWithCustomViewFromNibNamed:@"IntroPage"];
     */
    EAIntroPage *page = [EAIntroPage pageWithCustomView:imageView];
    return page;
}

最后就是显现介绍页面(注意,显示完介绍页之后需要更改版本号,表示新版本的功能已经向用户展示过了)

+ (void)showIntroPage{
    if (![self needToShowIntro]) {
        return;
    }
    // 将两(kIntroPageNum)张Page放入数组
    NSMutableArray *pages = [NSMutableArray new];
    for (int index = 0; index < kIntroPageNum; index ++) {
        EAIntroPage *page = [self p_pageWithIndex:index];
        [pages addObject:page];
    }
    if (pages.count <= 0) {
        return;
    }

    /* 第三方库EAIntroView 创建介绍视图(介绍视图会按顺序展示page) */
    EAIntroView *introView = [[EAIntroView alloc] initWithFrame:kScreen_Bounds andPages:pages];
    introView.backgroundColor = [UIColor whiteColor];
    introView.swipeToExit = YES;
    introView.scrollView.bounces = YES;

//    introView.skipButton = [self p_skipButton];
//    introView.skipButtonY = 20.f + CGRectGetHeight(introView.skipButton.frame);
//    introView.skipButtonAlignment = EAViewAlignmentCenter;

    if (pages.count <= 1) {
        introView.pageControl.hidden = YES;
    }else{
        introView.pageControl = [self p_pageControl];
        introView.pageControlY = 10.f + CGRectGetHeight(introView.pageControl.frame);
    }
    // Show Introduction View(动画显示)
    [introView showFullscreen];
    // 修改引导页相关的版本号(表示已经显示过了)
    [self markHasBeenShowed];
}

+ (BOOL)needToShowIntro{
    return YES;
/*
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSString *preVersion = [defaults stringForKey:kIntroPageKey];
    // 版本不相同且当前时间是中秋节时,显示介绍页, 否则不显示.
    BOOL needToShow = ![preVersion isEqualToString:kVersion_Coding];
    if (![NSDate isDuringMidAutumn]) {
        needToShow = NO;
    }
    return needToShow;
 */
}

启动过程:

启动过程的UML时序图:

Created with Raphaël 2.1.0 AppDelegate AppDelegate AFNetworking AFNetworking SDWebImageManager SDWebImageManager Login Login FunctionIntroManager FunctionIntroManager EaseStartView EaseStartView startMonitoring 网络活动指示器 setValue:forHTTPHeaderField: 监听网络连接状态的变化 isLogin YES - (void)setupTabViewController NO - (void)setupIntroductionViewController makeKeyAndVisible 显示主窗口 + (void)showIntroPage + (instancetype)startView - (void)startAnimationWithCompletionBlock:(void(^)(EaseStartView *easeStartView))completionHandler

应用启动后显示视图的流程如下:

Created with Raphaël 2.1.0 开始 获取当前用户的登录状态 是否为真? 加载RootTabViewController(项目/任务/微博/消息等) 结束 加载IntroductionViewController(引导页/登录/注册) yes no
  • 关于markdown 流程图 的用法可以参考 这儿.

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值