六天完成一個簡單iOS App - 第一天

項目介紹

仿照百思不得姐,通過看李明杰老師視頻學習自己實踐并簡單總結項目開發(fā)過程中普遍遇到的問題,并且將可以用到其他項目中的分類方法進行簡單總結,便于以后在別的項目中使用。
每天任務 1. 實現相應功能 2. 代碼重構,簡單優(yōu)化

第一天任務:

  1. 配置項目基本環(huán)境
  2. 搭建框架
  3. 代碼重構

配置項目基本環(huán)境

一. 接口獲取

我們可以通過Charles等工具抓包來獲取我們想做的App的接口,然后通過解析將每個接口的數據解析出來。也可以去知乎中有趣的 API 接口推薦找找看。

二. 項目圖片獲取方式

圖片的獲取非常簡單,我們只要將iTunes中的項目拖到桌面,然后改后綴名為zip,然后在解壓就可以了,更簡單暴力的可以使用iOS-Images-Extractor運行后直接將項目拖進去,就會自動解壓圖片。

三. 配置基本環(huán)境

創(chuàng)建好項目之后,之后要做的就是配置項目基本信息,首先在info.plist中設置一些基本信息,這里挑選幾個比較重要的


info信息

其中Bundle name是應用的名稱,默認與項目名稱相同,可以更改。
項目使用代碼,storyboard,和xib結合完成,但是框架的搭建不建議使用storyboard,因為框架的搭建往往頁面比較多,多個頁面擠在storyboard中實在難受,并且難找。所以框架的搭建就使用代碼了。
啟動圖片的設置在LaunchScreen.storyboard中,當然也可以在Assets.xcassets中直接拖入啟動圖片,但是需要在General中設置


General
Migrate

然后我們就會發(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中選中圖片設置即可


Paste_Image.png

文字被渲染成藍色,我們可以通過富文本來解決。

   /* 文字屬性 */
   //普通狀態(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屬性。例如

UI_APPEARANCE_SELECTOR

2. UITabBar 中間添加按鈕的實現

我們知道中間加號按鈕是沒有標題的,即使我們將標題設置為空,還有有標題的label站位,所以UITabBarItem是不能實現了,那么我們只能將一個button覆蓋在中間這塊區(qū)域上。
方法一:添加站位控制器,我們可以在中間的位置上添加一個空的站位控制器,然后將button覆蓋到UITabBar中間,這樣做簡單方便,但是創(chuàng)建了一個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++;
    }
    /**** 設置中間的發(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也是定義在分類里邊的

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,一只長大很久但還沒有二夠的家伙。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現,斷路器,智...
    卡卡羅2017閱讀 134,785評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,662評論 25 708
  • 昨天看到一則新聞,日經亞洲評論說理光可能會放棄品牌,重新評估自己旗下的相機部門,不排除取消相機部門的考慮。雖然之后...
    LikeAKid閱讀 322評論 0 0
  • 生活中太多無奈太多感慨,人都是脆弱的,卸下灰甲,內心的獨白只有自己懂。外表看起來很堅強,遇到困難總會迎難而上,只...
    九日木每閱讀 129評論 0 0
  • 近期我剛剛讀完《灰犀牛》這本書,本書詳細講述了多種類型的,“灰犀牛事件”的具體成因,以及當遭遇了此類事件的...
    阿升68閱讀 375評論 0 5