私人通讯录知识点

Posted on 2016-06-29 19:35  柠檬片  阅读(225)  评论(0)    收藏  举报

1.演示私人通讯录

    • 学习目标:控制器跳转,控制器之间传值,数据存储

 

2.项目演示:(4个界面, 交给4个控制器管理, 每个界面一个控制器。)

    0> 先搭建项目的界面。(使用自动布局搭建界面)

 

    0.1> 为每个控制器创建自定义的控制器类。

 

    1>  登录界面:

        ○ 登录按钮只有文本框都有文字才能点击

            ** 实现思路: 为"账号"和"密码"文本框都设置监听事件, 当文本框内容改变的时候, 判断如果两个文本框都有内容, 那么设置登录按钮可用, 否则登录按钮不可用。

 

            ** 设置监听事件常用的3种方式:

            1> 通过addTarget方式, 但是这种方式只能监听一些"单击事件"、"滚动条滚动事件"等, 有些事件通过addTarget方式监听无效, 比如"Value Changed"事件。

 

            2> 通过代理, 文本框的代理协议"UITextFieldDelegate"。演示:textFieldShouldBeginEditing方法。

            /** 演示代码:

             

             // 为某个文本框设置代理

             - (void)viewDidLoad {

                 [super viewDidLoad];

                 // 设置文本框代理为当前控制器。

                 self.txtLoginId.delegate = self;

             }

             

             // 让当前控制器遵守UITextFieldDelegate协议, 并且实现- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField方法, 返回YES

             - (BOOL)textFieldShouldBeginEditing:(UITextField *)textField

             {

                return YES;

             }

             

             */

 

            3> 通过"通知中心"监听事件。

                ** 思路: 让用户名、密码文本框同时设置监听到textChanged方法。

            /** 参考代码:

             

             - (void)viewDidLoad {

                 [super viewDidLoad];

             

                 //通过通知中心监听文本框内容变化

                 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textFieldValueChanged) name:UITextFieldTextDidChangeNotification object:self.userNameTextField];

                 

                 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textFieldValueChanged) name:UITextFieldTextDidChangeNotification object:self.passWordTextField];

                 

                 

                 //    UIKIT_EXTERN NSString *const UITextFieldTextDidBeginEditingNotification;

                 //    UIKIT_EXTERN NSString *const UITextFieldTextDidEndEditingNotification;

                 //    UIKIT_EXTERN NSString *const UITextFieldTextDidChangeNotification;

             }

             

             //实现监听方法

             - (void) textFieldValueChanged

             {

                 //判断用户名和密码文本框都有内容时  登录按钮可用 否则 不可用

                 if (self.userNameTextField.text.length > 0 && self.passWordTextField.text.length > 0)

                 {

                 self.loginBtn.enabled = YES;

                 }

                 else

                 {

                 self.loginBtn.enabled = NO;

                 }

                 

                 //优化

                 //    self.loginBtn.enabled = self.userNameTextField.text.length > 0 && self.passWordTextField.text.length > 0;

             

             }

             

             

             **** 注意通过通知的方式注册事件, 需要在dealloc方法中移除 ****

                 // 移除通知监听

                 - (void)dealloc

                 {

                    [[NSNotificationCenter defaultCenter]removeObserver:self];

                 }

             **** 注意通过通知的方式注册事件, 需要在dealloc方法中移除 ****

             

             

             */

 

 

        ○ 开关细节注意:当打开自动登录,记住密码也会打开,当关闭记住密码,自动登录也会打开。

            思路: 监听开关的"Value Changed"事件。

            /** 参考代码:

             

             //自动登录

             - (IBAction)autoSwitchValueChanged:(UISwitch *)sender

             {

                 if (sender.isOn)

                 {

                 //以动画的形式关闭开关

                 [self.remSwitch setOn:YES animated:YES];

                 }

             }

             //记住密码

             - (IBAction)remSwitchValueChanged:(UISwitch *)sender

             {

                 if (sender.isOn == NO)

                 {

                 //以动画的形式关闭开关

                 [self.autoSwitch setOn:NO animated:YES];

                 }

             }

             

             */

         

         

        ○ 文本框有占位符,提示用户输入什么。文本框的"Placeholder"属性

 

        ○ 密码文本框是暗文,密码属于机密,不需要显示出来。文本框的"Secure Text Entry"属性

 

        ○ 当文本框输入文字,会显示清除按钮,提高用户体验。文本框的"Clear Button"属性

 

        ○ 点击登录,会判断账号和密码是否正确,只有正确输入才会进入下一个界面,并且提示用户正在登录,模拟网络延迟。

        ** 思路:

            1> 当直接按住某个按钮, 然后拖线到另外一个控制器的时候, 点击按钮时, 直接跳转到另外一个控制器, 无法拦截跳转操作进行用户名、密码校验。

 

            2> 需要当点击按钮的时候, 可以判断用户名、密码, 如果正确才跳转到下一个控制器的界面。

 

            3> 所以如果希望"拦截"跳转过程, 需要按住control键, 从"源"控制器, 拖线到"目标"控制器。

 

            4> 介绍Segue对象。参考PPT。

                ** 在xcode6.1下, 拖线时选择Segue的style为show, 等价于以前的push。

 

                ** 重新拖线(手动 Segue), 从控制器 to 控制器。

            /** 参考代码 :

             

             // 去掉两端空格参考代码:

             NSString *loginId = self.txtLoginId.text;

             loginId = [loginId stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];

             

             

             //登录按钮事件

             - (IBAction)loginBtnClick

             {

                 //收回键盘

                 [self.view endEditing:YES];

                 

                 //显示登录状态

                 [MBProgressHUD showMessage:@"正在拼命登录..." toView:self.view];

                 

                 //模拟延迟登录

                 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

                     //隐藏状态

                     [MBProgressHUD hideHUDForView:self.view];

                     

                     if ([self.userNameTextField.text isEqualToString:@"admin"] && [self.passWordTextField.text isEqualToString:@"123"])

                     {

                         //执行收到sugue

                         [self performSegueWithIdentifier:@"login2contact" sender:@"Jack"];

                     }

                     else

                     {

                         NSLog(@"对不起!您的用户名或者密码有误");

                         [MBProgressHUD showError:@"对不起!您的用户名或者密码有误" toView:self.view];

                     }

                 });

             }

             */

 

            5> 使用MBProgressHUD框架改进操作提示对话框(代码见上面)。

 

            6> 登录时, 显示"等待指示器"2秒, 模拟登录。

 

 

    2> 联系人列表界面

        ○ 联系人列表界面导航条的标题跟账号有关系(控制器之间传值,把登录界面的账号传递过来)

        ** 登录成功以后, 在联系人界面的导航栏中, 显示当前登录人的账号"Steve的联系人"

        ** 思路:

                1> 实现控制器的- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender方法。

 

                2> 在该方法中获取目标控制器, 然后把值直接传递给目标控制器的导航栏的title属性。

                ** 该方法会在Segue即将跳转之前自动执行。

                ** 这个方法一般用作跳转之前的准备工作、输出传递等。

 

                /** 参考代码:

                 // 在控制器跳转之前执行

                 - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {

                     // Get the new view controller using [segue destinationViewController].

                     // Pass the selected object to the new view controller.

                     //    NSLog(@"%@",sender);

                     //获取目标控制器

                     UIViewController * targetVC = segue.destinationViewController;

                     

                     targetVC.title = [NSString stringWithFormat:@"%@的联系人",self.userNameTextField.text];

                 }

                 

                 ====================== 补充: performSegueWithIdentifier:sender:的执行过程 =======================

                 

                 [self performSegueWithIdentifier:@"login2contacts" sender:nil];

                 

                 1> self是来源控制器,只能通过来源控制器来调该方法。

                 

                 2> 根据identifier去storyboard中找到对应的线,新建UIStoryboardSegue对象

                 3> 设置Segue对象的sourceViewController(来源控制器)

                 4> 新建并且设置Segue对象的destinationViewController(目标控制器)

                 5> 调用- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender方法

                 6> 执行跳转。

                    ** 具体跳转步骤:

                    1> 通过segue对象的sourceViewController获取源控制器所在的导航控制器。

                    2> 将segue的destinationViewControlelr压栈(push)进去。

 

                 ====================== 补充: performSegueWithIdentifier:sender:的执行过程 =======================

                 

                 

                 */

 

        ○ 注销按钮,回到登录界面

        ** 思路:

                1> 为"注销"按钮注册单击事件

 

                2> 在单击事件中弹出"UIActionSheet"对话框

                ** 设置"UIActionSheet"的代理为当前控制器

                ** 让当前控制器遵守"UIActionSheetDelegate"协议, 实现- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex方法。

                ** 判断"UIActionSheet"对话框, 用户点击的时哪个按钮, 如果点击的是"注销", 则弹出当前控制器。

        /** 参考代码:

            

         //注销按钮事件

         //xcode7下

         - (IBAction)logOutBtnClick:(id)sender

         {

             UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"确定注销吗?" message:nil preferredStyle:UIAlertControllerStyleActionSheet];

             

             UIAlertAction * okAction = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDestructive handler:^(UIAlertAction * _Nonnull action) {

             [self.navigationController popViewControllerAnimated:YES];

             }];

             

             UIAlertAction * cancel = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil];

             

             [alert addAction:okAction];

             

             [alert addAction:cancel];

             

             [self presentViewController:alert animated:YES completion:nil];

         }

         

         // 注销按钮的单击事件 xcode6

         - (IBAction)logout:(id)sender {

             // 1. 弹出UIActionSheet对话框

             UIActionSheet *sheet = [[UIActionSheet alloc] initWithTitle:@"确定要注销" delegate:self cancelButtonTitle:@"取消" destructiveButtonTitle:@"注销" otherButtonTitles:nil, nil];

             

             [sheet showInView:self.view];

         }

         

         

         // UIActionSheet中的按钮点击事件

         - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex

         {

             if (buttonIndex == 0) {

                [self.navigationController popViewControllerAnimated:YES];

             }

         }

         

         

         */

 

 

        ○ 添加按钮进入添加联系人界面

        ** 通过按住"+"按钮, 拖线到"MJKAddContactViewController"控制器

 

 

    3> 添加联系人界面

        ○ 默认焦点停留在姓名文本框中,并且叫出键盘。不需要用户点击文本框弹出键盘,人性化设计。

            ** 让姓名文本框成为第一响应者。

            ** 使用viewDidAppear事件。

            /** 参考代码:

             

             // 注意: 使用viewDidAppear事件, 不要使用viewDidLoad, 否则是键盘先出来。

             //视图已经显示

             - (void) viewDidAppear:(BOOL)animated

             {

                 [super viewDidAppear:animated];

                 //呼出键盘

                 [self.nameField becomeFirstResponder];

             

             }

             

             

             */

 

 

        ○ 设置"添加"按钮一个开始是"禁用"状态。等到姓名和电话都输入内容以后才变为"可用"状态。

            ** 为文本框注册文本改变事件。通过通知。与设置"登录"按钮是否可用的代码相同。

 

        ○ 修改"MJKAddContactViewController"控制器界面的状态栏的最左侧按钮为"返回"。

            ** 通过设置上一个控制器的navigationItem.backBarButtonItem.title = @"返回";

            ** 或者直接在storyboard中, 找到上一个控制器的"导航栏", 修改Back Button属性的值。

 

        ○ 点击添加按钮,回到联系人列表界面,并且把数据显示到联系人列表界面(控制器之间传值)

            ** 为"添加"按钮注册单击事件。

            ** 在"添加"事件中:

                1> 关闭当前控制器

 

                2> 把数据回传给上一个控制器, 通过代理实现。

 

 

                ** 如果回传的数据很多的话, 在代理方法中通过模型实现。

 

                /** 参考代码:

                 1.在MJKAddContactViewController.h中:

                 @class MJKAddContactViewController,MJKContact;

                 //定义代理

                 

                 @protocol MJKAddContactViewControllerDelegate <NSObject>

                 //1.定义代理方法

                 - (void) addContactViewController:(MJKAddContactViewController *) addContactVC andContact:(MJKContact *) contact;

                 

                 @end

                 @interface MJKAddContactViewController : UIViewController

                 //2.定义代理属性

                 @property (nonatomic,weak) id<MJKAddContactViewControllerDelegate> delegate;

                 @end

                 

                 2.在MJKAddContactViewController.m中点击添加按钮:

                 //添加按钮事件

                 - (IBAction)addBtnClick:(id)sender

                 {

                 //1.创建联系人对象

                 MJKContact * contact = [[MJKContact alloc] init];

                 

                 //2.获取用户输入的姓名

                 contact.name = self.nameField.text;

                 //3.获取用户输入的电话

                 contact.phone = self.phoneField.text;

                 

                 //在合适的地方调用代理

                 

                 //先判断代理能否响应代理方法

                 if ([self.delegate respondsToSelector:@selector(addContactViewController:andContact:)])

                 {

                 [self.delegate addContactViewController:self andContact:contact];

                 }

                 

                 

                 //    NSLog(@"姓名:%@ ----- 电话:%@",contact.name,contact.phone);

                 

                 //回到上个控制器

                 [self.navigationController popViewControllerAnimated:YES];

                 }

 

                 3.在MJKContactViewController.m中:

                 //1.设置代理

                 //2.遵守代理协议

                 //3.实现代理方法

                 - (void)addContactViewController:(MJKAddContactViewController *)addContactVC andContact:(MJKContact *)contact

                 {

                 //    NSLog(@"姓名:%@ ----- 电话:%@",contact.name,contact.phone);

                 //把传过来的联系人对象添加到联系人数组里

                 [self.contacts addObject:contact];

                 //刷新联系人列表

                 [self.tableView reloadData];

                 }

                 

                 */

 

    4> 编辑联系人界面

        ○ 点击cell,进入编辑联系人界面

        ** 思路: 在"联系人列表"控制器中, 选中Cell单元格, 然后按住control键, 直接拖线到"编辑控制器"。

        ** 注意: 事实上, 在本例中, 按照如上方式是无法实现控制器跳转的, 因为TableView中的的Cell是通过代码直接动态创建的, 不是加载的storyboard中的cell。【解决:为storyboard中的Cell设置一个identifier的值为"contact", 这样的话动态创建Cell的那段代码就可以根据storyboard中的这个cell创建了】

            /** 参考代码:

             - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

             {

                 MJKContactCell *cell = [tableView dequeueReusableCellWithIdentifier:@"contactCell"];

                 

                 // Configure the cell...

                 

                 //

                 cell.contact = self.contacts[indexPath.row];

                 

                 return cell;

             }

             

             */

 

        ** 报错: "reason: '-[MJKEditorViewController setDelegate:]: unrecognized selector sent to instance 0x7feb51fc3b20'"

        【原因: 在"联系人列表"控制器中, 有两个segue对象(控制器跳转连线), 无论任何一个segue被执行, 在跳转之前都会执行- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender方法。】而现在这个方法中的代码如下(在这段代码中, 没有判断是哪个segue的目标控制器, 就直接调用delegate属性进行赋值了, 所以报错了。):

 

        【解决: 在prepareForSegue中判断当前执行的时哪个segue, 或者判断当前的目标控制器是否为我们要得目标控制器, 然后再执行相应代码】

            /** 参考代码:

             

             - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {

                 // Get the new view controller using [segue destinationViewController].

                 // Pass the selected object to the new view controller.

                 

                 //获取目标控制器

                 UIViewController * vc = segue.destinationViewController;

                 //判断目标控制器是否是添加联系人控制器

                 if([vc isKindOfClass:[MJKAddContactViewController class]])

                 {

                 

                     MJKAddContactViewController * addVC = (MJKAddContactViewController *) vc;

                     //1.设置代理

                     addVC.delegate = self;

                 }

                 else if ([vc isKindOfClass:[MJKEditorViewController class]])//如果是编辑联系人界面 对应的操作

                 {

                     MJKEditorViewController * editorVC = (MJKEditorViewController *) vc;

                     

                     //获取tableView 选中的 indexPth

                     NSIndexPath * indexPth = [self.tableView indexPathForSelectedRow];

                     

                     //通过 indexPth 获取row

                     

                     NSInteger row = indexPth.row;

                     //通过row 在联系人数组里获取选中的联系人

                     

                     MJKContact * con = self.contacts[row];

                     //将获取的联系人给目标控制器的contact属性

                     editorVC.contact = con;

                     

                     //        NSLog(@"----%p",editorVC.contact);

                     

                     //使用代理 --- 1.设置代理

                     editorVC.delegate = self;

                 

                 }

             }

 

             

             */

 

        ○  设置"编辑"控制器的导航栏的标题和"编辑/取消"按钮

 

        ○ 默认保存按钮是隐藏的。

        ** 设置"保存"按钮为Hidden。

 

        ○ 默认文本框不能交互,一开始是查看联系人。

        ** 设置文本框的Enable属性为NO。

 

        ○ 当现实"编辑"界面的时候, 将当前点击的单元格的数据传递过来。

        ** 思路: 拦截- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender方法, 在该方法中判断如果"目标控制器"是"编辑控制器", 则传递相应数据。

 

        ○ 当点击编辑按钮的时候,文本框才允许交互,并且保存按钮默认不能点击

        ** 为编辑按钮注册单击事件, 当单击的时候, 设置两个文本框可用、按钮显示并且可用、电话号码文本框成为第一响应者。

        /** 参考代码:

         //编辑或者取消按钮

         - (IBAction)editOrCancleBtn:(UIBarButtonItem *)sender {

         

             //通过保存按钮的显示状态 来做相应的操作

             if(self.saveBtn.hidden)

             {

                 sender.title = @"取消";

                 

                 self.nameText.enabled = YES;

                 

                 self.phoneText.enabled = YES;

                 

                 self.saveBtn.hidden = NO;

                 //让电话文本框成为第一响应者

                 [self.phoneText becomeFirstResponder];

             }

             else

             {

                 sender.title = @"编辑";

                 

                 self.nameText.enabled = NO;

                 

                 self.phoneText.enabled = NO;

                 

                 self.saveBtn.hidden = YES;

                 

                 //还原数据

                 

                 self.nameText.text = self.contact.name;

                 self.phoneText.text = self.contact.phone;

             }

         

         }

         

         */

 

 

 

        ○ 默认弹出电话文本框,一般修改联系人信息,大部分都是改电话,人性化设计。

        ** [self.phoneText becomeFirstResponder];

 

        ○ 编辑状态的时候,点击取消按钮,会恢复修改的文本框数据,重新回到查看联系人界面。(代码见上)

         */

 

        ○ 实现当文本框中有任何一个没有文字的时候, 禁用"保存"按钮(通过通知的方式)。

        ** 参考登录界面的代码。

 

        ○ 编辑状态的时候,点击保存按钮,更新最新的数据,展示在联系人列表界面。(数据传递)

        ** 在编辑控制器的.h文件中, 添加一个代理协议

        ** 在编辑控制器中, 添加一个delegate属性,类型为id<协议>

        ** 在编辑控制器中, 当点击添加的时候, 判断delegate属性中是否有某个代理方法, 如果有则调用该方法更新数据, 并刷新tableView。

        ** 让联系人列表控制器, 遵守上面的这个代理协议。

        ** 实现协议中的"刷新"数据方法

        ** 在联系人列表控制器的- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender中设置"编辑控制器"的代理为当前联系人列表控制器。

        /** 参考代码:

         

         =============== 编辑控制器中定义协议 =================

         #import <UIKit/UIKit.h>

         @class MJKContact,MJKEditorViewController;

         //定义代理

         

         // 1.定义代理方法

         @protocol MJKEditorViewControllerDelegate <NSObject>

         

         - (void) editorViewControllerWithSaveBtnClick:(MJKEditorViewController *) enitorVC;

         

         @end

         

         @interface MJKEditorViewController : UIViewController

         @property (nonatomic,strong) MJKContact * contact;

         //2.定义代理属性

         @property (nonatomic,weak) id<MJKEditorViewControllerDelegate> delegate;

         @end

         =============== 编辑控制器中定义协议 =================

         

         

         

         

         

         

 

         

         

         =============== 编辑控制器中点"保存"按钮时调用协议的方法 =================

         

         //点击保存按钮的事件

         - (IBAction)saveBtnClick:(id)sender

         {

             //修改传过来的联系人对象属性(姓名 和 电话)

             self.contact.name = self.nameText.text;

             self.contact.phone = self.phoneText.text;

             

             //在合适的时机调用代理

             if ([self.delegate respondsToSelector:@selector(editorViewControllerWithSaveBtnClick:)])

             {

                 [self.delegate editorViewControllerWithSaveBtnClick:self];

             }

             

             //返回上一界面

             [self.navigationController popViewControllerAnimated:YES];

         }

         

         

         =============== 编辑控制器中点"保存"按钮时调用协议的方法 =================

         

 

         

         

         

         =============== 联系人控制器实现协议中的方法 =================

         

         // 编辑完毕后更新数据和tableView

         - (void)editorViewControllerWithSaveBtnClick:(MJKEditorViewController *)enitorVC

         {

             //刷新联系人列表

             [self.tableView reloadData];

         }

         

         =============== 联系人控制器实现协议中的方法 =================

         

 

         

         

         =============== 在联系人控制器中, 跳转到编辑控制器之前设置代理 =================

         

         - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {

             // Get the new view controller using [segue destinationViewController].

             // Pass the selected object to the new view controller.

             

             //获取目标控制器

             UIViewController * vc = segue.destinationViewController;

             //判断目标控制器是否是添加联系人控制器

             if([vc isKindOfClass:[MJKAddContactViewController class]])

             {

             

                 MJKAddContactViewController * addVC = (MJKAddContactViewController *) vc;

                 //1.设置代理

                 addVC.delegate = self;

             }

             else if ([vc isKindOfClass:[MJKEditorViewController class]])//如果是编辑联系人界面 对应的操作

             {

                 MJKEditorViewController * editorVC = (MJKEditorViewController *) vc;

                 

                 //获取tableView 选中的 indexPth

                 NSIndexPath * indexPth = [self.tableView indexPathForSelectedRow];

                 

                 //通过 indexPth 获取row

                 

                 NSInteger row = indexPth.row;

                 //通过row 在联系人数组里获取选中的联系人

                 

                 MJKContact * con = self.contacts[row];

                 //将获取的联系人给目标控制器的contact属性

                 editorVC.contact = con;

                 

                 //        NSLog(@"----%p",editorVC.contact);

                 

                 //使用代理 --- 1.设置代理

                 editorVC.delegate = self;

             

             }

         }

         

         

         =============== 在联系人控制器中, 跳转到编辑控制器之前设置代理 =================

 

         

         */

 

        ○ 设置联系人列表中的分割线。思路:先设置分割线的样式为none, 然后再通过UIView自定义分割线。

        1> 直接设置tableView的行的分割线。(不是我们想要的效果)

        /** 参考代码:

         

         - (void)viewDidLoad {

             [super viewDidLoad];

             //去掉分割线;

             self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;

         }

 

         */

 

        2> 我们想要的是:有数据的cell下面才有分割线, 没有数据的下面则没有分割线。

        ** 思路: 自己手动写代码来添加。向每个cell中添加一个高度 = 1, 宽度 = 屏幕宽度的UIView。

            1> 设置系统的分割线为None。

            ** 在viewDidLoad中执行: self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;

 

            1.1> 要在自定义 cell 的 awakeFromNib方法中创建分割线 UIView, 因为这个 Cell 是从缓存池中获取的, 所以不会调用 initWithStyle方法。

            1.2> 不要在 storyboard 中的单元格中直接拖拽一个 UIView 作为分割线, 因为那样的话单元格的 style 就变成了 custom 了, 不是系统样式了。

 

            2> 在storyboard的cell中增加一个UIView。

            ** 把UIView放到cell中, 设置UIView的x = 0, y = cell的高度, 背景色为浅灰色。

 

            3> 注意要在自定义 cell 的 layoutSubviews 方法中设置分割线的 frame, 这样才最准确。

            ** 重写layoutSubviews方法的时候, 一定要调用父类的layoutSubviews方法。

         

         

 

    5> 导航控制器

        ○ 通过导航控制器管理上面的4个界面(4个控制器)

 

3.分析项目框架(项目第一步)

    1> 有导航条,而且需要进行跳转,项目框架采取导航控制器(UINavigationController)。

 

4.分析项目开发方式:采用纯代码还是storyboard.

    1> 界面都是固定死,而且界面比较少,用storyboard开发,能快速进行界面之间的跳转。

 

5.搭建项目框架

    1> 程序一启动就会加载storyboard中箭头所指向的控制器("初始化控制器", "Initial View Controller"),箭头所指向的控制器换成导航控制器。

 

6.搭建登录界面,完善登录界面功能

    1> 程序一启动,就是登录界面,因此导航控制器的根控制器就是登录控制器。

    2> 处理登录按钮细节,当文本框都有内容的时候,才允许登录按钮点击。

    • 代理能监听文本框的内容,不能实时监控文本框的内容改变,不能采取代理方式。

    • 只能监听上一次输入,不能监听当前输入文本框内容。

    • 通知也能监听文本框的内容,文本框的内容一改变,文本框就会发出通知。

    • UITextField继承UIControll,可以addTarget监听。

    3> 处理开关按钮细节

    • 当打开自动登录,自动打开记住密码开关

    • 当打开记住密码,自动打开自动登录开关

    4> 点击登录按钮,跳转到联系人界面

 

7. 搭建联系人界面

    1> 继承UITableViewController

    2> 注销功能

    • 弹出UIActionSheet,destructiveButtonTitle一般显示比较危险的操作标题。

    • 监听UIActionSheet按钮点击,通过代理。

    • 按钮角标从上往下,依次拯救,最上面按钮角标为0.

    • 点击确定按钮,回到登录界面

 

8. 完善登录功能

    1> 点击登录不应该自动跳转,而且需要判断账号和密码是否正确。

    2> 连线的方式不对,应该控制器之间连线,控制器之间连线,就不知道什么时候做跳转,只知道从哪个控制器跳转到另外一个控制器。这种方式,需要手动执行跳转。PPT演示segue。

    3> 登录业务逻辑分析:

    • 如果账号和密码输入正确,就直接跳转到下一个界面,不需要提示用户是否输入正确,跳转界面是最好的提示,手动执行segue,必须用来源控制器调用performSegueWithIdentifier,否则会报错,而且segue标识必须正确,否则也会报错。

    • 如果账号和密码输入错误,就提示用户输入错误,用UIAlertView不会自动取消提示框,一般用第三方框架提示MBProgressHud。

    • 最好模仿网络处理延迟,2秒后判断是否输入正确。用GCD延迟

    • 延迟这段时间,最好搞个蒙版提示用户正在干嘛,蒙版的作用就是2秒内提示用户,而且不允许用户与界面交互,做其他不好的操作。2秒后,就自动销毁。

 

    4> 登录成功后,需要把登录账户传递给联系人控制器,显示到导航条的标题上

        • 顺传:上一个控制器传递给下一个控制器

        • 思路:

        • 第一步:有属性接收登录账户,联系人控制器的navgationItem.title接收这个属性

        • 第二步:拿到联系人控制器给他的navgationItem.title这个属性赋值,把账户传递给他。

        • 第三步:怎么拿到联系人控制器,通过segue对象,segue对象的目的控制器就是下一级控制器联系人控制器。segue怎么拿?不能自己创建一个联系人控制器,应该拿到storyboard帮我们创建的联系人控制器.

             1> 分析performSegueWithIdentifier方法底层做的事情。

             2> 通过prepareForSegue这个方法拿。

             3> prepareForSegue什么时候调用:在执行segue后,跳转之前调用

             4> prepareForSegue方法作用:在跳转之前,做一些准备工作,一般给跳转的下一个控制器传值。

9. 搭建添加联系人界面

    1> 点击添加按钮,进入添加联系人界面

    2> 默认添加按钮不允许点击,跟登录界面做一样的判断

    3> 默认弹出姓名文本框,人性化设计

    4> 点击添加,把数据传到联系人界面,展示出来。

        • 回传:下一个控制器,传递给上一个控制器

        • 第一种方式:

          1.给添加控制器添加一个联系人控制器的属性,拿到联系人控制器,才能做事情。

          2.添加控制器怎么拿到联系人控制器?在联系人控制器跳转到添加控制器之前,给添加控制器的联系人控制器属性赋值。

          3.耦合性太强,设计不好,两个控制器太依赖了

        • 第二种方式:

          1.通过代理

          2.谁作为添加联系人的代理:联系人控制器,监听添加联系人。

          3.只要添加一个联系人,就通知联系人控制器

          4.什么时候给添加联系人的代理赋值:在联系人控制器跳转到添加控制器之前,给添加控制器的代理属性赋值。

 

10.总结控制器之间的传值

    顺传:上一级控制器传递给下一级控制器,直接能拿到下一级控制器,拿到之后,做什么事情都行。

    回传:下一级控制器传递给上一级控制器,用代理。上一级控制器作为下一级的代理,监听下一级控制器的事情。

 

11.完善联系人界面

    1> 搞个数组,保存所有联系人,因为不只添加一个联系人。

    2> 实现tableView的数据源方法,展示联系人界面。

    3> 监听到添加联系人的时候,就把联系人添加到数组,然后刷新表格。

 

12.搭建编辑联系人界面

    1> 默认文本框不允许交互

    2> 默认保存按钮是隐藏的。

    3> 当点击编辑按钮,文本框允许交互,并且默认弹出电话的键盘

    4> 编辑状态的时候,点击取消按钮,会恢复修改的文本框数据,重新回到查看联系人界面。

    • 模型里面的数据一直没有改变,给文本框重新赋值原来的属性就OK了。

    5> 编辑状态的时候,点击保存按钮,更新最新的数据,展示在联系人列表界面。

    • 更新模型的数据,然后直接刷新联系人控制器的界面就好。

    • 原因:编辑控制器的模型就是联系人控制器选中cell的模型,只需要那模型里面的属性更新下就好了。

 

13.处理tableView分割线

    1> tableView中有些分割线没必要显示,只有有数据才需要显示

    2> 怎么取消分割线?进入头文件搜separator

    • 首先肯定会去找cell头文件,有没有提供一个属性,取消分割线,没有。

    • 然后去tableView头文件里找,有没有提供一个属性,取消分割线,

    3> 发现整个tableView都没有分割线了,有数据的cell需要分割线,怎么做?自己添加分割线。

    • 第一种方式,把分割线添加到tableView上,太麻烦了,每个分割线的位置不好计算

    • 第二种方式,给cell添加分割线,分割线的位置计算比较简单。

    4> 自定义cell,系统自带的不好用,不能添加分割线,分割线添加到contentView上。

    • 封装思想:自己的东西自己管理,以后开发有同样的,直接拿过去就好了,最好提供一个快速创建对象的类方法。提供模型,重写模型的set方法,传递模型过来,就给控件赋值。

    • 分割线的位置,在哪设置比较准确?一创建的时候设置是不准确的,cell的位置和尺寸是由tableView决定的,因此不能在awakeFromNib里设置。

        1. awakeFromNib什么时候调用?xib加载完成的时候调用

        2. awakeFromNib的作用:从控件从xib加载完成之后,做一些初始化操作。

        3. 在layoutSubViews设置尺寸。

 

14.数据存储(数据持久化)

    1> 介绍iOS数据存储的5种方式

    2> 介绍应用沙盒(应用程序的文件夹)

    • 如何找到应用沙盒的路径?首先需要显示隐藏文件。

    • 点击前往->个人->资源库->Application Support->iPhone Simulator->7.1->里面全是应用沙盒

    3> 应用沙盒怎么多文件夹保存,在哪个文件夹。介绍沙盒里的每一个文件夹。

 

15.plist存储

    • 把一些系统自带的OC对象生成pilst文件存储起来。

    1> 了解数据存储:数据存储一般有两个操作,一个存,一个取。拖两个按钮,一个用来存,一个用来取

    2> plist存储原理:

    • 只要有writeToFile的对象,就能进行plist存储,调用writeToFile就能自动生成plist格式的文件。

    • 一般常用的Foundation对象都有这个方法,数组,字典,字符串等

    3> 如何写入到沙盒,需要获取沙盒路径。

    • 获取Documents路径

    • 拼接文件名,因为数据是写入到文件中,不是写入到文件夹中。路径之间通过/分开的,为了避免自己写/,会用stringByAppendingPathCompent,自动在文件夹与文件之间添加/。

    4> 如何读取,存储是什么类型存储,读取出来也是什么类型,直接用存储的类型,解析文件就好,用ContentsOfFile解析。

    5> 注意plist存储,不能存储自定义对象,会失败的。

 

16> 偏好设置

    1> 什么是偏好设置存储:就是保存一些基本的信息,账号,密码,状态。

    2> 偏好设置原理:不需要关心文件名,直接通过NSUserDefaults操作,默认就存到偏好设置里面了。

    • 通过NSUserDefaults就能直接访问软件的偏好设置(Library/Preferences)

    3> 怎么利用偏好设置存储?利用NSUserDefaults调用setObject:forKey存储。

    • 偏好设置底层实现原理:底层其实就是利用一个字典,存储一些键值对。

    • 偏好设置好处:能快速存储一些键值对,如果用字典去存储,还需要获取文件名比较麻烦。

    • 偏好设置坏处:不能及时存储,需要做同步操作,把内存中的数据同步到硬盘上。

    4> 怎么利用偏好设置读取?和字典一样,根据刚刚存储的Key读取。

 

17> 自定义对象归档(归档:数据存储)

    1> 自定义对象如何归档:用NSKeyedArchiver,调用archiveRootObject:toFile:方法,需要传一个对象,自定义一个对象,传进去。

    •  会报错,说对象没有encodeWithCoder方法,说明归档的时候默认会调用这个方法,去实现这个方法。

    • 默认打不出encodeWithCoder,必须遵守NSCoding协议才能实现这个方法。

    • encodeWithCoder什么时候调用:对象归档时候调用

    • encodeWithCoder作用:告诉系统对象里的哪些属性需要归档,怎么去归档,根据一个key去归档,目的就是以后取的时候,也根据这个key去取数据。

    2> 自定义对象如何解档:用NSKeyedUnarchiver,调用unarchiveObjectWithFile方法,需要传一个文件名。

    • 会报错,说对象没有initWithCoder方法,说明解档的时候默认会调用这个方法,去实现这个方法。

    • initWithCoder什么时候调用:对象解档时候调用

    • initWithCoder作用:告诉系统对象里的哪些属性需要解档,怎么去解档,根据之前存储的key去解档

    • initWithCoder是一个初始化方法,需要先初始化父类的,但是不能调用[super initWithCoder:],因为父类NSObject没有遵守NSCoding协议。

    3> initWithCoder什么时候需要调用[super initWithCoder:]

    • initWithCoder原理:只要解析文件就会调用,xib,storyboard都是文件,因此只要解析这两个文件,就会调用initWithCoder。

    • 因此如果在storyboard使用自定义view,重写initWithCoder方法,一定要调用[super initWithCoder:],因为只有系统才知道怎么解析storyboard,如果没有调用,就解析不了这个文件。