ui进阶第三天,私人通讯录的创建(元宵节快乐)
2016-02-22 21:44
519 查看
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(textChanged) name:UITextFieldTextDidChangeNotification object:self.txtLoginId];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textChanged) name:UITextFieldTextDidChangeNotification object:self.txtPwd];
// UIKIT_EXTERN NSString *const UITextFieldTextDidBeginEditingNotification;
// UIKIT_EXTERN NSString *const UITextFieldTextDidEndEditingNotification;
// UIKIT_EXTERN NSString *const UITextFieldTextDidChangeNotification;
}
// 用户登录名、密码文本框发生改变事件
- (void)textChanged
{
// //
常规代码
// if (self.txtLoginId.text.length > 0 && self.txtPwd.text.length > 0 ) {
// self.btnLogin.enabled = YES;
// } else {
// self.btnLogin.enabled = NO;
// }
// 优化代码
self.btnLogin.enabled = (self.txtLoginId.text.length > 0 && self.txtPwd.text.length > 0);
}
**** 注意通过通知的方式注册事件,
需要在dealloc方法中移除 ****
// 移除通知监听
- (void)dealloc
{
[[NSNotificationCenter defaultCenter]removeObserver:self];
}
**** 注意通过通知的方式注册事件,
需要在dealloc方法中移除 ****
*/
○ 开关细节注意:当打开自动登录,记住密码也会打开,当关闭记住密码,自动登录也会打开。
思路:
监听开关的"Value Changed"事件。
/** 参考代码:
// 记住密码开关,
值改变事件。
- (IBAction)switchPwdValueChanged {
if (self.switchPwd.isOn == NO) {
//
非动画方式
//self.switchAutoLogin.isOn = NO;
//
动画方式
[self.switchAutoLogin setOn:NO animated:YES];
}
}
// 自动登录开关,
值改变事件
- (IBAction)switchAutoLoginValueChanged {
if (self.switchAutoLogin.isOn) {
[self.switchPwd setOn:YES 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)btnLoginClick {
if ([self.txtLoginId.text isEqualToString:@"steve"] && [self.txtPwd.text isEqualToString:@"123"]) {
//
登录成功
[self performSegueWithIdentifier:@"login2ContactsList" sender:nil];
} else {
//
登录失败
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"登录失败" message:@"用户名或密码错误!" delegate:nil
cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
//
显示提示对话框
[alertView show];
// ================
使用MBProgressHUD框架,
显示错误提示 ================
if ([self.txtLoginId.text isEqualToString:@"steve"] && [self.txtPwd.text isEqualToString:@"123"]) {
//
登录成功
[self performSegueWithIdentifier:@"login2ContactsList" sender:nil];
} else {
// showError方法、showSucess方法等会自动消失
[MBProgressHUD showError:@"登录失败!"];
}
}
}
*/
5>
使用MBProgressHUD框架改进操作提示对话框(代码见上面)。
6>
登录时, 显示"等待指示器"2秒,
模拟登录。
* 使用dispatch_after。示例代码如下:
/** 参考代码:
// 登录成功以后:
1>
提示用户等待
[MBProgressHUD showMessage:@"正在登录..."];
2>
延迟2秒操作
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUD];
[self performSegueWithIdentifier:@"segueLoginVC2ContactsVC" sender:nil];
});
================================
完成参考代码 ================================
// 登录操作
- (IBAction)btnLoginClick {
//
使用第三方框架, 美化
if ([self.txtLoginId.text isEqualToString:@"steve"] && [self.txtPwd.text isEqualToString:@"123"]) {
//
登录成功
[MBProgressHUD showMessage:@"正在登录..."];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUD];
[self performSegueWithIdentifier:@"login2ContactsList" sender:nil];
});
} else {
[MBProgressHUD showError:@"登录失败!"];
}
}
*/
2>
联系人列表界面
○ 联系人列表界面导航条的标题跟账号有关系(控制器之间传值,把登录界面的账号传递过来)
** 登录成功以后,
在联系人界面的导航栏中, 显示当前登录人的账号"Steve的联系人"
** 思路:
1>
实现控制器的- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender方法。
2>
在该方法中获取目标控制器, 然后把值直接传递给目标控制器的导航栏的title属性。
** 该方法会在Segue即将跳转之前自动执行。
** 这个方法一般用作跳转之前的准备工作、输出传递等。
/** 参考代码:
// 在控制器跳转之前执行
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
SteveZContactsListTableViewController *destVc = segue.destinationViewController;
destVc.navigationItem.title = [NSString stringWithFormat:@"%@的联系人", self.txtLoginId.text];
//
注意也可以直接设置控制器的标题
destVc.title = [NSString stringWithFormat:@"%@的联系人", self.txtLoginId.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"对话框,
用户点击的时哪个按钮,
如果点击的是"注销",
则弹出当前控制器。
/**
参考代码:
// 注销按钮的单击事件
- (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];
}
}
*/
○ 添加按钮进入添加联系人界面
** 通过按住"+"按钮,
拖线到"SteveZAddContactViewController"控制器
3>
添加联系人界面
○ 默认焦点停留在姓名文本框中,并且叫出键盘。不需要用户点击文本框弹出键盘,人性化设计。
** 让姓名文本框成为第一响应者。
** 使用viewDidAppear事件。
/** 参考代码:
// 注意:
使用viewDidAppear事件,
不要使用viewDidLoad,
否则是键盘先出来。
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
//让姓名文本框成为第一响应者,
默认焦点停留在姓名文本框中,并且叫出键盘。不需要用户点击文本框弹出键盘,人性化设计。
[self.txtName becomeFirstResponder];
}
*/
○ 设置"添加"按钮一个开始是"禁用"状态。等到姓名和电话都输入内容以后才变为"可用"状态。
** 为文本框注册文本改变事件。通过通知。与设置"登录"按钮是否可用的代码相同。
○ 修改"SteveZAddContactViewController"控制器界面的状态栏的最左侧按钮为"返回"。
** 通过设置上一个控制器的navigationItem.backBarButtonItem.title =
@"返回";
** 或者直接在storyboard中,
找到上一个控制器的"导航栏",
修改Back Button属性的值。
○ 点击添加按钮,回到联系人列表界面,并且把数据显示到联系人列表界面(控制器之间传值)
** 为"添加"按钮注册单击事件。
** 在"添加"事件中:
1>
关闭当前控制器
2>
把数据回传给上一个控制器,
通过代理实现。
** 思路:
1>
在"添加联系人"控制器中,
增加一个属性,
这个属性的类型就是"SteveZContactsListTableViewController"类型。
2>
然后在"SteveZContactsListTableViewController(联系人列表)"控制器中声明一个方法,
接收两个参数(姓名、电话)
3>
同时在"SteveZContactsListTableViewController(联系人列表)"控制器中,
实现- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender方法,
用来设置目标控制器的这个属性为self。
3>
在"添加联系人"控制器中,
需要传值的时候, 调用上一个控制器的这个方法即可。
4>
把这个属性的数据类型改成id<代理协议>的类型,
就变成代理了。
** 如果回传的数据很多的话,
在代理方法中通过模型实现。
/** 参考代码:
**************
方式一(不推荐, 因为耦合度太高) **************
1. 联系人列表控制器:
//
为目标控制器中的属性设置值。
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
SteveZAddContactViewController *targetVc = segue.destinationViewController;
targetVc.contactsList = self;
}
//
用来传递数据的方法
- (void)addNewContactWithName:(NSString *)name andPhone:(NSString *)phone
{
NSLog(@"--%@---%@", name, phone);
}
2. 增加联系人控制器中的"添加"按钮的方法
//
增加联系人
- (IBAction)addContact {
//
关闭当前控制器
[self.navigationController popViewControllerAnimated:YES];
NSString *name = self.txtName.text;
NSString *phone = self.txtPhoneNumber.text;
//
调用上一个控制器的方法来实现传值。
[self.contactsList addNewContactWithName:name andPhone:phone];
}
**************
方式二(推荐, 使用代理) **************
1. 联系人列表控制器
*****
遵守协议, 实现协议的方法 *****
// 遵守协议
@interface SteveZContactsListTableViewController () <SteveZAddContactViewControllerDelegate>
@end
// 实现协议的方法
- (void)AddContactView:(SteveZAddContactViewController *)addVc DidAddContactWithName:(NSString *)name andPhone:(NSString *)phone
{
//
实现添加联系人协议中的方法
- (void)AddContactView:(SteveZAddContactViewController *)addVc DidAddContactWithName:(NSString *)name andPhone:(NSString *)phone
{
// 1.
获取模型数据
[self.contacts addObject:[[SteveZContact alloc] initWithName:name andPhone:phone]];
// 2.
重新刷新表格
[self.tableView reloadData];
}
}
======================
关于刷新表格数据 ======================
1. 需要在联系人列表控制器中增加一个contacts属性。
参考代码:
@property (nonatomic, strong) NSMutableArray *contacts;
- (NSMutableArray *)contacts
{
if (_contacts == nil) {
_contacts = [NSMutableArray array];
}
return _contacts;
}
2. 实现数据源方法:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 1.
创建cell
NSString *cellId = @"contact";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:@"contact"];
}
// 2.
设置cell的数据
SteveZContact *model = self.contacts[indexPath.row];
cell.textLabel.text = model.name;
cell.detailTextLabel.text = model.phone;
//
设置每个单元格最右侧的箭头
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
// 3.
返回cell
return cell;
}
======================
关于刷新表格数据 ======================
*****
遵守协议, 实现协议的方法 *****
2. 增加联系人控制器
*****
定义协议, 增加delegate属性。 *****
@class SteveZAddContactViewController;
@protocol SteveZAddContactViewControllerDelegate <NSObject>
//- (void)addNewContactWithName:(NSString *)name andPhone:(NSString *)phone;
- (void)AddContactView:(SteveZAddContactViewController *) addVc DidAddContactWithName:(NSString *)name andPhone:(NSString *)phone;
@end
@interface SteveZAddContactViewController : UIViewController
@property (nonatomic, strong) id<SteveZAddContactViewControllerDelegate> delegate;
@end
*****
定义协议, 增加delegate属性。 *****
*****
在"添加联系人"方法中调用协议的方法 *****
// 增加联系人
- (IBAction)addContact {
//
关闭当前控制器
[self.navigationController popViewControllerAnimated:YES];
NSString *name = self.txtName.text;
NSString *phone = self.txtPhoneNumber.text;
//
调用上一个控制器的方法来实现传值。
if ([self.delegate respondsToSelector:@selector(AddContactView:DidAddContactWithName:andPhone:)]) {
[self.delegate AddContactView:self DidAddContactWithName:name andPhone:phone];
}
}
*****
在"添加联系人"方法中调用协议的方法 *****
*/
4>
编辑联系人界面
○ 点击cell,进入编辑联系人界面
** 思路:
在"联系人列表"控制器中,
选中Cell单元格,
然后按住control键,
直接拖线到"编辑控制器"。
** 注意:
事实上,
在本例中, 按照如上方式是无法实现控制器跳转的,
因为TableView中的的Cell是通过代码直接动态创建的,
不是加载的storyboard中的cell。【解决:为storyboard中的Cell设置一个identifier的值为"contact",
这样的话动态创建Cell的那段代码就可以根据storyboard中的这个cell创建了】
/** 参考代码:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 1.
创建cell
NSString *cellId = @"contact";
// 下面这句代码,
首先从"缓存池"中查找是否有id为@"contact"的cell,
如果没有则优先去storyboard中查找是否有identifier为@"contact"的cell,
如果有则根据storyboard中的cell创建一个出来。
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
////////////////////////////
注释掉这段代码即可 ////////////////////////////
// if (cell == nil) {
// cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:@"contact"];
// }
////////////////////////////
注释掉这段代码即可 ////////////////////////////
// 2.
设置cell的数据
SteveZContact *model = self.contacts[indexPath.row];
cell.textLabel.text = model.name;
cell.detailTextLabel.text = model.phone;
// 设置每个单元格最右侧的箭头
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
// 3.
返回cell
return cell;
}
*/
** 报错:
"reason: '-[SteveZEditViewController setDelegate:]: unrecognized selector sent to instance 0x7feb51fc3b20'"
【原因:
在"联系人列表"控制器中,
有两个segue对象(控制器跳转连线),
无论任何一个segue被执行,
在跳转之前都会执行- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender方法。】而现在这个方法中的代码如下(在这段代码中,
没有判断是哪个segue的目标控制器,
就直接调用delegate属性进行赋值了,
所以报错了。):
/**
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
SteveZAddContactViewController *targetVc = segue.destinationViewController;
targetVc.delegate = self;
}
*/
【解决:
在prepareForSegue中判断当前执行的时哪个segue,
或者判断当前的目标控制器是否为我们要得目标控制器,
然后再执行相应代码】
/** 参考代码:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// 根据segue的标识来判断。
// [segue.identifier isEqualToString:@"login2contacts"];
// 根据目标控制器的类型来判断
SteveZAddContactViewController *targetVc = segue.destinationViewController;
if ([targetVc isKindOfClass:[SteveZAddContactViewController class]]) {
targetVc.delegate = self;
}
}
*/
○ 设置"编辑"控制器的导航栏的标题和"编辑/取消"按钮
/** 参考代码:
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationItem.title = @"编辑联系人";
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"编辑" style:UIBarButtonItemStylePlain target:self action:@selector(beginEdit)];
}
*/
○ 默认保存按钮是隐藏的。
** 设置"保存"按钮为Hidden。
○ 默认文本框不能交互,一开始是查看联系人。
** 设置文本框的Enable属性为NO。
○ 当现实"编辑"界面的时候,
将当前点击的单元格的数据传递过来。
** 思路:
拦截- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender方法,
在该方法中判断如果"目标控制器"是"编辑控制器",
则传递相应数据。
/** 参考代码:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// 根据segue的标识来判断。
// [segue.identifier isEqualToString:@"login2contacts"];
// 根据目标控制器的类型来判断
id targetVc = segue.destinationViewController;
// 判断目标控制器的类型
if ([targetVc isKindOfClass:[SteveZAddContactViewController class]]) {
((SteveZAddContactViewController *)targetVc).delegate = self;
} else if ([targetVc isKindOfClass:[SteveZEditViewController class]]) {
//
获取当前选中的行号
NSInteger rowIndex = [self.tableView indexPathForSelectedRow].row;
//将当前选中行的model数据传递过去
((SteveZEditViewController *)targetVc).contact = self.contacts[rowIndex];
}
}
*** 提醒:
不要使用:
重写SteveZEditViewControlle的setContact方法,
将model中的数据设置到控件上。这样做无效,
因为在设置数据的时候控制器的view还没有创建好。所以应该把设置数据的代码写到viewDidLoad中。
/** 参考代码:
- (void)viewDidLoad {
[super viewDidLoad];
//
设置界面上控件的数据
self.txtName.text = self.contact.name;
self.txtPhone.text = self.contact.phone;
}
*/
*/
○ 当点击编辑按钮的时候,文本框才允许交互,并且保存按钮默认不能点击
** 为编辑按钮注册单击事件,
当单击的时候, 设置两个文本框可用、按钮显示并且可用、电话号码文本框成为第一响应者。
/**
参考代码:
// 开始编辑
- (void)beginEdit
{
self.txtName.enabled = YES;
self.txtPhone.enabled = YES;
self.btnSave.hidden = NO;
self.btnSave.enabled = YES;
[self.txtPhone becomeFirstResponder];
}
*/
○ 默认弹出电话文本框,一般修改联系人信息,大部分都是改电话,人性化设计。
** [self.txtPhone becomeFirstResponder];
○ 编辑状态的时候,点击取消按钮,会恢复修改的文本框数据,重新回到查看联系人界面。
/**
参考代码:
// 开始编辑
- (void)beginEdit
{
if ([self.navigationItem.rightBarButtonItem.title isEqualToString:@"编辑"]) {
// 点击了"编辑"
self.navigationItem.rightBarButtonItem.title = @"取消";
self.txtName.enabled = YES;
self.txtPhone.enabled = YES;
self.btnSave.hidden = NO;
self.btnSave.enabled = YES;
// 设置焦点默认在"电话文本框"上
[self.txtPhone becomeFirstResponder];
} else {
// 点击了"取消"
self.navigationItem.rightBarButtonItem.title = @"编辑";
self.txtName.enabled = NO;
self.txtPhone.enabled = NO;
self.btnSave.hidden = YES;
self.btnSave.enabled = NO;
// 重新设置model数据到控件上
self.txtName.text = self.contact.name;
self.txtPhone.text = self.contact.phone;
// 关闭键盘
[self.view endEditing:YES];
// 回到上一个界面
[self.navigationController popViewControllerAnimated:YES];
}
** 注意:
以上的else中的部分代码,可以省略。只保留[self.navigationController popViewControllerAnimated:YES];代码即可,因为当当前控制器pop以后,当前控制器,视图就都销毁了。下次创建的又是新的视图,不会保留上次的状态了。
}
*/
○ 实现当文本框中有任何一个没有文字的时候,
禁用"保存"按钮(通过通知的方式)。
** 参考登录界面的代码。
○ 编辑状态的时候,点击保存按钮,更新最新的数据,展示在联系人列表界面。(数据传递)
** 在编辑控制器的.h文件中,
添加一个代理协议
** 在编辑控制器中,
添加一个delegate属性,类型为id<协议>
** 在编辑控制器中,
当点击添加的时候, 判断delegate属性中是否有某个代理方法,
如果有则调用该方法更新数据,
并刷新tableView。
** 让联系人列表控制器,
遵守上面的这个代理协议。
** 实现协议中的"刷新"数据方法
** 在联系人列表控制器的- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender中设置"编辑控制器"的代理为当前联系人列表控制器。
/**
参考代码:
===============
编辑控制器中定义协议 =================
@class SteveZEditViewController, SteveZContact;
@protocol SteveZEditViewControllerDelegate <NSObject>
- (void)editViewController:(SteveZEditViewController *)editViewController didSaveContact:(SteveZContact *)model;
@end
@interface SteveZEditViewController : UIViewController
@property (nonatomic, strong) SteveZContact *contact;
@property (nonatomic, weak) id<SteveZEditViewControllerDelegate> delegate;
@end
===============
编辑控制器中定义协议 =================
===============
编辑控制器中点"保存"按钮时调用协议的方法 =================
// 单击保存按钮
- (IBAction)btnSaveClick {
// 1. 关闭当前控制器
[self.navigationController popViewControllerAnimated:YES];
// 2.调用代理方法
if ([self.delegate respondsToSelector:@selector(editViewController:diDSaveContact:)]) {
self.contact.name = self.txtName.text;
self.contact.phone = self.txtPhone.text;
[self.delegate editViewController:self diDSaveContact:self.contact];
}
}
===============
编辑控制器中点"保存"按钮时调用协议的方法 =================
===============
联系人控制器遵守协议 =================
@interface SteveZContactsListTableViewController () <SteveZEditViewControllerDelegate>
@end
===============
联系人控制器遵守协议 =================
===============
联系人控制器实现协议中的方法 =================
// 编辑完毕后更新数据和tableView
- (void)editViewController:(SteveZEditViewController *)editViewController diDSaveContact:(SteveZContact *)model
{
[self.tableView reloadData];
}
===============
联系人控制器实现协议中的方法 =================
===============
在联系人控制器中, 跳转到编辑控制器之前设置代理 =================
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// 根据segue的标识来判断。
// [segue.identifier isEqualToString:@"login2contacts"];
// 根据目标控制器的类型来判断
id targetVc = segue.destinationViewController;
// 判断目标控制器的类型
if ([targetVc isKindOfClass:[SteveZAddContactViewController class]]) {
((SteveZAddContactViewController *)targetVc).delegate = self;
} else if ([targetVc isKindOfClass:[SteveZEditViewController class]]) {
// 获取当前选中的行号
NSInteger rowIndex = [self.tableView indexPathForSelectedRow].row;
//将当前选中行的model数据传递过去
((SteveZEditViewController *)targetVc).contact = self.contacts[rowIndex];
// 设置代理
((SteveZEditViewController *)targetVc).delegate = self;
}
}
===============
在联系人控制器中, 跳转到编辑控制器之前设置代理 =================
*/
○ 设置联系人列表中的分割线。思路:先设置分割线的样式为none,
然后再通过UIView自定义分割线。
1>
直接设置tableView的行的分割线。(不是我们想要的效果)
/**
参考代码:
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLineEtched;
}
*/
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,如果没有调用,就解析不了这个文件。
• 学习目标:控制器跳转,控制器之间传值,数据存储
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(textChanged) name:UITextFieldTextDidChangeNotification object:self.txtLoginId];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textChanged) name:UITextFieldTextDidChangeNotification object:self.txtPwd];
// UIKIT_EXTERN NSString *const UITextFieldTextDidBeginEditingNotification;
// UIKIT_EXTERN NSString *const UITextFieldTextDidEndEditingNotification;
// UIKIT_EXTERN NSString *const UITextFieldTextDidChangeNotification;
}
// 用户登录名、密码文本框发生改变事件
- (void)textChanged
{
// //
常规代码
// if (self.txtLoginId.text.length > 0 && self.txtPwd.text.length > 0 ) {
// self.btnLogin.enabled = YES;
// } else {
// self.btnLogin.enabled = NO;
// }
// 优化代码
self.btnLogin.enabled = (self.txtLoginId.text.length > 0 && self.txtPwd.text.length > 0);
}
**** 注意通过通知的方式注册事件,
需要在dealloc方法中移除 ****
// 移除通知监听
- (void)dealloc
{
[[NSNotificationCenter defaultCenter]removeObserver:self];
}
**** 注意通过通知的方式注册事件,
需要在dealloc方法中移除 ****
*/
○ 开关细节注意:当打开自动登录,记住密码也会打开,当关闭记住密码,自动登录也会打开。
思路:
监听开关的"Value Changed"事件。
/** 参考代码:
// 记住密码开关,
值改变事件。
- (IBAction)switchPwdValueChanged {
if (self.switchPwd.isOn == NO) {
//
非动画方式
//self.switchAutoLogin.isOn = NO;
//
动画方式
[self.switchAutoLogin setOn:NO animated:YES];
}
}
// 自动登录开关,
值改变事件
- (IBAction)switchAutoLoginValueChanged {
if (self.switchAutoLogin.isOn) {
[self.switchPwd setOn:YES 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)btnLoginClick {
if ([self.txtLoginId.text isEqualToString:@"steve"] && [self.txtPwd.text isEqualToString:@"123"]) {
//
登录成功
[self performSegueWithIdentifier:@"login2ContactsList" sender:nil];
} else {
//
登录失败
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"登录失败" message:@"用户名或密码错误!" delegate:nil
cancelButtonTitle:@"确定" otherButtonTitles:nil, nil];
//
显示提示对话框
[alertView show];
// ================
使用MBProgressHUD框架,
显示错误提示 ================
if ([self.txtLoginId.text isEqualToString:@"steve"] && [self.txtPwd.text isEqualToString:@"123"]) {
//
登录成功
[self performSegueWithIdentifier:@"login2ContactsList" sender:nil];
} else {
// showError方法、showSucess方法等会自动消失
[MBProgressHUD showError:@"登录失败!"];
}
}
}
*/
5>
使用MBProgressHUD框架改进操作提示对话框(代码见上面)。
6>
登录时, 显示"等待指示器"2秒,
模拟登录。
* 使用dispatch_after。示例代码如下:
/** 参考代码:
// 登录成功以后:
1>
提示用户等待
[MBProgressHUD showMessage:@"正在登录..."];
2>
延迟2秒操作
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUD];
[self performSegueWithIdentifier:@"segueLoginVC2ContactsVC" sender:nil];
});
================================
完成参考代码 ================================
// 登录操作
- (IBAction)btnLoginClick {
//
使用第三方框架, 美化
if ([self.txtLoginId.text isEqualToString:@"steve"] && [self.txtPwd.text isEqualToString:@"123"]) {
//
登录成功
[MBProgressHUD showMessage:@"正在登录..."];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUD];
[self performSegueWithIdentifier:@"login2ContactsList" sender:nil];
});
} else {
[MBProgressHUD showError:@"登录失败!"];
}
}
*/
2>
联系人列表界面
○ 联系人列表界面导航条的标题跟账号有关系(控制器之间传值,把登录界面的账号传递过来)
** 登录成功以后,
在联系人界面的导航栏中, 显示当前登录人的账号"Steve的联系人"
** 思路:
1>
实现控制器的- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender方法。
2>
在该方法中获取目标控制器, 然后把值直接传递给目标控制器的导航栏的title属性。
** 该方法会在Segue即将跳转之前自动执行。
** 这个方法一般用作跳转之前的准备工作、输出传递等。
/** 参考代码:
// 在控制器跳转之前执行
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
SteveZContactsListTableViewController *destVc = segue.destinationViewController;
destVc.navigationItem.title = [NSString stringWithFormat:@"%@的联系人", self.txtLoginId.text];
//
注意也可以直接设置控制器的标题
destVc.title = [NSString stringWithFormat:@"%@的联系人", self.txtLoginId.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"对话框,
用户点击的时哪个按钮,
如果点击的是"注销",
则弹出当前控制器。
/**
参考代码:
// 注销按钮的单击事件
- (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];
}
}
*/
○ 添加按钮进入添加联系人界面
** 通过按住"+"按钮,
拖线到"SteveZAddContactViewController"控制器
3>
添加联系人界面
○ 默认焦点停留在姓名文本框中,并且叫出键盘。不需要用户点击文本框弹出键盘,人性化设计。
** 让姓名文本框成为第一响应者。
** 使用viewDidAppear事件。
/** 参考代码:
// 注意:
使用viewDidAppear事件,
不要使用viewDidLoad,
否则是键盘先出来。
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
//让姓名文本框成为第一响应者,
默认焦点停留在姓名文本框中,并且叫出键盘。不需要用户点击文本框弹出键盘,人性化设计。
[self.txtName becomeFirstResponder];
}
*/
○ 设置"添加"按钮一个开始是"禁用"状态。等到姓名和电话都输入内容以后才变为"可用"状态。
** 为文本框注册文本改变事件。通过通知。与设置"登录"按钮是否可用的代码相同。
○ 修改"SteveZAddContactViewController"控制器界面的状态栏的最左侧按钮为"返回"。
** 通过设置上一个控制器的navigationItem.backBarButtonItem.title =
@"返回";
** 或者直接在storyboard中,
找到上一个控制器的"导航栏",
修改Back Button属性的值。
○ 点击添加按钮,回到联系人列表界面,并且把数据显示到联系人列表界面(控制器之间传值)
** 为"添加"按钮注册单击事件。
** 在"添加"事件中:
1>
关闭当前控制器
2>
把数据回传给上一个控制器,
通过代理实现。
** 思路:
1>
在"添加联系人"控制器中,
增加一个属性,
这个属性的类型就是"SteveZContactsListTableViewController"类型。
2>
然后在"SteveZContactsListTableViewController(联系人列表)"控制器中声明一个方法,
接收两个参数(姓名、电话)
3>
同时在"SteveZContactsListTableViewController(联系人列表)"控制器中,
实现- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender方法,
用来设置目标控制器的这个属性为self。
3>
在"添加联系人"控制器中,
需要传值的时候, 调用上一个控制器的这个方法即可。
4>
把这个属性的数据类型改成id<代理协议>的类型,
就变成代理了。
** 如果回传的数据很多的话,
在代理方法中通过模型实现。
/** 参考代码:
**************
方式一(不推荐, 因为耦合度太高) **************
1. 联系人列表控制器:
//
为目标控制器中的属性设置值。
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
SteveZAddContactViewController *targetVc = segue.destinationViewController;
targetVc.contactsList = self;
}
//
用来传递数据的方法
- (void)addNewContactWithName:(NSString *)name andPhone:(NSString *)phone
{
NSLog(@"--%@---%@", name, phone);
}
2. 增加联系人控制器中的"添加"按钮的方法
//
增加联系人
- (IBAction)addContact {
//
关闭当前控制器
[self.navigationController popViewControllerAnimated:YES];
NSString *name = self.txtName.text;
NSString *phone = self.txtPhoneNumber.text;
//
调用上一个控制器的方法来实现传值。
[self.contactsList addNewContactWithName:name andPhone:phone];
}
**************
方式二(推荐, 使用代理) **************
1. 联系人列表控制器
*****
遵守协议, 实现协议的方法 *****
// 遵守协议
@interface SteveZContactsListTableViewController () <SteveZAddContactViewControllerDelegate>
@end
// 实现协议的方法
- (void)AddContactView:(SteveZAddContactViewController *)addVc DidAddContactWithName:(NSString *)name andPhone:(NSString *)phone
{
//
实现添加联系人协议中的方法
- (void)AddContactView:(SteveZAddContactViewController *)addVc DidAddContactWithName:(NSString *)name andPhone:(NSString *)phone
{
// 1.
获取模型数据
[self.contacts addObject:[[SteveZContact alloc] initWithName:name andPhone:phone]];
// 2.
重新刷新表格
[self.tableView reloadData];
}
}
======================
关于刷新表格数据 ======================
1. 需要在联系人列表控制器中增加一个contacts属性。
参考代码:
@property (nonatomic, strong) NSMutableArray *contacts;
- (NSMutableArray *)contacts
{
if (_contacts == nil) {
_contacts = [NSMutableArray array];
}
return _contacts;
}
2. 实现数据源方法:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 1.
创建cell
NSString *cellId = @"contact";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:@"contact"];
}
// 2.
设置cell的数据
SteveZContact *model = self.contacts[indexPath.row];
cell.textLabel.text = model.name;
cell.detailTextLabel.text = model.phone;
//
设置每个单元格最右侧的箭头
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
// 3.
返回cell
return cell;
}
======================
关于刷新表格数据 ======================
*****
遵守协议, 实现协议的方法 *****
2. 增加联系人控制器
*****
定义协议, 增加delegate属性。 *****
@class SteveZAddContactViewController;
@protocol SteveZAddContactViewControllerDelegate <NSObject>
//- (void)addNewContactWithName:(NSString *)name andPhone:(NSString *)phone;
- (void)AddContactView:(SteveZAddContactViewController *) addVc DidAddContactWithName:(NSString *)name andPhone:(NSString *)phone;
@end
@interface SteveZAddContactViewController : UIViewController
@property (nonatomic, strong) id<SteveZAddContactViewControllerDelegate> delegate;
@end
*****
定义协议, 增加delegate属性。 *****
*****
在"添加联系人"方法中调用协议的方法 *****
// 增加联系人
- (IBAction)addContact {
//
关闭当前控制器
[self.navigationController popViewControllerAnimated:YES];
NSString *name = self.txtName.text;
NSString *phone = self.txtPhoneNumber.text;
//
调用上一个控制器的方法来实现传值。
if ([self.delegate respondsToSelector:@selector(AddContactView:DidAddContactWithName:andPhone:)]) {
[self.delegate AddContactView:self DidAddContactWithName:name andPhone:phone];
}
}
*****
在"添加联系人"方法中调用协议的方法 *****
*/
4>
编辑联系人界面
○ 点击cell,进入编辑联系人界面
** 思路:
在"联系人列表"控制器中,
选中Cell单元格,
然后按住control键,
直接拖线到"编辑控制器"。
** 注意:
事实上,
在本例中, 按照如上方式是无法实现控制器跳转的,
因为TableView中的的Cell是通过代码直接动态创建的,
不是加载的storyboard中的cell。【解决:为storyboard中的Cell设置一个identifier的值为"contact",
这样的话动态创建Cell的那段代码就可以根据storyboard中的这个cell创建了】
/** 参考代码:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 1.
创建cell
NSString *cellId = @"contact";
// 下面这句代码,
首先从"缓存池"中查找是否有id为@"contact"的cell,
如果没有则优先去storyboard中查找是否有identifier为@"contact"的cell,
如果有则根据storyboard中的cell创建一个出来。
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
////////////////////////////
注释掉这段代码即可 ////////////////////////////
// if (cell == nil) {
// cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:@"contact"];
// }
////////////////////////////
注释掉这段代码即可 ////////////////////////////
// 2.
设置cell的数据
SteveZContact *model = self.contacts[indexPath.row];
cell.textLabel.text = model.name;
cell.detailTextLabel.text = model.phone;
// 设置每个单元格最右侧的箭头
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
// 3.
返回cell
return cell;
}
*/
** 报错:
"reason: '-[SteveZEditViewController setDelegate:]: unrecognized selector sent to instance 0x7feb51fc3b20'"
【原因:
在"联系人列表"控制器中,
有两个segue对象(控制器跳转连线),
无论任何一个segue被执行,
在跳转之前都会执行- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender方法。】而现在这个方法中的代码如下(在这段代码中,
没有判断是哪个segue的目标控制器,
就直接调用delegate属性进行赋值了,
所以报错了。):
/**
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
SteveZAddContactViewController *targetVc = segue.destinationViewController;
targetVc.delegate = self;
}
*/
【解决:
在prepareForSegue中判断当前执行的时哪个segue,
或者判断当前的目标控制器是否为我们要得目标控制器,
然后再执行相应代码】
/** 参考代码:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// 根据segue的标识来判断。
// [segue.identifier isEqualToString:@"login2contacts"];
// 根据目标控制器的类型来判断
SteveZAddContactViewController *targetVc = segue.destinationViewController;
if ([targetVc isKindOfClass:[SteveZAddContactViewController class]]) {
targetVc.delegate = self;
}
}
*/
○ 设置"编辑"控制器的导航栏的标题和"编辑/取消"按钮
/** 参考代码:
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationItem.title = @"编辑联系人";
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"编辑" style:UIBarButtonItemStylePlain target:self action:@selector(beginEdit)];
}
*/
○ 默认保存按钮是隐藏的。
** 设置"保存"按钮为Hidden。
○ 默认文本框不能交互,一开始是查看联系人。
** 设置文本框的Enable属性为NO。
○ 当现实"编辑"界面的时候,
将当前点击的单元格的数据传递过来。
** 思路:
拦截- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender方法,
在该方法中判断如果"目标控制器"是"编辑控制器",
则传递相应数据。
/** 参考代码:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// 根据segue的标识来判断。
// [segue.identifier isEqualToString:@"login2contacts"];
// 根据目标控制器的类型来判断
id targetVc = segue.destinationViewController;
// 判断目标控制器的类型
if ([targetVc isKindOfClass:[SteveZAddContactViewController class]]) {
((SteveZAddContactViewController *)targetVc).delegate = self;
} else if ([targetVc isKindOfClass:[SteveZEditViewController class]]) {
//
获取当前选中的行号
NSInteger rowIndex = [self.tableView indexPathForSelectedRow].row;
//将当前选中行的model数据传递过去
((SteveZEditViewController *)targetVc).contact = self.contacts[rowIndex];
}
}
*** 提醒:
不要使用:
重写SteveZEditViewControlle的setContact方法,
将model中的数据设置到控件上。这样做无效,
因为在设置数据的时候控制器的view还没有创建好。所以应该把设置数据的代码写到viewDidLoad中。
/** 参考代码:
- (void)viewDidLoad {
[super viewDidLoad];
//
设置界面上控件的数据
self.txtName.text = self.contact.name;
self.txtPhone.text = self.contact.phone;
}
*/
*/
○ 当点击编辑按钮的时候,文本框才允许交互,并且保存按钮默认不能点击
** 为编辑按钮注册单击事件,
当单击的时候, 设置两个文本框可用、按钮显示并且可用、电话号码文本框成为第一响应者。
/**
参考代码:
// 开始编辑
- (void)beginEdit
{
self.txtName.enabled = YES;
self.txtPhone.enabled = YES;
self.btnSave.hidden = NO;
self.btnSave.enabled = YES;
[self.txtPhone becomeFirstResponder];
}
*/
○ 默认弹出电话文本框,一般修改联系人信息,大部分都是改电话,人性化设计。
** [self.txtPhone becomeFirstResponder];
○ 编辑状态的时候,点击取消按钮,会恢复修改的文本框数据,重新回到查看联系人界面。
/**
参考代码:
// 开始编辑
- (void)beginEdit
{
if ([self.navigationItem.rightBarButtonItem.title isEqualToString:@"编辑"]) {
// 点击了"编辑"
self.navigationItem.rightBarButtonItem.title = @"取消";
self.txtName.enabled = YES;
self.txtPhone.enabled = YES;
self.btnSave.hidden = NO;
self.btnSave.enabled = YES;
// 设置焦点默认在"电话文本框"上
[self.txtPhone becomeFirstResponder];
} else {
// 点击了"取消"
self.navigationItem.rightBarButtonItem.title = @"编辑";
self.txtName.enabled = NO;
self.txtPhone.enabled = NO;
self.btnSave.hidden = YES;
self.btnSave.enabled = NO;
// 重新设置model数据到控件上
self.txtName.text = self.contact.name;
self.txtPhone.text = self.contact.phone;
// 关闭键盘
[self.view endEditing:YES];
// 回到上一个界面
[self.navigationController popViewControllerAnimated:YES];
}
** 注意:
以上的else中的部分代码,可以省略。只保留[self.navigationController popViewControllerAnimated:YES];代码即可,因为当当前控制器pop以后,当前控制器,视图就都销毁了。下次创建的又是新的视图,不会保留上次的状态了。
}
*/
○ 实现当文本框中有任何一个没有文字的时候,
禁用"保存"按钮(通过通知的方式)。
** 参考登录界面的代码。
○ 编辑状态的时候,点击保存按钮,更新最新的数据,展示在联系人列表界面。(数据传递)
** 在编辑控制器的.h文件中,
添加一个代理协议
** 在编辑控制器中,
添加一个delegate属性,类型为id<协议>
** 在编辑控制器中,
当点击添加的时候, 判断delegate属性中是否有某个代理方法,
如果有则调用该方法更新数据,
并刷新tableView。
** 让联系人列表控制器,
遵守上面的这个代理协议。
** 实现协议中的"刷新"数据方法
** 在联系人列表控制器的- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender中设置"编辑控制器"的代理为当前联系人列表控制器。
/**
参考代码:
===============
编辑控制器中定义协议 =================
@class SteveZEditViewController, SteveZContact;
@protocol SteveZEditViewControllerDelegate <NSObject>
- (void)editViewController:(SteveZEditViewController *)editViewController didSaveContact:(SteveZContact *)model;
@end
@interface SteveZEditViewController : UIViewController
@property (nonatomic, strong) SteveZContact *contact;
@property (nonatomic, weak) id<SteveZEditViewControllerDelegate> delegate;
@end
===============
编辑控制器中定义协议 =================
===============
编辑控制器中点"保存"按钮时调用协议的方法 =================
// 单击保存按钮
- (IBAction)btnSaveClick {
// 1. 关闭当前控制器
[self.navigationController popViewControllerAnimated:YES];
// 2.调用代理方法
if ([self.delegate respondsToSelector:@selector(editViewController:diDSaveContact:)]) {
self.contact.name = self.txtName.text;
self.contact.phone = self.txtPhone.text;
[self.delegate editViewController:self diDSaveContact:self.contact];
}
}
===============
编辑控制器中点"保存"按钮时调用协议的方法 =================
===============
联系人控制器遵守协议 =================
@interface SteveZContactsListTableViewController () <SteveZEditViewControllerDelegate>
@end
===============
联系人控制器遵守协议 =================
===============
联系人控制器实现协议中的方法 =================
// 编辑完毕后更新数据和tableView
- (void)editViewController:(SteveZEditViewController *)editViewController diDSaveContact:(SteveZContact *)model
{
[self.tableView reloadData];
}
===============
联系人控制器实现协议中的方法 =================
===============
在联系人控制器中, 跳转到编辑控制器之前设置代理 =================
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// 根据segue的标识来判断。
// [segue.identifier isEqualToString:@"login2contacts"];
// 根据目标控制器的类型来判断
id targetVc = segue.destinationViewController;
// 判断目标控制器的类型
if ([targetVc isKindOfClass:[SteveZAddContactViewController class]]) {
((SteveZAddContactViewController *)targetVc).delegate = self;
} else if ([targetVc isKindOfClass:[SteveZEditViewController class]]) {
// 获取当前选中的行号
NSInteger rowIndex = [self.tableView indexPathForSelectedRow].row;
//将当前选中行的model数据传递过去
((SteveZEditViewController *)targetVc).contact = self.contacts[rowIndex];
// 设置代理
((SteveZEditViewController *)targetVc).delegate = self;
}
}
===============
在联系人控制器中, 跳转到编辑控制器之前设置代理 =================
*/
○ 设置联系人列表中的分割线。思路:先设置分割线的样式为none,
然后再通过UIView自定义分割线。
1>
直接设置tableView的行的分割线。(不是我们想要的效果)
/**
参考代码:
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLineEtched;
}
*/
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,如果没有调用,就解析不了这个文件。
相关文章推荐
- GUI - Web前端开发框架
- 评价ui设计作品好坏的八个标准(界面/交互设计研究)
- 为Yahoo! UI Extensions Grid增加内置的可编辑器
- Cocos2d-x UI开发之文本类使用实例
- 谈谈对jquery ui tabs 的理解
- 在IE 浏览器中使用 jquery的fadeIn() 效果 英文字符字体加粗
- UI Events 用户界面事件
- jquery UI Datepicker时间控件的使用方法(基础版)
- Hallo.js基于jQuery UI所见即所得的Web编辑器
- 基于jQuery UI CSS Framework开发Widget的经验
- jquery ui 1.7 ui.tabs 动态添加与关闭(按钮关闭+双击关闭)
- jQuery UI设置固定日期选择特效代码分享
- Jquery ajax 同步阻塞引起的UI线程阻塞问题
- jQuery UI的Dialog无法提交问题的解决方法
- jQuery ui实现动感的圆角渐变网站导航菜单效果代码
- jquery UI Datepicker时间控件的使用方法(终结版)
- jquery UI Datepicker时间控件的使用方法(加强版)
- 关于jQuery UI 使用心得及技巧
- jquery ui dialog替代confirm实例分析
- jQuery ui 1.7更新小结