六天完成一个简单iOS App - 第一天

项目介绍

仿照百思不得姐,通过看李明杰老师视频学习自己实践并简单总结项目开发过程中普遍遇到的问题,并且将可以用到其他项目中的分类方法进行简单总结,便于以后在别的项目中使用。
每天任务 1. 实现相应功能 2. 代码重构,简单优化

第一天任务:

  1. 配置项目基本环境
  2. 搭建框架
  3. 代码重构

配置项目基本环境

一. 接口获取

我们可以通过Charles等工具抓包来获取我们想做的App的接口,然后通过解析将每个接口的数据解析出来。也可以去知乎中有趣的 API 接口推荐找找看。

二. 项目图片获取方式

图片的获取非常简单,我们只要将iTunes中的项目拖到桌面,然后改后缀名为zip,然后在解压就可以了,更简单暴力的可以使用iOS-Images-Extractor运行后直接将项目拖进去,就会自动解压图片。

三. 配置基本环境

创建好项目之后,之后要做的就是配置项目基本信息,首先在info.plist中设置一些基本信息,这里挑选几个比较重要的


info信息

其中Bundle name是应用的名称,默认与项目名称相同,可以更改。
项目使用代码,storyboard,和xib结合完成,但是框架的搭建不建议使用storyboard,因为框架的搭建往往页面比较多,多个页面挤在storyboard中实在难受,并且难找。所以框架的搭建就使用代码了。
启动图片的设置在LaunchScreen.storyboard中,当然也可以在Assets.xcassets中直接拖入启动图片,但是需要在General中设置


General
Migrate

然后我们就会发现在Assets.xcassets中除了AppIcon文件夹还多了Brand Assets文件夹,将启动图片直接拖到Brand Assets中就可以了。AppIcon中放应用图标。
关于图片素材,个人习惯在项目开始前就将图片全部放到Assets.xcassets中,这样使用的时候方便去找。也可以再用到的时候在将使用到的图片素材拖入到Assets.xcassets中,防止一下拖入过多图片素材,不好找。

应用名称,应用图片,应用启动图片设置好之后,需要根据项目分出模块,观察项目发现由5个模块组成,精华,新帖,发布,关注,和我,那么我们将每个模块的代码放在一起,并在根据MVC原则将每个模块的代码细分为3部分。如图


模块划分

注意要在文件show in finder 中创建文件,在项目中直接新建的文件夹并不是真实存在的,模块的区分有利于我们对项目模块的理解,更加快捷方便的找到要找的模块,开发也更简单明了

搭建框架

一. 框架结构

框架的搭建使用经典的UITabBarController -> UINavigationController -> UIViewController结构。如图


框架基本结构

UITabBarController 中添加五个UINavigationController,UINavigationController的子控制器来显示内容,管理自己的NavigationBar。

二. 解决实际问题

1. UITabBarItem自动将图片文字渲染成蓝色
图片文字被自动渲染成蓝色

解决方法:解决图片渲染成蓝色
方法一:

// 产生一张不会进行自动渲染的图片
UIImage *selectedImage = [tempImage imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];

方法二:
直接在Assets.xcassets中选中图片设置即可


Paste_Image.png

文字被渲染成蓝色,我们可以通过富文本来解决。

   /* 文字属性 */
   //普通状态下的文字属性
    NSMutableDictionary *normalAttrs = [NSMutableDictionary dictionary];
    normalAttrs[NSFontAttributeName] = [UIFont systemFontOfSize:14];
    normalAttrs[NSForegroundColorAttributeName] = [UIColor grayColor];
    // 选中状态下的文字属性
    NSMutableDictionary *selectedAttrs = [NSMutableDictionary dictionary];
    selectedAttrs[NSForegroundColorAttributeName] = [UIColor darkGrayColor];

    // 设置tabBarItem字体
    [vc0.tabBarItem setTitleTextAttributes:normalAttrs forState:UIControlStateNormal];
    [vc0.tabBarItem setTitleTextAttributes:selectedAttrs forState:UIControlStateSelected];

多个tabBarItem每个都需要设置一遍同样的内容,tabBarItem提供了统一设置的方法,我们可以用appearance属性来对所有的tabBarItem进行统一设置

/**** 设置所有UITabBarItem的文字属性 ****/
// 这里对item进行设置,即相当于对所有item进行统一设置
UITabBarItem *item = [UITabBarItem appearance];

appearance的使用注意:方法或者属性后面必须有UI_APPEARANCE_SELECTOR才可以获得appearance属性进行统一设置,否则则不可以使用appearance属性。例如

UI_APPEARANCE_SELECTOR

2. UITabBar 中间添加按钮的实现

我们知道中间加号按钮是没有标题的,即使我们将标题设置为空,还有有标题的label站位,所以UITabBarItem是不能实现了,那么我们只能将一个button覆盖在中间这块区域上。
方法一:添加站位控制器,我们可以在中间的位置上添加一个空的站位控制器,然后将button覆盖到UITabBar中间,这样做简单方便,但是创建了一个Controller和一个UITabBarItem没有别的用处只是用来站位,虽然并不会消耗很多空间,但是总是觉得十分别扭。


中间button覆盖在原有UITabBarItem上

方法二:自定义tabbar重写layoutsubViews方法
为了避免第一种方法产生站位Controller和UITabBarItem,我们自定义一个UItabbar,重写layoutsubViews尝试我们自己控制TabBarItem的位置,实现方法很简单,将UITabBar平均分为5段,将中间空出,其他四个TabBarItem设置完frame之后,懒加载button添加到中间位置。并实现其点击方法
layoutSubviews方法。

- (void)layoutSubviews
{
    [super layoutSubviews];
    /**** 设置所有UITabBarButton的frame ****/
    // 按钮的尺寸
    CGFloat buttonW = self.frame.size.width / 5;
    CGFloat buttonH = self.frame.size.height;
    CGFloat buttonY = 0;
    // 按钮索引
    int buttonIndex = 0;
    for (UIView *subview in self.subviews) {
        // 过滤掉非UITabBarButton
        // if (![@"UITabBarButton" isEqualToString:NSStringFromClass(subview.class)]) continue;
        if (subview.class != NSClassFromString(@"UITabBarButton")) continue;
        // 设置frame
        CGFloat buttonX = buttonIndex * buttonW;
        if (buttonIndex >= 2) { // 右边的2个UITabBarButton
            buttonX += buttonW;
        }
        subview.frame = CGRectMake(buttonX, buttonY, buttonW, buttonH);
        // 增加索引
        buttonIndex++;
    }
    /**** 设置中间的发布按钮的frame ****/
    self.publishButton.frame = CGRectMake(0, 0, buttonW, buttonH);
    self.publishButton.center = CGPointMake(self.frame.size.width * 0.5, self.frame.size.height * 0.5);
}
3. 实现UINavigationController 返回按钮统一设置

方法一:创建基类,其他继承基类,自动有这个按钮类型
创建一个UINavigationController基类,设置好统一的返回按钮,然后让其他导航栏控制器继承于他,这样可以达到返回按钮统一,但是这样做有一个局限性,UINavigationController的子控制器是固定的,例如UIViewController,如果我们需要使用UITableViewControlller则需要自己创建tableView。比较麻烦

方法二:自定义UINavigationController 重写pushViewController方法
重写pushViewController方法,判断NavigationController子控制器的个数,如果不是第一个push进来的控制器,则添加左边返回按钮。
注意:NavigationController的根控制器也是push进来的,所以需要判断是否是根控制器

-(void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
    if (self.childViewControllers.count > 0) { // 不是第一个push进来的 左上角加上返回键
      /*
      // 返回button初始化以及设置  
      */
     // 将button放置在leftBarButtonItem
    viewController.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc]initWithCustomView:button];
    }
    [super pushViewController:viewController animated:animated];
}
4. pop右划手势失效的问题

当我们重写posh方法后,发现pop右划返回的手势失效,我们猜想是系统的返回按钮做了一些事情,而我们自己的button没有实现,解决办法,遵循代理,并实现代理方法

self.interactivePopGestureRecognizer.delegate = self; 
// 实现代理方法 
-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
   // 判断如果不是根控制器 才需要pop返回手势
    return self.childViewControllers.count > 1;
}

三. 代码重构与优化

1. UINavigationControlller 设置左右UIbarbuttonitem代码的抽取

我们发现每一个UINavigationControlller根控制器中都需要写一大段相同的代码来设置UIbarbuttonite,那么我们写一个UIbarbuttonitem的分类抽取一个方法来简化代码。

@implementation UIBarButtonItem (CLExtension)
+(instancetype)itemWithImage:(NSString *)image HeightImage:(NSString *)heightImage Target:(id)target action:(SEL)action
{
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    [button setImage:[UIImage imageNamed:image] forState:UIControlStateNormal];
    [button setImage:[UIImage imageNamed:heightImage] forState:UIControlStateHighlighted];
    [button sizeToFit];
    [button addTarget:target action:action forControlEvents:UIControlEventTouchUpInside];
    return [[UIBarButtonItem alloc]initWithCustomView:button];
}

这样我们在根控制器中设置UIbarbuttonitem一句话就搞定了

// 设置左边按钮button
self.navigationItem.leftBarButtonItem = [UIBarButtonItem itemWithImage:@"MainTagSubIcon" HeightImage:@"MainTagSubIconClick" Target:self action:@selector(leftBtnClick)];
2. uiview关于frame的分类

当我们在设置控件的宽高以及位置的时候需要设置self.frame.size.height;代码很长,那么我们可以写一个UIView的分类,直接就可以通过self.height来设置其高度。
UIView+CLExtension.h

@interface UIView (CLExtension)

@property(nonatomic,assign)CGFloat cl_width;
@property(nonatomic,assign)CGFloat cl_height;
@property(nonatomic,assign)CGFloat cl_x;
@property(nonatomic,assign)CGFloat cl_y;
@property(nonatomic,assign)CGFloat cl_centerX;
@property(nonatomic,assign)CGFloat cl_centerY;

@end

UIView+CLExtension.m

@implementation UIView (CLExtension)  
-(void)setCl_width:(CGFloat)cl_width
{
    CGRect frame = self.frame;
    frame.size.width = cl_width;
    self.frame = frame;
}
-(CGFloat)cl_width
{
    return self.frame.size.width;
}
-(void)setCl_height:(CGFloat)cl_height
{
    CGRect frame = self.frame;
    frame.size.height = cl_height;
    self.frame = frame;
}
-(CGFloat)cl_height
{
    return self.frame.size.height;
}
-(void)setCl_x:(CGFloat)cl_x
{
    CGRect frame = self.frame;
    frame.origin.x = cl_x;
    self.frame = frame;
}
-(CGFloat)cl_x
{
    return self.frame.origin.x;
}
-(void)setCl_y:(CGFloat)cl_y
{
    CGRect frame = self.frame;
    frame.origin.y = cl_y;
    self.frame = frame;
}
-(CGFloat)cl_y
{
    return self.frame.origin.y;
}
-(void)setCl_centerX:(CGFloat)cl_centerX
{
    CGPoint center = self.center;
    center.x = cl_centerX;
    self.center = center;
}
-(CGFloat)cl_centerX
{
    return self.center.x;
}
-(void)setCl_centerY:(CGFloat)cl_centerY
{
    CGPoint center = self.center;
    center.y = cl_centerY;
    self.center = center;
}
-(CGFloat)cl_centerY
{
    return self.center.y;
}
@end

这样我们在设置宽高,x,y的时候就可以直接通过height,width,x,y来设置了,建议在这些属性前面加上前缀,防止和其他文件属性冲突

3. PCH文件

所有文件都用的到的东西,例如颜色设置的宏,分类,修改的输出日志等等,我们可以写到PCH文件中,保证所有的文件都可以用,而不用频繁的每个类中都引入

#ifdef __OBJC__
/** 在这之间的 在OC文件中会引用 防止OC与C混编的时候引起错误 **/
#import "UIView+CLExtension.h"
#import "UIBarButtonItem+CLExtension.h"
#define CLLogfunc CLLog(@"%s",__func__);
/******** 输出日志 ********/
#ifdef DEBUG
#define CLLog(...) NSLog(__VA_ARGS__)
#else
#define CLLog(...)
#endif
/******** 日志输出 ********/
/******** 关于颜色的宏********/
// 带透明度的颜色
#define CLColorA(r,g,b,a) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:(a)]
// 不带透明度的颜色
#define CLColor(r,g,b) CLColorA(r,g,b,1);
// 随机颜色
#define CLRandomColor CLColor(arc4random_uniform(255),arc4random_uniform(255),arc4random_uniform(255))
// 灰色
#define CLCommonColor(v) CLColor(v,v,v)

/******** 关于颜色的宏********/
#endif

这是目前的pch文件内容,如果项目报错找不到pch文件,那是因为pch文件路径可能换了,在BuildSettings 搜索 prefix header ,直接将pch拖入其中自动生成路径即可。

四.疑惑

分类中能不能添加属性呢?之前uiview关于frame的分类不就是给分类添加了许多属性吗?
注意:
1. 分类原则是不可以添加属性,只能添加方法,我们之前给 UIView增加了一些属性,而且为其实现了相应的 getter和 setter方法。而这些方法实际上访问的是本类的frame属性,其实frame,bounds也是定义在分类里边的

frame,bounds也是定义在分类里

可以看到,这种定义在分类里的属性,实际上是实现了相应的方法,并在方法里边通过访问其它属性来达到目的。这通常用来简化某些操作。

2. 在分类中可以写@property添加属性,但是不会自动生成私有属性,也不会生成set,get方法的实现,只会生成set,get的声明,需要我们自己去实现。
3. 为什么不直接设置frame而需要一个中间量来设置呢?
因为在分类的方法实现中不可以直接访问本类的私有属性,但是可以调用本类的set,get方法。

4. 当分类中有和本类中同名的方法的时候,优先调用分类的方法,如果多个分类中有相同的方法,优先调用最后编译的分类。
5. 分类可以通过Runtime运行时给分类添加属性,对象的属性其实是让属性与对象产生关联,如果想动态添加属性,其实是动态产生一种关系,让对象的某个属性可以关联到另外一块内存地址。

五. 总结

今天的任务已经完成,我们完成了环境的配置,主框架的搭建,以及对一些繁琐重复的代码做了简单整理。第一天效果如下


第一天效果

文中如果有不对的地方欢迎指出。我是xx_cc,一只长大很久但还没有二够的家伙。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,504评论 4 365
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,898评论 1 300
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,218评论 0 248
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,322评论 0 214
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,693评论 3 290
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,812评论 1 223
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,010评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,747评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,476评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,700评论 2 251
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,190评论 1 262
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,541评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,206评论 3 240
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,129评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,903评论 0 199
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,894评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,748评论 2 274

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,105评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,584评论 25 707
  • 昨天看到一则新闻,日经亚洲评论说理光可能会放弃品牌,重新评估自己旗下的相机部门,不排除取消相机部门的考虑。虽然之后...
    LikeAKid阅读 277评论 0 0
  • 生活中太多无奈太多感慨,人都是脆弱的,卸下灰甲,内心的独白只有自己懂。外表看起来很坚强,遇到困难总会迎难而上,只...
    九日木每阅读 112评论 0 0
  • 近期我刚刚读完《灰犀牛》这本书,本书详细讲述了多种类型的,“灰犀牛事件”的具体成因,以及当遭遇了此类事件的...
    阿升68阅读 341评论 0 5