開源項目之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.0AppDelegateAppDelegateAFNetworkingAFNetworkingSDWebImageManagerSDWebImageManagerLoginLoginFunctionIntroManagerFunctionIntroManagerEaseStartViewEaseStartViewstartMonitoring網絡活動指示器setValue:forHTTPHeaderField:監聽網絡連接狀態的變化isLoginYES- (void)setupTabViewControllerNO- (void)setupIntroductionViewControllermakeKeyAndVisible顯示主窗口+ (void)showIntroPage+ (instancetype)startView- (void)startAnimationWithCompletionBlock:(void(^)(EaseStartView *easeStartView))completionHandler

應用啓動後顯示視圖的流程如下:

Created with Raphaël 2.1.0開始獲取當前用戶的登錄狀態是否爲真?加載RootTabViewController(項目/任務/微博/消息等)結束加載IntroductionViewController(引導頁/登錄/註冊)yesno
  • 關於markdown 流程圖 的用法可以參考 這兒.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章