开源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 流程图 的用法可以参考 这儿.