項(xiàng)目介紹
仿照百思不得姐巧骚,通過看李明杰老師視頻學(xué)習(xí)自己實(shí)踐并簡單總結(jié)項(xiàng)目開發(fā)過程中普遍遇到的問題蜀细,并且將可以用到其他項(xiàng)目中的分類方法進(jìn)行簡單總結(jié)昼扛,便于以后在別的項(xiàng)目中使用月褥。
每天任務(wù) 1. 實(shí)現(xiàn)相應(yīng)功能 2. 代碼重構(gòu)瞭恰,簡單優(yōu)化
第一天任務(wù):
- 配置項(xiàng)目基本環(huán)境
- 搭建框架
- 代碼重構(gòu)
配置項(xiàng)目基本環(huán)境
一. 接口獲取
我們可以通過Charles等工具抓包來獲取我們想做的App的接口屯曹,然后通過解析將每個接口的數(shù)據(jù)解析出來。也可以去知乎中有趣的 API 接口推薦找找看惊畏。
二. 項(xiàng)目圖片獲取方式
圖片的獲取非常簡單恶耽,我們只要將iTunes中的項(xiàng)目拖到桌面,然后改后綴名為zip颜启,然后在解壓就可以了偷俭,更簡單暴力的可以使用iOS-Images-Extractor運(yùn)行后直接將項(xiàng)目拖進(jìn)去,就會自動解壓圖片缰盏。
三. 配置基本環(huán)境
創(chuàng)建好項(xiàng)目之后涌萤,之后要做的就是配置項(xiàng)目基本信息,首先在info.plist中設(shè)置一些基本信息口猜,這里挑選幾個比較重要的
其中Bundle name是應(yīng)用的名稱负溪,默認(rèn)與項(xiàng)目名稱相同,可以更改济炎。
項(xiàng)目使用代碼川抡,storyboard,和xib結(jié)合完成须尚,但是框架的搭建不建議使用storyboard猖腕,因?yàn)榭蚣艿拇罱ㄍ撁姹容^多拆祈,多個頁面擠在storyboard中實(shí)在難受,并且難找倘感。所以框架的搭建就使用代碼了放坏。
啟動圖片的設(shè)置在LaunchScreen.storyboard中,當(dāng)然也可以在Assets.xcassets中直接拖入啟動圖片老玛,但是需要在General中設(shè)置
然后我們就會發(fā)現(xiàn)在Assets.xcassets中除了AppIcon文件夾還多了Brand Assets文件夾淤年,將啟動圖片直接拖到Brand Assets中就可以了。AppIcon中放應(yīng)用圖標(biāo)蜡豹。
關(guān)于圖片素材麸粮,個人習(xí)慣在項(xiàng)目開始前就將圖片全部放到Assets.xcassets中,這樣使用的時候方便去找镜廉。也可以再用到的時候在將使用到的圖片素材拖入到Assets.xcassets中弄诲,防止一下拖入過多圖片素材,不好找娇唯。
應(yīng)用名稱齐遵,應(yīng)用圖片,應(yīng)用啟動圖片設(shè)置好之后塔插,需要根據(jù)項(xiàng)目分出模塊梗摇,觀察項(xiàng)目發(fā)現(xiàn)由5個模塊組成,精華想许,新帖伶授,發(fā)布,關(guān)注流纹,和我糜烹,那么我們將每個模塊的代碼放在一起,并在根據(jù)MVC原則將每個模塊的代碼細(xì)分為3部分漱凝。如圖
注意要在文件show in finder 中創(chuàng)建文件景图,在項(xiàng)目中直接新建的文件夾并不是真實(shí)存在的,模塊的區(qū)分有利于我們對項(xiàng)目模塊的理解碉哑,更加快捷方便的找到要找的模塊挚币,開發(fā)也更簡單明了
搭建框架
一. 框架結(jié)構(gòu)
框架的搭建使用經(jīng)典的UITabBarController -> UINavigationController -> UIViewController結(jié)構(gòu)。如圖
UITabBarController 中添加五個UINavigationController扣典,UINavigationController的子控制器來顯示內(nèi)容妆毕,管理自己的NavigationBar。
二. 解決實(shí)際問題
1. UITabBarItem自動將圖片文字渲染成藍(lán)色
解決方法:解決圖片渲染成藍(lán)色
方法一:
// 產(chǎn)生一張不會進(jìn)行自動渲染的圖片
UIImage *selectedImage = [tempImage imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
方法二:
直接在Assets.xcassets中選中圖片設(shè)置即可
文字被渲染成藍(lán)色贮尖,我們可以通過富文本來解決笛粘。
/* 文字屬性 */
//普通狀態(tài)下的文字屬性
NSMutableDictionary *normalAttrs = [NSMutableDictionary dictionary];
normalAttrs[NSFontAttributeName] = [UIFont systemFontOfSize:14];
normalAttrs[NSForegroundColorAttributeName] = [UIColor grayColor];
// 選中狀態(tài)下的文字屬性
NSMutableDictionary *selectedAttrs = [NSMutableDictionary dictionary];
selectedAttrs[NSForegroundColorAttributeName] = [UIColor darkGrayColor];
// 設(shè)置tabBarItem字體
[vc0.tabBarItem setTitleTextAttributes:normalAttrs forState:UIControlStateNormal];
[vc0.tabBarItem setTitleTextAttributes:selectedAttrs forState:UIControlStateSelected];
多個tabBarItem每個都需要設(shè)置一遍同樣的內(nèi)容,tabBarItem提供了統(tǒng)一設(shè)置的方法,我們可以用appearance屬性來對所有的tabBarItem進(jìn)行統(tǒng)一設(shè)置
/**** 設(shè)置所有UITabBarItem的文字屬性 ****/
// 這里對item進(jìn)行設(shè)置薪前,即相當(dāng)于對所有item進(jìn)行統(tǒng)一設(shè)置
UITabBarItem *item = [UITabBarItem appearance];
appearance的使用注意:方法或者屬性后面必須有UI_APPEARANCE_SELECTOR
才可以獲得appearance屬性進(jìn)行統(tǒng)一設(shè)置润努,否則則不可以使用appearance屬性。例如
2. UITabBar 中間添加按鈕的實(shí)現(xiàn)
我們知道中間加號按鈕是沒有標(biāo)題的示括,即使我們將標(biāo)題設(shè)置為空铺浇,還有有標(biāo)題的label站位瘟檩,所以UITabBarItem是不能實(shí)現(xiàn)了匪煌,那么我們只能將一個button覆蓋在中間這塊區(qū)域上。
方法一:添加站位控制器应民,我們可以在中間的位置上添加一個空的站位控制器吼拥,然后將button覆蓋到UITabBar中間倚聚,這樣做簡單方便,但是創(chuàng)建了一個Controller和一個UITabBarItem沒有別的用處只是用來站位凿可,雖然并不會消耗很多空間惑折,但是總是覺得十分別扭。
方法二:自定義tabbar重寫layoutsubViews方法
為了避免第一種方法產(chǎn)生站位Controller和UITabBarItem枯跑,我們自定義一個UItabbar,重寫layoutsubViews嘗試我們自己控制TabBarItem的位置惨驶,實(shí)現(xiàn)方法很簡單,將UITabBar平均分為5段全肮,將中間空出,其他四個TabBarItem設(shè)置完frame之后棘捣,懶加載button添加到中間位置辜腺。并實(shí)現(xiàn)其點(diǎn)擊方法
layoutSubviews方法。
- (void)layoutSubviews
{
[super layoutSubviews];
/**** 設(shè)置所有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;
// 設(shè)置frame
CGFloat buttonX = buttonIndex * buttonW;
if (buttonIndex >= 2) { // 右邊的2個UITabBarButton
buttonX += buttonW;
}
subview.frame = CGRectMake(buttonX, buttonY, buttonW, buttonH);
// 增加索引
buttonIndex++;
}
/**** 設(shè)置中間的發(fā)布按鈕的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. 實(shí)現(xiàn)UINavigationController 返回按鈕統(tǒng)一設(shè)置
方法一:創(chuàng)建基類乍恐,其他繼承基類评疗,自動有這個按鈕類型
創(chuàng)建一個UINavigationController基類,設(shè)置好統(tǒng)一的返回按鈕茵烈,然后讓其他導(dǎo)航欄控制器繼承于他百匆,這樣可以達(dá)到返回按鈕統(tǒng)一,但是這樣做有一個局限性呜投,UINavigationController的子控制器是固定的加匈,例如UIViewController,如果我們需要使用UITableViewControlller則需要自己創(chuàng)建tableView仑荐。比較麻煩
方法二:自定義UINavigationController 重寫pushViewController方法
重寫pushViewController方法雕拼,判斷NavigationController子控制器的個數(shù),如果不是第一個push進(jìn)來的控制器粘招,則添加左邊返回按鈕啥寇。
注意:NavigationController的根控制器也是push進(jìn)來的,所以需要判斷是否是根控制器
-(void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if (self.childViewControllers.count > 0) { // 不是第一個push進(jìn)來的 左上角加上返回鍵
/*
// 返回button初始化以及設(shè)置
*/
// 將button放置在leftBarButtonItem
viewController.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc]initWithCustomView:button];
}
[super pushViewController:viewController animated:animated];
}
4. pop右劃手勢失效的問題
當(dāng)我們重寫posh方法后,發(fā)現(xiàn)pop右劃返回的手勢失效辑甜,我們猜想是系統(tǒng)的返回按鈕做了一些事情衰絮,而我們自己的button沒有實(shí)現(xiàn),解決辦法磷醋,遵循代理猫牡,并實(shí)現(xiàn)代理方法
self.interactivePopGestureRecognizer.delegate = self;
// 實(shí)現(xiàn)代理方法
-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
// 判斷如果不是根控制器 才需要pop返回手勢
return self.childViewControllers.count > 1;
}
三. 代碼重構(gòu)與優(yōu)化
1. UINavigationControlller 設(shè)置左右UIbarbuttonitem代碼的抽取
我們發(fā)現(xiàn)每一個UINavigationControlller根控制器中都需要寫一大段相同的代碼來設(shè)置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];
}
這樣我們在根控制器中設(shè)置UIbarbuttonitem一句話就搞定了
// 設(shè)置左邊按鈕button
self.navigationItem.leftBarButtonItem = [UIBarButtonItem itemWithImage:@"MainTagSubIcon" HeightImage:@"MainTagSubIconClick" Target:self action:@selector(leftBtnClick)];
2. uiview關(guān)于frame的分類
當(dāng)我們在設(shè)置控件的寬高以及位置的時候需要設(shè)置self.frame.size.height;
代碼很長镊掖,那么我們可以寫一個UIView的分類,直接就可以通過self.height
來設(shè)置其高度褂痰。
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
這樣我們在設(shè)置寬高亩进,x,y的時候就可以直接通過height缩歪,width归薛,x,y來設(shè)置了匪蝙,建議在這些屬性前面加上前綴主籍,防止和其他文件屬性沖突
3. PCH文件
所有文件都用的到的東西,例如顏色設(shè)置的宏逛球,分類千元,修改的輸出日志等等,我們可以寫到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
/******** 日志輸出 ********/
/******** 關(guān)于顏色的宏********/
// 帶透明度的顏色
#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);
// 隨機(jī)顏色
#define CLRandomColor CLColor(arc4random_uniform(255),arc4random_uniform(255),arc4random_uniform(255))
// 灰色
#define CLCommonColor(v) CLColor(v,v,v)
/******** 關(guān)于顏色的宏********/
#endif
這是目前的pch文件內(nèi)容,如果項(xiàng)目報錯找不到pch文件奥务,那是因?yàn)閜ch文件路徑可能換了物独,在BuildSettings 搜索 prefix header ,直接將pch拖入其中自動生成路徑即可氯葬。
四.疑惑
分類中能不能添加屬性呢挡篓?之前uiview關(guān)于frame的分類不就是給分類添加了許多屬性嗎?
注意:
1. 分類原則是不可以添加屬性帚称,只能添加方法官研,我們之前給 UIView增加了一些屬性,而且為其實(shí)現(xiàn)了相應(yīng)的 getter和 setter方法闯睹。而這些方法實(shí)際上訪問的是本類的frame屬性阀参,其實(shí)frame,bounds也是定義在分類里邊的
可以看到蛛壳,這種定義在分類里的屬性杏瞻,實(shí)際上是實(shí)現(xiàn)了相應(yīng)的方法,并在方法里邊通過訪問其它屬性來達(dá)到目的衙荐。這通常用來簡化某些操作捞挥。
2. 在分類中可以寫
@property
添加屬性,但是不會自動生成私有屬性忧吟,也不會生成set,get方法的實(shí)現(xiàn)砌函,只會生成set,get的聲明,需要我們自己去實(shí)現(xiàn)溜族。3. 為什么不直接設(shè)置frame而需要一個中間量來設(shè)置呢讹俊?
因?yàn)樵诜诸惖姆椒▽?shí)現(xiàn)中不可以直接訪問本類的私有屬性,但是可以調(diào)用本類的set,get方法煌抒。
4. 當(dāng)分類中有和本類中同名的方法的時候仍劈,優(yōu)先調(diào)用分類的方法,如果多個分類中有相同的方法寡壮,優(yōu)先調(diào)用最后編譯的分類贩疙。
5. 分類可以通過Runtime運(yùn)行時給分類添加屬性,對象的屬性其實(shí)是讓屬性與對象產(chǎn)生關(guān)聯(lián)况既,如果想動態(tài)添加屬性这溅,其實(shí)是動態(tài)產(chǎn)生一種關(guān)系,讓對象的某個屬性可以關(guān)聯(lián)到另外一塊內(nèi)存地址棒仍。
五. 總結(jié)
今天的任務(wù)已經(jīng)完成悲靴,我們完成了環(huán)境的配置,主框架的搭建莫其,以及對一些繁瑣重復(fù)的代碼做了簡單整理癞尚。第一天效果如下
文中如果有不對的地方歡迎指出。我是xx_cc榜配,一只長大很久但還沒有二夠的家伙否纬。