項目介紹
仿照百思不得姐,通過看李明杰老師視頻學習自己實踐并簡單總結項目開發(fā)過程中普遍遇到的問題,并且將可以用到其他項目中的分類方法進行簡單總結,便于以后在別的項目中使用。
每天任務 1. 實現相應功能 2. 代碼重構,簡單優(yōu)化
第一天任務:
- 配置項目基本環(huán)境
- 搭建框架
- 代碼重構
配置項目基本環(huán)境
一. 接口獲取
我們可以通過Charles等工具抓包來獲取我們想做的App的接口,然后通過解析將每個接口的數據解析出來。也可以去知乎中有趣的 API 接口推薦找找看。
二. 項目圖片獲取方式
圖片的獲取非常簡單,我們只要將iTunes中的項目拖到桌面,然后改后綴名為zip,然后在解壓就可以了,更簡單暴力的可以使用iOS-Images-Extractor運行后直接將項目拖進去,就會自動解壓圖片。
三. 配置基本環(huán)境
創(chuàng)建好項目之后,之后要做的就是配置項目基本信息,首先在info.plist中設置一些基本信息,這里挑選幾個比較重要的
其中Bundle name是應用的名稱,默認與項目名稱相同,可以更改。
項目使用代碼,storyboard,和xib結合完成,但是框架的搭建不建議使用storyboard,因為框架的搭建往往頁面比較多,多個頁面擠在storyboard中實在難受,并且難找。所以框架的搭建就使用代碼了。
啟動圖片的設置在LaunchScreen.storyboard中,當然也可以在Assets.xcassets中直接拖入啟動圖片,但是需要在General中設置
然后我們就會發(fā)現在Assets.xcassets中除了AppIcon文件夾還多了Brand Assets文件夾,將啟動圖片直接拖到Brand Assets中就可以了。AppIcon中放應用圖標。
關于圖片素材,個人習慣在項目開始前就將圖片全部放到Assets.xcassets中,這樣使用的時候方便去找。也可以再用到的時候在將使用到的圖片素材拖入到Assets.xcassets中,防止一下拖入過多圖片素材,不好找。
應用名稱,應用圖片,應用啟動圖片設置好之后,需要根據項目分出模塊,觀察項目發(fā)現由5個模塊組成,精華,新帖,發(fā)布,關注,和我,那么我們將每個模塊的代碼放在一起,并在根據MVC原則將每個模塊的代碼細分為3部分。如圖
注意要在文件show in finder 中創(chuàng)建文件,在項目中直接新建的文件夾并不是真實存在的,模塊的區(qū)分有利于我們對項目模塊的理解,更加快捷方便的找到要找的模塊,開發(fā)也更簡單明了
搭建框架
一. 框架結構
框架的搭建使用經典的UITabBarController -> UINavigationController -> UIViewController結構。如圖
UITabBarController 中添加五個UINavigationController,UINavigationController的子控制器來顯示內容,管理自己的NavigationBar。
二. 解決實際問題
1. UITabBarItem自動將圖片文字渲染成藍色
解決方法:解決圖片渲染成藍色
方法一:
// 產生一張不會進行自動渲染的圖片
UIImage *selectedImage = [tempImage imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
方法二:
直接在Assets.xcassets中選中圖片設置即可
文字被渲染成藍色,我們可以通過富文本來解決。
/* 文字屬性 */
//普通狀態(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];
// 設置tabBarItem字體
[vc0.tabBarItem setTitleTextAttributes:normalAttrs forState:UIControlStateNormal];
[vc0.tabBarItem setTitleTextAttributes:selectedAttrs forState:UIControlStateSelected];
多個tabBarItem每個都需要設置一遍同樣的內容,tabBarItem提供了統(tǒng)一設置的方法,我們可以用appearance屬性來對所有的tabBarItem進行統(tǒng)一設置
/**** 設置所有UITabBarItem的文字屬性 ****/
// 這里對item進行設置,即相當于對所有item進行統(tǒng)一設置
UITabBarItem *item = [UITabBarItem appearance];
appearance的使用注意:方法或者屬性后面必須有UI_APPEARANCE_SELECTOR
才可以獲得appearance屬性進行統(tǒng)一設置,否則則不可以使用appearance屬性。例如
2. UITabBar 中間添加按鈕的實現
我們知道中間加號按鈕是沒有標題的,即使我們將標題設置為空,還有有標題的label站位,所以UITabBarItem是不能實現了,那么我們只能將一個button覆蓋在中間這塊區(qū)域上。
方法一:添加站位控制器,我們可以在中間的位置上添加一個空的站位控制器,然后將button覆蓋到UITabBar中間,這樣做簡單方便,但是創(chuàng)建了一個Controller和一個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++;
}
/**** 設置中間的發(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. 實現UINavigationController 返回按鈕統(tǒng)一設置
方法一:創(chuàng)建基類,其他繼承基類,自動有這個按鈕類型
創(chuàng)建一個UINavigationController基類,設置好統(tǒng)一的返回按鈕,然后讓其他導航欄控制器繼承于他,這樣可以達到返回按鈕統(tǒng)一,但是這樣做有一個局限性,UINavigationController的子控制器是固定的,例如UIViewController,如果我們需要使用UITableViewControlller則需要自己創(chuàng)建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方法后,發(fā)現pop右劃返回的手勢失效,我們猜想是系統(tǒng)的返回按鈕做了一些事情,而我們自己的button沒有實現,解決辦法,遵循代理,并實現代理方法
self.interactivePopGestureRecognizer.delegate = self;
// 實現代理方法
-(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
// 判斷如果不是根控制器 才需要pop返回手勢
return self.childViewControllers.count > 1;
}
三. 代碼重構與優(yōu)化
1. UINavigationControlller 設置左右UIbarbuttonitem代碼的抽取
我們發(fā)現每一個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也是定義在分類里邊的
可以看到,這種定義在分類里的屬性,實際上是實現了相應的方法,并在方法里邊通過訪問其它屬性來達到目的。這通常用來簡化某些操作。
2. 在分類中可以寫
@property
添加屬性,但是不會自動生成私有屬性,也不會生成set,get方法的實現,只會生成set,get的聲明,需要我們自己去實現。3. 為什么不直接設置frame而需要一個中間量來設置呢?
因為在分類的方法實現中不可以直接訪問本類的私有屬性,但是可以調用本類的set,get方法。
4. 當分類中有和本類中同名的方法的時候,優(yōu)先調用分類的方法,如果多個分類中有相同的方法,優(yōu)先調用最后編譯的分類。
5. 分類可以通過Runtime運行時給分類添加屬性,對象的屬性其實是讓屬性與對象產生關聯(lián),如果想動態(tài)添加屬性,其實是動態(tài)產生一種關系,讓對象的某個屬性可以關聯(lián)到另外一塊內存地址。
五. 總結
今天的任務已經完成,我們完成了環(huán)境的配置,主框架的搭建,以及對一些繁瑣重復的代碼做了簡單整理。第一天效果如下
文中如果有不對的地方歡迎指出。我是xx_cc,一只長大很久但還沒有二夠的家伙。