前面對著別人的代碼學(xué)習(xí)做了第一個iOS的Demo-通訊錄融击,這次完全靠自己設(shè)計編碼實現(xiàn)了另一個簡單的Demo-備忘錄。沒錯,就是仿iPhone上的備忘錄沮明。雖然demo很簡單色乾,但是我完全自己做的第一個demo誊册,涵蓋了我所學(xué)到的大部分知識,比如委托和協(xié)議暖璧、UITableViewController案怯、UINavigationController等等,自認為對初學(xué)者有點幫助。
本文將詳細講解我的設(shè)計思路和源碼分析澎办。文章最后有源碼鏈接嘲碱,歡迎指正!
- 功能介紹
- 設(shè)計模式
- 實現(xiàn)細節(jié)
- 不足之處
1. 功能介紹###
其實大家都應(yīng)該用過備忘錄局蚀,而且本來就很簡單麦锯,即使看上面的gif圖就大致了解了備忘錄的功能了,這里簡單說明一下至会。
首先离咐,首頁上顯示的是賬戶列表。你可以有很多個賬戶奉件,我隨機選了三個賬戶作為例子宵蛀。這里的賬戶個數(shù)是固定的,當(dāng)然實現(xiàn)可變也是很簡單的县貌。
點擊任何一個賬戶選項术陶,進入新的頁面,展示了當(dāng)前選擇賬戶下的備忘錄主題列表煤痕。列表選項的左邊是備忘錄的題目梧宫,右側(cè)是創(chuàng)建時間。其中如果創(chuàng)建時間在24小時之內(nèi)摆碉,就只顯示時和分塘匣,否則只顯示年月日。
若點擊任何一個備忘錄選項巷帝,進入新的頁面忌卤,展示當(dāng)前選擇備忘錄的詳細內(nèi)容,可以直接編輯修改這個備忘錄楞泼,但不能是空驰徊;若點擊“新建”按鈕,則進入創(chuàng)建新備忘錄的頁面堕阔,新備忘錄的第一行文本默認作為標(biāo)題棍厂;若向左滑動一個選項,則彈出“刪除”按鈕超陆,點擊可以刪除這個備忘錄牺弹。
在創(chuàng)建新備忘錄的頁面中,若點擊“返回”,則什么都不做例驹;若點擊“完成”捐韩,若新備忘錄是空的,則什么都不做鹃锈,否則添加新的備忘錄到內(nèi)存中荤胁。
設(shè)計模式###
還是最簡單經(jīng)典的MVC模式。
-
Model####
設(shè)計一個JWMemoDetail類屎债,表示一個備忘錄信息對象仅政,包括標(biāo)題,創(chuàng)建時間和具體內(nèi)容盆驹。
@interface JWMemoDetail : NSObject
#pragma mark 標(biāo)題
@property (nonatomic, strong) NSString *title;
#pragma mark 創(chuàng)建時間
@property (nonatomic, strong) NSString *createTime;
#pragma mark 具體內(nèi)容
@property (nonatomic,strong) NSString *detail;
#pragma mark 初始化方法
- (JWMemoDetail *)initWithTitle:(NSString *)title andCreateTime:(NSString *)createTime
andDetail:(NSString *)detail;
#pragma mark 靜態(tài)初始化方法
+ (JWMemoDetail *)memoDetailWithTitle:(NSString *)title andCreateTime:(NSString *)createTime
andDetail:(NSString *)detail;
@end
再設(shè)計一個JWMemoAccount類圆丹,表示一個賬戶信息對象,包括賬戶名稱和所包含的若干備忘錄信息對象躯喇。
@class JWMemoDetail;
@interface JWMemoAccount : NSObject
#pragma mark 賬戶名稱
@property (nonatomic,strong) NSString *accountName;
#pragma mark 具體內(nèi)容(標(biāo)題辫封、時間、內(nèi)容)
@property (nonatomic,strong) NSMutableArray *memoDetail;
#pragma mark 初始化方法
- (JWMemoAccount *)initWithAccountName:(NSString *)accountName andDetail:(NSMutableArray *)detail;
#pragma mark 靜態(tài)初始化方法
+ (JWMemoAccount *)memoAccountWithAccountName:(NSString *)accountName andDetail:(NSMutableArray *)detail;
@end
有了這兩個Model廉丽,可以滿足所有的ViewController操作以及所有的View展示了倦微。
-
View####
對照功能介紹,就只有四個簡單的視圖正压,分別是:
首頁視圖欣福,homeView,繼承自UITableView焦履。 展示賬戶列表拓劝。
目錄視圖,contentView嘉裤,繼承自UITableView郑临。展示備忘錄標(biāo)題列表。
詳細視圖屑宠,deteailView牧抵,繼承自UITextView。展示備忘錄相信信息侨把。
新建視圖,neMemoView妹孙,繼承自UITextView秋柄。編輯新建備忘錄。
-
Controller####
由于是多個頁面之間的切換蠢正,就我目前所學(xué)骇笔,知道最好的方法是用UINavigationController。所以設(shè)置最開始的rootViewController為一個UINavigationController。
self.navViewController = [[UINavigationController alloc] initWithRootViewController:self.homeViewController];
self.window.rootViewController = self.navViewController;
剩余就是跟View對應(yīng)的幾個Controller笨触。
JWHomeViewController懦傍,繼承自UITableViewController。
JWContentViewController芦劣,繼承自UITableViewController粗俱。
JWDetailViewController,繼承自UIViewController虚吟。
JWNewMemoViewController寸认,繼承自UIViewController。
這個navViewController的rootViewController是首頁視圖的Controller串慰,既homeViewController偏塞。
各個視圖之間的切換借助于UINavigationController的
pushViewController:animated:和popViewControllerAnimated:方法
[self.navigationController pushViewController:self.contentViewController animated:YES];
[self.navigationController popViewControllerAnimated:YES];
比如從首頁到詳細信息的過程中,控制器棧的情況如下圖:
實現(xiàn)細節(jié)###
-
數(shù)據(jù)持久化
關(guān)于iOS的數(shù)據(jù)持久化邦鲫,大家都知道常見的四種方法:屬性列表灸叼、對象歸檔、SQLite3和Core Data庆捺。在本demo中古今,選擇的是第一種方法,原因有二:(1) 我第一次使用數(shù)據(jù)持久化疼燥,選個最簡單的試試先沧卢;(2)備忘錄數(shù)據(jù)很簡單,而且沒有安全性的要求醉者,所以選擇屬性列表最方便但狭。
這里通過沙盒機制創(chuàng)建和使用 plist。
我在程序啟動一開始就記錄下數(shù)據(jù)文件的絕對路徑撬即,方便后續(xù)的讀取和寫入立磁。
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *plistPath = [paths objectAtIndex:0];
self.homeViewController.dataFileName = [plistPath stringByAppendingPathComponent:@"MemoInfo.plist"];
那在首頁加載完成后,就可以讀取數(shù)據(jù)到內(nèi)存中了:
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:self.dataFileName];
在程序退出的時候?qū)?nèi)存中最新的數(shù)據(jù)寫入文件中:
[dataToStore writeToFile:self.dataFileName atomically:YES];
MemInfo.plist存儲的格式如下圖所示:
首先是一個Dictonary剥槐,再是一個Array唱歧,每個元素又是一個Dictionary。
由于plist只能存儲Array粒竖、Dictionary颅崩、String等簡單數(shù)據(jù)類型,不能存儲自定義類型蕊苗,所以在存儲的時候沿后,還要做個轉(zhuǎn)化。
//將_memoAccount中的memoDetails轉(zhuǎn)化為NSDictionary類型
NSMutableDictionary *dataToStore = [[NSMutableDictionary alloc] init];
for (JWMemoAccount *account in _memoAccount) {
NSString *accoutName = [account accountName];
NSMutableArray *accountDetails = [account memoDetail];
NSMutableArray *tmpArr = [[NSMutableArray alloc] init];
for (JWMemoDetail *md in accountDetails) {
NSMutableDictionary *tmpDic = [[NSMutableDictionary alloc] init];
[tmpDic setValue:[md title] forKey:@"title"];
[tmpDic setValue:[md createTime] forKey:@"createTime"];
[tmpDic setValue:[md detail] forKey:@"detail"];
[tmpArr addObject:tmpDic];
}
[dataToStore setObject:tmpArr forKey:accoutName];
}
[dataToStore writeToFile:self.dataFileName atomically:YES];
-
共用視圖
這里的視圖都是共用的朽砰。具體地說尖滚,所有賬戶的備忘錄目錄都是共用一個contentView的喉刘;所有備忘錄的具體內(nèi)容都是共用一個detailView的;在任何賬戶下新建備忘錄時共用的是neMemoView的漆弄。實現(xiàn)這一點要注意的就是保證數(shù)據(jù)源不同:不同的賬戶展示的contentView的數(shù)據(jù)源是不同的睦裳,不同備忘錄選項展示的detailView的數(shù)據(jù)源也是不同的。其原理就是每次進入新的視圖頁面時撼唾,會傳遞不同的參數(shù)值廉邑。
homeView ->contentView:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
JWMemoAccount *account = [_memoAccount objectAtIndex:indexPath.row];
self.selectedIndex = indexPath.row;
//第一次進入contentViewController才分配內(nèi)存
//以后直接復(fù)用。但是這里不要用initWithArray,
//要顯示賦值券坞,才能使account的memoDetail與contentViewController的
//memoDetails指向同一塊內(nèi)存鬓催,才能使兩者保持實時一致性。
if (self.contentViewController.memoDetails == nil) {
self.contentViewController.memoDetails = [[NSMutableArray alloc] init];
self.contentViewController.memoDetails = [account memoDetail];
} else {
self.contentViewController.memoDetails = [account memoDetail];
}
...
}
contentView —>detailView:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
JWMemoDetail *md = [_memoDetails objectAtIndex:indexPath.row];
self.selectedIndex = indexPath.row;
NSString *detail = [md detail];
self.detailViewController.detail = detail;
self.detailViewController.createTime = [md createTime];
...
}
此時恨锚,新的視圖接收到的數(shù)據(jù)會被更新了宇驾,但是由于視圖的加載是只有一次的,再次進入視圖時并不會自動更新tableView的視圖猴伶,所以視圖顯示的數(shù)據(jù)還是舊的课舍,始終是第一次打開的賬戶的備忘錄列表和內(nèi)容。 這就需要手動刷新tableView的數(shù)據(jù)他挎,這里選擇的刷新時刻是在視圖即將展現(xiàn)的時刻:
- (void)viewWillAppear:(BOOL)animated {
[self.tableView reloadData];
}
這樣筝尾,點擊新的賬戶或者備忘錄時,下一個視圖的數(shù)據(jù)是新的办桨,而且在展現(xiàn)視圖之前已經(jīng)刷新了tableView筹淫,最終達到了共用視圖展示不同數(shù)據(jù)的效果。
-
數(shù)據(jù)同步
根據(jù)前面的Model設(shè)計方式呢撞,不同的ViewController管理的Model是不同的损姜。JWHomeViewController管理是整個數(shù)據(jù)結(jié)構(gòu)JWMemoAccount;JWContentViewController管理的是部分數(shù)據(jù)結(jié)構(gòu)JWMemoDetail殊霞;JWDetailViewController管理的是更小部分的數(shù)據(jù)結(jié)構(gòu)detail和createTime摧阅。
當(dāng)更新或添加新的備忘錄時,不僅要保證當(dāng)前detail和createTime更新绷蹲,而且JWMemoDetail和JWMemoAccout也要更新棒卷,既要保證數(shù)據(jù)的全局同步性。實現(xiàn)的原理就是利用OC的引用指針祝钢。引用指針使不同的指針對象指向同一塊內(nèi)存區(qū)域比规,任一個指針對象對內(nèi)存的改變將對所有的指針對象可見。
JWContentViewController的屬性memoDetails是NSMutableArray類型的拦英,但使用的描述符是strong而非copy蜒什,這樣它與JWHomeViewController的JWMemoContent屬性中的memoDetails指向同一塊內(nèi)存。更新數(shù)據(jù)對兩者都可見龄章。
@property (nonatomic,strong) NSMutableArray *memoDetails;
要注意的就是吃谣,在JWHomeViewController給JWContentViewController的memoDetails第一次賦值的時候,不要用initWithArray方法做裙,它會默認使用copy岗憋,而要顯示賦值。
if (self.contentViewController.memoDetails == nil) {
self.contentViewController.memoDetails = [[NSMutableArray alloc] init];
self.contentViewController.memoDetails = [account memoDetail];
-
更新/添加備忘錄的協(xié)議及委托
更新備忘錄后锚贱,返回上一級控制器仔戈,需要上一級控制器更新數(shù)據(jù);同樣拧廊,添加完新的備忘錄后监徘,返回上一級控制器,也要上一級控制器更新數(shù)據(jù)吧碾。
給下一級控制器傳值時可以直接調(diào)用下級控制器的setter方法凰盔,而給上一級控制器傳值時需要用到協(xié)議和委托。
具體方法是:
若控制器C1是控制器C2的上一級倦春,C2返回到C1時需要給C1傳值户敬。
1.定義一個協(xié)議P,聲明一個傳值的方法F睁本,參數(shù)類型是傳值的類型;
2.在C2中定義一個P類型的委托D尿庐;
3.在C1中,實現(xiàn)P協(xié)議的方法F呢堰;
4.在C1中抄瑟,指定C2的D是self(C1);
5.在C2的合適地方給傳值賦值,并調(diào)用D的方法F;
這樣枉疼,就可以讓C1獲的C2想傳遞的值了皮假。參考下面具體代碼:
//JWNewMemoProtocol.h
@protocol NewMemoProtocol
- (void) addNewMemo:(JWMemoDetail *)memoData;
@end
//JWDetailViewController.h
@property (nonatomic) id<UpdateMemoProtocol> delegate;
//JWContentViewController.m
@interface JWContentVIewController ()<NewMemoProtocol,UpdateMemoProtocol>
@end
- (void) updateMemo:(JWMemoDetail *)memoData {
[self.memoDetails replaceObjectAtIndex:self.selectedIndex withObject:memoData];
}
self.detailViewController.delegate = self;
//JWDetailViewController.m
[self.delegate updateMemo:memoDetail];
-
鍵盤和中文輸入法
在UITextView中,一開始我這里是獲得焦點后沒有彈出鍵盤的往衷,后來google一下钞翔,其實很簡單,Cmd + Shift + K就可以調(diào)出來席舍。
一開始也是不能輸中文的布轿,方法是在模擬器中的settings->General->Keyboard->Keyboards->Add New Keyboard->Chinese就可以了。
不足之處###
雖然可以實現(xiàn)基本的功能来颤,但還是有很多不足之處的:
- 首頁改成課編輯的汰扭,既添加/刪除 賬戶。
- 備忘錄選項的標(biāo)題長度超過一定長時顯示省略號福铅,不遮擋時間萝毛。
- 數(shù)據(jù)模型的定義方式與存儲方式不同,在寫文件時要做一次轉(zhuǎn)化滑黔,顯得很不雅笆包。
當(dāng)然還有很多不足之處环揽,畢竟小白第一次自己寫iOS小程序,文件組織庵佣、代碼風(fēng)格歉胶、性能方面肯定有很多需要改進的地方,真誠希望各位大牛指正巴粪!
源碼:https://github.com/foolish-boy/Memo
其中在Memo目錄下有MemoInfo.plist通今,測試的話可以把他拷貝到你自己的沙盒目錄下去。