開源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時序圖:
應用啓動後顯示視圖的流程如下:
- 關於markdown 流程圖 的用法可以參考 這兒.