iOS 小而精的Demo(2) 備忘錄

備忘錄.gif

前面對著別人的代碼學(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展示了倦微。

Model之間的關(guān)系
  • 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é)###

  1. 數(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存儲的格式如下圖所示:


MemoInfo.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];
  1. 共用視圖
    這里的視圖都是共用的朽砰。具體地說尖滚,所有賬戶的備忘錄目錄都是共用一個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ù)的效果。

  1. 數(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];
  1. 更新/添加備忘錄的協(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];
  1. 鍵盤和中文輸入法
    在UITextView中,一開始我這里是獲得焦點后沒有彈出鍵盤的往衷,后來google一下钞翔,其實很簡單,Cmd + Shift + K就可以調(diào)出來席舍。

一開始也是不能輸中文的布轿,方法是在模擬器中的settings->General->Keyboard->Keyboards->Add New Keyboard->Chinese就可以了。


不足之處###

雖然可以實現(xiàn)基本的功能来颤,但還是有很多不足之處的:

  1. 首頁改成課編輯的汰扭,既添加/刪除 賬戶。
  1. 備忘錄選項的標(biāo)題長度超過一定長時顯示省略號福铅,不遮擋時間萝毛。
  2. 數(shù)據(jù)模型的定義方式與存儲方式不同,在寫文件時要做一次轉(zhuǎn)化滑黔,顯得很不雅笆包。

當(dāng)然還有很多不足之處环揽,畢竟小白第一次自己寫iOS小程序,文件組織庵佣、代碼風(fēng)格歉胶、性能方面肯定有很多需要改進的地方,真誠希望各位大牛指正巴粪!

源碼:https://github.com/foolish-boy/Memo
其中在Memo目錄下有MemoInfo.plist通今,測試的話可以把他拷貝到你自己的沙盒目錄下去。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末肛根,一起剝皮案震驚了整個濱河市辫塌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌派哲,老刑警劉巖臼氨,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異狮辽,居然都是意外死亡一也,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進店門喉脖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來椰苟,“玉大人,你說我怎么就攤上這事树叽∮吆” “怎么了?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵题诵,是天一觀的道長洁仗。 經(jīng)常有香客問我,道長性锭,這世上最難降的妖魔是什么赠潦? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮草冈,結(jié)果婚禮上她奥,老公的妹妹穿的比我還像新娘。我一直安慰自己怎棱,他們只是感情好哩俭,可當(dāng)我...
    茶點故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著拳恋,像睡著了一般凡资。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上谬运,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天隙赁,我揣著相機與錄音垦藏,去河邊找鬼。 笑死伞访,一個胖子當(dāng)著我的面吹牛膝藕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播咐扭,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼滑废!你這毒婦竟也來了蝗肪?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蠕趁,失蹤者是張志新(化名)和其女友劉穎薛闪,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體俺陋,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡豁延,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了腊状。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诱咏。...
    茶點故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖缴挖,靈堂內(nèi)的尸體忽然破棺而出袋狞,到底是詐尸還是另有隱情,我是刑警寧澤映屋,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布苟鸯,位于F島的核電站,受9級特大地震影響棚点,放射性物質(zhì)發(fā)生泄漏早处。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一瘫析、第九天 我趴在偏房一處隱蔽的房頂上張望砌梆。 院中可真熱鬧,春花似錦颁股、人聲如沸么库。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽诉儒。三九已至,卻和暖如春亏掀,著一層夾襖步出監(jiān)牢的瞬間忱反,已是汗流浹背泛释。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留温算,地道東北人怜校。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像注竿,于是被迫代替她去往敵國和親茄茁。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,630評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 概述在iOS開發(fā)中UITableView可以說是使用最廣泛的控件巩割,我們平時使用的軟件中到處都可以看到它的影子裙顽,類似...
    liudhkk閱讀 9,067評論 3 38
  • 前言 最近忙完項目比較閑,想寫一篇博客來分享一些自學(xué)iOS的心得體會宣谈,希望對迷茫的你有所幫助愈犹。博主非科班出身,一些...
    GitHubPorter閱讀 1,435評論 9 5
  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結(jié)起來就是把...
    Dove_iOS閱讀 27,165評論 30 470
  • 1 場景問題# 1.1 開發(fā)仿真系統(tǒng)## 考慮這樣一個仿真應(yīng)用闻丑,功能是:模擬運行針對某個具體問題的多個解決方案漩怎,記...
    七寸知架構(gòu)閱讀 2,150評論 1 50
  • 下午六點鐘,我與這個城市照面嗦嗡。 夕陽下 有人下地鐵勋锤,有人上電梯,有人取車侥祭, 有人發(fā)傳單怪得,有人等綠燈,有人走路卑硫。 而...
    蔥頭越閱讀 279評論 0 1