2015/10/24
Day 23
我插入移動硬盤后,電腦右上角老是出現一個齒輪轉啊轉,然后彈出對話框說有新文件加入電腦什么文件夾,要不要去看,關還關不掉,於是乎,昨晚腦袋一抽就把那個彈出來的文件夾移入廢紙簍,然后發現不能這么干,就從廢紙簍恢復,然而Finder就卡死了= =不管怎樣都沒響應,我煩的不行就強制按電源鍵重啟電腦了(用Win系統的壞習慣,有事沒事重啟一下),然后!!!輸入密碼登陸,如下圖:
然后就重啟了= =,無限循環進不了系統,各種方法無果后,沒辦法去了售后店。解決方法,重裝系統,花費300大洋,我去!真特么貴,唉~誰叫自己作呢,弄好后回家第一件事就是自制U盤啟動盤。
其實很簡單,先下好鏡像文件,然后插入U盤,格式化它,再可以給它分區,這個隨意我就沒分,最后終端輸入命令,等着就行了。
命令如下
安裝包名稱:Install OS X El Capitan.app
制作U盤啟動盤指令
sudo /Applications/Install\ OS\ X\ El\ Capitan.app/Contents/Resources/createinstallmedia --volume /Volumes/u盤 --applicationpath /Applications/Install\ OS\ X\ El\ Capitan.app --nointeraction
第二件事就是開啟TimeMachine備份。
之前寫的代碼都沒了= =,唉,不說這事了,都是淚%>_<%
2015/10/25
Day 24
調試新系統,調整心情,再一次安裝Xcode,繼續學習啦~
今天開始學習UITableView
UITableView
在眾多app中,能看到各式各樣的表格數據,如下
在iOS中,要實現表格數據展示,最常用的做法就是使用UITableView
UITableView繼承自UIScrollView,因此支持垂直滾動,而且性能極佳
UITableView的兩種樣式
- UITableView需要一個數據源(dataSource)來顯示數據
- UITableView會向數據源查詢一共有多少行數據以及每一行顯示什么數據等
- 沒有設置數據源的UITableView只是個空殼
- 凡是遵守UITableViewDataSource協議的OC對象,都可以是UITableView的數據源
UITableViewDataSource協議
Cell簡介
UITableView的每一行都是一個UITableViewCell,通過dataSource的tableView:cellForRowAtIndexPath:方法來初始化每一行
UITableViewCell內部有個默認的子視圖:contentView,contentView是UITableViewCell所顯示內容的父視圖,可顯示一些輔助指示視圖。還可以通過cell的accessoryView屬性來自定義輔助指示視圖(比如往右邊放一個開關)
UITableViewCell的contentView
contentView下默認有3個子視圖
其中2個是UILabel(通過UITableViewCell的textLabel和detailTextLabel屬性訪問)
第3個是UIImageView(通過UITableViewCell的imageView屬性訪問)
UITableViewCell還有一個UITableViewCellStyle屬性,用於決定使用contentView的哪些子視圖,以及這些子視圖在contentView中的位置
Cell的重用
- iOS設備的內存有限,如果用UITableView顯示成千上萬條數據,就需要成千上萬個UITableViewCell對象的話,那將會耗盡iOS設備的內存。要解決該問題,需要重用UITableViewCell對象
- 重用原理:當滾動列表時,部分UITableViewCell會移出窗口,UITableView會將窗口外的UITableViewCell放入一個對象池中,等待重用。當UITableView要求dataSource返回UITableViewCell時,dataSource會先查看這個對象池,如果池中有未使用的UITableViewCell,dataSource會用新的數據配置這個UITableViewCell,然后返回給UITableView,重新顯示到窗口中,從而避免創建新對象
- 還有一個非常重要的問題:有時候需要自定義UITableViewCell(用一個子類繼承UITableViewCell),而且每一行用的不一定是同一種UITableViewCell,所以一個UITableView可能擁有不同類型的UITableViewCell,對象池中也會有很多不同類型的UITableViewCell,那么UITableView在重用UITableViewCell時可能會得到錯誤類型的UITableViewCell
- 解決方案:UITableViewCell有個NSString *reuseIdentifier屬性,可以在初始化UITableViewCell的時候傳入一個特定的字符串標識來設置reuseIdentifier(一般用UITableViewCell的類名)。當UITableView要求dataSource返回UITableViewCell時,先通過一個字符串標識到對象池中查找對應類型的UITableViewCell對象,如果有,就重用,如果沒有,就傳入這個字符串標識來初始化一個UITableViewCell對象
Cell的重用代碼
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 1.定義一個cell的標識
static NSString *yu3 = @“yu3”;
// 2.從緩存池中取出cell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:yu3];
// 3.如果緩存池中沒有cell
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:yu3];
}
// 4.設置cell的屬性...
return cell;
}
上面的寫法並不好,因為數據源不需要知道cell內部的標識為什么,cell的標識cell自己最清楚,應該把創建cell的代碼封裝在cell里面,如下(YUCell為我自定義的cell類名)
+ (instancetype)cellWithTableView:(UITableView *)tableView{
static NSString *yu3 = @“yu3Cell";
YUCell *cell = [tableView dequeueReusableCellWithIdentifier:yu3];
if (cell == nil) {
cell = [[YUCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:yu3];
}
return cell;
}
這樣,數據源里的方法就很簡單
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//創建cell
YUCell *cell = [YUCell cellWithTableView:tableView];
//設置cell的屬性...
return cell;
}
這樣的話,如果以后cell里面的東西要變化,只需要動cell里的代碼,數據源里代碼不需要改變。
2015/10/26
Day 25
今天做了一個UIScrollView和UITableView綜合的小項目
仿造團購app的界面,上面的廣告可以自動翻頁,滑到下面有按鈕點擊后加載數據
上面的廣告,每個cell還有下面的加載更多按鈕都是用xib封裝的
使用xib封裝一個view的步驟:
1.新建一個xib文件描述一個view的內部結構
2.新建一個自定義的類(自定義類需要繼承自系統自帶的view, 繼承自哪個類, 取決於xib根對象的Class)
3.新建類的類名最好跟xib的文件名保持一致
4.將xib中的控件 和 自定義類的.m文件 進行連線
5.提供一個類方法返回一個創建好的自定義view(屏蔽從xib加載的過程)
6.提供一個模型屬性讓外界傳遞模型數據
7.重寫模型屬性的setter方法,在這里將模型數據展示到對應的子控件上面
代理的使用
像上面的加載更多按鈕,他是在xib里面的,如果想讓用戶點擊它加載新數據,這件事得控制器ViewController來做。可以用代理模式實現,讓ViewController成為YUTgFooterView(我封裝該View的類名)的代理(delegate),這樣ViewController就可以監聽那個按鈕點擊做出反應了。
使用delegate的步驟
- 先搞清楚誰是誰的代理(delegate)
- 定義代理協議,協議名稱的命名規范:控件類名 + Delegate
- 定義代理方法
- 代理方法一般都定義為@optional
- 代理方法名都以控件名開頭
- 代理方法至少有1個參數,將控件本身傳遞出去
- 設置代理(delegate)對象 (比如myView.delegate = xxxx;)
- 代理對象遵守協議
- 代理對象實現協議里面該實現的方法
- 在恰當的時刻調用代理對象(delegate)的代理方法,通知代理發生了什么事情(在調用之前判斷代理是否實現了該代理方法)
2015/10/27
Day 26
昨天的項目中,cell是xib封裝的,每個cell的內容都是類似的,高度也是一樣的,這樣太局限,比如微博頁面,它的每個cell的內容是不一樣的,這就需要用代碼來自定義cell
如圖,每個cell最多可以有一個頭像,一個名字,一個vip標識,一個文本,一張圖片,plist文件結構如下
首先,把plist封裝成模型
創建自定義的cell類繼承自UItableViewCell
cell內部的控件屬性要寫在.m文件中的類擴展中
實現cell的構造方法
/**
* 構造方法(在初始化對象的時候會調用)
* 一般在這個方法中添加需要顯示的子控件
*/
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
UIImageView *iconView = [[UIImageView alloc] init];
[self.contentView addSubview:iconView];
self.iconView = iconView;
UILabel *nameView = [[UILabel alloc] init];
nameView.font = YUNameFont;
nameView.backgroundColor = [UIColor orangeColor];
[self.contentView addSubview:nameView];
self.nameView = nameView;
UIImageView *vipView = [[UIImageView alloc] init];
[self.contentView addSubview:vipView];
self.vipView = vipView;
UILabel *textView = [[UILabel alloc] init];
textView.font = YUTextFont;
textView.numberOfLines = 0;//自動換行!
textView.backgroundColor = [UIColor blueColor];
[self.contentView addSubview:textView];
self.textView = textView;
UIImageView *pictureView = [[UIImageView alloc] init];
[self.contentView addSubview:pictureView];
self.pictureView = pictureView;
}
return self;
}
再重寫成員屬性的setter方法,里面設置控件數據
/**設置子控件的大小和數據 */
-(void)setStatus:(YUStatus *)status {
_status = status;
//設置數據
[self setData];
//設置控件大小
[self setFrame];
}
設置的代碼冗長,我把他們封裝了
別忘了實現.h中聲明的類方法
+ (instancetype)cellWithTableView:(UITableView *)tableView{
static NSString *yu3 = @"statusCell";
YUStatusCell *cell = [tableView dequeueReusableCellWithIdentifier:yu3];
if (cell == nil) {
cell = [[YUStatusCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:yu3];
}
return cell;
}
最后到控制器,先讓控制器繼承自UITableViewController
設置成員屬性
重寫getter方法加載數據
- (NSArray *)statuses {
NSMutableArray *result = [[NSMutableArray alloc] init];
NSString *path = [[NSBundle mainBundle] pathForResource:@"statuses.plist" ofType:nil];
NSArray *dics = [NSArray arrayWithContentsOfFile:path];
for (NSDictionary *dic in dics) {
YUStatus *status = [YUStatus statusWithDic:dic];
[result addObject:status];
}
return result;
}
最后重寫數據源方法
//默認就是1,可以不寫這個方法
//- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// return 1;
//}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.statuses.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
YUStatusCell *cell = [YUStatusCell cellWithTableView:tableView];
cell.status = self.statuses[indexPath.row];
return cell;
}
這樣運行出來是這個效果,很坑爹,因為個cell默認的高度是一樣的,有個方法可以改變
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
//如何自定義cell高度?
return 100;
}
這里插播一下,計算一個字符串所占的size,如圖我文本的背景色
第一種方法,利用控件的一個方法
//根據內部文字以及參數中給的參考size計算自適應的size
CGSize nameSize = [self.nameView sizeThatFits:CGSizeZero];
第二種方法,利用NSString一個類別(分類)中的方法
//根據內部文字以及參數中給的參考的最大size計算自適應的size
CGSize nameSize = [status.name boundingRectWithSize:CGSizeMake(MAXFLOAT, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:14]} context:nil].size;
第一個參數:參考的最大size,這里可以規定最大寬度和高度
第二個參數:一般寫NSStringDrawingUsesLineFragmentOrigin |NSStringDrawingUsesFontLeading
第三個參數:是個字典,傳入字體大小
那么問題來了,如何計算每個cell的高度?
cell的高度是根據里面的數據決定的,數據是由模型傳過來的,因此cell的高度應該由模型來計算!
解決方法:
1.提供2個模型
- 數據模型: 存放文字數據\圖片數據
- frame模型: 存放數據模型\所有子控件的frame\cell的高度
2.cell擁有一個frame模型(frame模型已經包含數據模型了)
3.重寫frame模型屬性的setter方法: 在這個方法中設置子控件的顯示數據和frame
- (void)setStatus:(YUStatus *)status {
_status = status;
CGFloat padding = 20;
CGFloat iconX = padding;
CGFloat iconY = padding;
CGFloat iconW = 30;
CGFloat iconH = 30;
_iconF = CGRectMake(iconX, iconY, iconW, iconH);
//根據內部文字以及參數中給的參考的最大size計算自適應的size
CGSize nameSize = [status.name boundingRectWithSize:CGSizeMake(MAXFLOAT, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : YUNameFont} context:nil].size;
CGFloat nameX = CGRectGetMaxX(_iconF) + padding;
CGFloat nameY = iconY + (iconH - nameSize.height) / 2;
_nameF = CGRectMake(nameX, nameY, nameSize.width, nameSize.height);
CGFloat vipX = nameX + nameSize.width + padding;
CGFloat vipY = nameY;
CGFloat vipW = 14;
CGFloat vipH = 14;
_vipF = CGRectMake(vipX, vipY, vipW, vipH);
CGFloat textX = iconX;
CGFloat textY = CGRectGetMaxY(_iconF) + padding;
CGSize textSize = [status.text boundingRectWithSize:CGSizeMake(345, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading attributes:@{NSFontAttributeName : YUTextFont} context:nil].size;
_textF = CGRectMake(textX, textY, textSize.width, textSize.height);
if (status.picture) {
CGFloat picX = textX;
CGFloat picY = CGRectGetMaxY(_textF) + padding;
CGFloat picW = 200;
CGFloat picH = 200;
_picF = CGRectMake(picX, picY, picW, picH);
_cellHight = CGRectGetMaxY(_picF) + padding;
} else {
_cellHight = CGRectGetMaxY(_textF) + padding;
}
}
4.frame模型數據的初始化已經采取懶加載的方式(每一個cell對應的frame模型數據只加載一次)
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
YUStatusCellFrame *frame = self.statusFrames[indexPath.row];
return frame.cellHight;
}
最后這樣返回每個cell的高度就可以啦
最后說一下彈窗事件,我做了個點擊cell彈出對話框修改用戶名的功能
在iOS8以前,彈窗需要遵守協議還有實現按鈕的點擊處理,比較麻煩,而iOS8以后可以在一個方法里搞定,而且不用遵守協議
// cell點擊事件監聽
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
YUStatusCellFrame *statusFrame = self.statusFrames[indexPath.row];
NSString *name = statusFrame.status.name;
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"博主" message:nil preferredStyle:UIAlertControllerStyleAlert];
[alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
textField.text = name;
}];
UIAlertAction *modify = [UIAlertAction actionWithTitle:@"修改" style:UIAlertActionStyleDestructive handler:^(UIAlertAction * _Nonnull action) {
statusFrame.status.name = alert.textFields[0].text;
//修改后計算出新的name所占frame的大小
CGSize newSize = [statusFrame.status.name boundingRectWithSize:CGSizeMake(MAXFLOAT, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName : YUTextFont} context:nil].size;
statusFrame.nameF = CGRectMake(CGRectGetMinX(statusFrame.nameF) , CGRectGetMinY(statusFrame.nameF), newSize.width, newSize.height);
//重新加載該行數據
[tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationRight];
}];
UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
}];
[alert addAction:modify];
[alert addAction:cancel];
[self presentViewController:alert animated:YES completion:nil];
}