一 搭建界面
- LoginViewController
// 设置账号文本框的代理,不能及时监听文本框的内容改变
_accountField.delegate = self;
/ 拦截用户的输入,每次用户想要修改文本框的内容的时候就会调用
// 这个方法不能及时监听文本框的内容改变
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
return YES;
}
注意:上述方法不能及时监听文本框内容改变,只能拦截用户输入
如果需要及时监听文本框内容改变,用下述方法:
- (void)viewDidLoad {
[super viewDidLoad];
// 及时监听文本框的内容改变
[_accountField addTarget:self action:@selector(textChange) forControlEvents:UIControlEventEditingChanged];
[_pwdField addTarget:self action:@selector(textChange) forControlEvents:UIControlEventEditingChanged];
}
- 登陆逻辑
// 账号或者密码文本框改变的时候都会调用
- (void)textChange
{
// 同时有内容,允许登录按钮点击
_loginBtn.enabled = _accountField.text.length && _pwdField.text.length;
}
二 自动登陆逻辑实现
- 自动登陆 = YES时,打开记住密码
// 当自动登录开关状态改变的时候调用
- (IBAction)autoLoginChange:(UISwitch *)sender {
// 当勾选自定登录,必须勾选记住密码
// 判断下自动登录开关是否打开
if (sender.on == YES) { // 打开自动登录
// 打开记住密码
[_rmbPwdSwitch setOn:YES animated:YES];
}
}
- 记住密码 = NO时,自动登录关闭
// 当记住密码开关状态改变的时候调用
- (IBAction)rmbPwdChange:(UISwitch *)sender {
// 当记住密码关闭,把自动登录关闭
if (sender.on == NO) { // 关闭记住密码
[_autoLoginSwitch setOn:NO animated:YES];
}
}
三 Segue
- 什么是Segue
- Storyboard上每一根用来界面跳转的线,都是一个UIStoryboardSegue对象(简称Segue)
- Segue的属性
- 每一个Segue对象,都有3个属性
// 唯一标识
@property (nonatomic, readonly) NSString *identifier;
// 来源控制器
@property (nonatomic, readonly) id sourceViewController;
// 目标控制器
@property (nonatomic, readonly) id destinationViewController;
Segue的类型
- 根据Segue的执行(跳转)时刻,Segue可以分为2大类型
- 自动型:点击某个控件后(比如按钮),自动执行Segue,自动完成界面跳转
- 手动型:需要通过写代码手动执行Segue,才能完成界面跳转
- 根据Segue的执行(跳转)时刻,Segue可以分为2大类型
自动型Segue
- 按住Control键,直接从控件拖线到目标控制器
- 点击“登录”按钮后,就会自动跳转到右边的控制器
- 如果点击某个控件后,不需要做任何判断,一定要跳转到下一个界面,建议使用“自动型Segue”
手动型Segue
- 按住Control键,从来源控制器拖线到目标控制器
- 手动型的Segue需要设置一个标识
在恰当的时刻,使用perform方法执行对应的Segue
[self performSegueWithIdentifier:@"login2contacts" sender:nil]; // Segue必须由来源控制器来执行,也就是说,这个perform方法必须由来源控制器来调用
如果点击某个控件后,需要做一些判断,也就是说:满足一定条件后才跳转到下一个界面,建议使用“手动型Segue”
Sender参数的传递
[self performSegueWithIdentifier:@“login2contacts” sender:@“jack”];
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender;
- 登陆跳转实现
- (IBAction)login:(id)sender {
// 默认网络延迟,目的不要这么快就跳转
// 弹出蒙版
[MBProgressHUD showMessage:@"正在登录ing..."];
// 1.延迟判断
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 隐藏蒙版
[MBProgressHUD hideHUD];
// 判断下账号和密码是否正确
if ([_accountField.text isEqualToString:@"xmg"] && [_pwdField.text isEqualToString:@"123"]) {
// 输入正确,直接进入联系人界面
// 跳转到下一个控制器
// 手动执行segue进行跳转
[self performSegueWithIdentifier:@"login2Contact" sender:nil];
}else{
// 输入错误,提示用户账号或者密码错误
[MBProgressHUD showError:@"用户账号或者密码错误"];
}
});
}
四 数据传值-顺传
顺传:来源控制器传值给目的控制器,上一个控制器传值给下一个控制器
数据传值步骤
- 1.首先接收方要有一个属性接收数据
- 2.必须要在传递方拿到接收方,给它传递数据
利用performSegueWithIdentifier:方法可以执行某个Segue,完成界面跳转
- 1>根据identifier去storyboard中找到对应的线,新建UIStoryboardSegue对象
- 设置Segue对象的sourceViewController(来源控制器)
- 新建并且设置Segue对象的destinationViewController(目标控制器)
2>调用sourceViewController的下面方法,做一些跳转前的准备工作并且传入创建好的Segue对象
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender; // 这个sender是当初performSegueWithIdentifier:sender:中传入的sender
3>调用Segue对象的- (void)perform;方法开始执行界面跳转操作
- 如果segue的style是push
- 取得sourceViewController所在的UINavigationController
- 调用UINavigationController的push方法将destinationViewController压入栈中,完成跳转
- 如果segue的style是modal
- 调用sourceViewController的presentViewController方法将destinationViewController展示出来
- 如果segue的style是push
- 1>根据identifier去storyboard中找到对应的线,新建UIStoryboardSegue对象
总结上述,Segue底层实现步骤为:
- 1.去storyboard中查找login2Contact这个线,如果找到,就创建segue对象
- 2.设置segue对象的来源控制器,并且创建segue的目的控制器
- 3.通知来源控制器,准备跳转,调用来源控制器prepareForSegue
- 4.执行[segue perform]进行界面的跳转
// 跳转之前调用,通知来源控制器需要跳转
// 作用:一般是用来数据传值
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
ContactViewController *destVc = segue.destinationViewController;
destVc.account = _accountField.text;
}
五 搭建添加界面
- 联系人ContactViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 设置导航条左边
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"注销" style:UIBarButtonItemStyleDone target:self action:@selector(logout)];
// 点击注销的时候调用
- (void)logout
{
UIActionSheet *sheet = [[UIActionSheet alloc] initWithTitle:@"确定要注销?" delegate:self cancelButtonTitle:@"取消" destructiveButtonTitle:@"注销" otherButtonTitles:nil, nil];
[sheet showInView:self.view];
}
// 点击UIActionSheet上的按钮的时候调用
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
NSLog(@"%ld",buttonIndex);
// 判断下是否点击了注销按钮
// 回到上一个界面
if (buttonIndex == 0) {
[self.navigationController popViewControllerAnimated:YES];
}
}
- 键盘会跟随 view 跳转弹出,需要在LoginViewController登陆代码中执行
// 退下键盘
[self.view endEditing:YES];
- 添加联系人AddViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 及时监听文本框的内容改变
[_nameField addTarget:self action:@selector(textChange) forControlEvents:UIControlEventEditingChanged];
[_phoneField addTarget:self action:@selector(textChange) forControlEvents:UIControlEventEditingChanged];
}
// 账号或者密码文本框改变的时候都会调用
- (void)textChange
{
// 同时有内容,允许添加按钮点击
_addBtn.enabled = _nameField.text.length && _phoneField.text.length;
}
- 控制器完全显示,弹出键盘
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// 主动弹出姓名文本框键盘
[_nameField becomeFirstResponder];
}
六 逆传
逆传
- 1.首先传递方,要拥有接收方
- 2.当需要传递的时候,直接拿到接收方就可以传递
数据传递通过模型XMGContact
@interface XMGContact : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *phone;
+ (instancetype)contactWithName:(NSString *)name phone:(NSString *)phone;
@end
+ (instancetype)contactWithName:(NSString *)name phone:(NSString *)phone
{
XMGContact *c = [[self alloc] init];
c.name = name;
c.phone = phone;
return c;
}
// 点击添加的时候
- (IBAction)add:(id)sender {
// 把内容包装成联系人模型
XMGContact *c = [XMGContact contactWithName:_nameField.text phone:_phoneField.text];
// 把添加的内容传递给联系人界面
_contactVc.contact = c;
// 回到上一个界面(联系人界面)
[self.navigationController popViewControllerAnimated:YES];
}
- 拿到添加控制器
// 跳转添加控制器之前调用
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
AddViewController *addVc = segue.destinationViewController;
addVc.contactVc = self;
}
- (void)setContact:(XMGContact *)contact
{
_contact = contact;
NSLog(@"%@",contact.name);
}
六 代理解耦
- ContactViewController太过依赖其他控制器,通过代理来解耦
- 添加代理协议
// 定义了添加控制器的协议
@protocol AddViewControllerDelegate <NSObject>
@optional
// 当添加新的联系人,通知代理接收这个联系人
- (void)addViewControlle:(XMGAddViewController *)addVc didAddContact:(XMGContact *)contact;
@interface AddViewController : UIViewController
// 定义了一个代理属性
@property (nonatomic, weak) id<AddViewControllerDelegate> delegate;
@end
- 代理方法
- (IBAction)add:(id)sender {
// 把内容包装成联系人模型
XMGContact *c = [XMGContact contactWithName:_nameField.text phone:_phoneField.text];
// 把添加的内容传递给联系人界面
// 通知代理接收数据
// _delegate = contactVc
if ([_delegate respondsToSelector:@selector(addViewControlle:didAddContact:)]) {
[_delegate addViewControlle:self didAddContact:c];
}
// 回到上一个界面(联系人界面)
[self.navigationController popViewControllerAnimated:YES];
}
- ContactViewController遵守AddViewControllerDelegate
#pragma mark - AddViewControllerDelegate方法
// 当添加界面点击添加按钮的时候调用,会把新增加的联系人模型传递
- (void)addViewControlle:(AddViewController *)addVc didAddContact:(XMGContact *)contact
{
NSLog(@"%@",contact.name);
}
拿到添加控制器,设置添加控制器的代理为联系人控制器,联系人控制器监听添加控制器点击,接受添加控制器的数据
- 逆传:一般是通过代理
- 1.首先在传递方声明代理协议,并且定义代理属性
- 2.在接收方跳转到传递方之前的时候,让接收方成为传递方的代理
- 3.在恰当的时候,通知代理接收数据
七 联系人展示控制器
- ContactViewController
@property (nonatomic, strong) NSMutableArray *contacts;
- (NSMutableArray *)contacts
{
if (_contacts == nil) {
_contacts = [NSMutableArray array];
}
return _contacts;
}
#pragma mark - AddViewControllerDelegate方法
// 当添加界面点击添加按钮的时候调用,会把新增加的联系人模型传递
// 只要添加了新的联系人就会调用
// 每次都保存到数组,并且刷新tableView展示最新的数据
- (void)addViewControlle:(AddViewController *)addVc didAddContact:(XMGContact *)contact
{
[self.contacts addObject:contact];
// 注意:一定要刷新下数据源方法
[self.tableView reloadData];
}
- tableView数据源方法
// 返回tableView有多少行
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.contacts.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *ID = @"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:ID];
}
// 获取对应的模型
XMGContact *c = self.contacts[indexPath.row];
cell.textLabel.text = c.name;
cell.detailTextLabel.text = c.phone;
return cell;
}
- cell 分割线设置,有数据才有分割线,没有数据就没有分割线
// 实现tableView中有数据才有分割线,没有数据就没有分割线
self.tableView.tableFooterView = [[UIView alloc] init];
八 编辑界面逻辑
- ContactViewController
// 跳转添加控制器之前调用
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// 获取目的控制器
UIViewController *destVc = segue.destinationViewController;
if ([destVc isKindOfClass:[AddViewController class]]) { // 目的控制器是添加控制器
AddViewController *addVc = (AddViewController *)destVc;
addVc.delegate = self;
}else{ // 进入编辑界面
// 把当前tableView选中的模型传递给编辑界面
EditViewController *editVc = (EditViewController *)destVc;
// 获取当前tableView选中的模型
// 获取当前ableView选中的角标
NSIndexPath *selIndex = [self.tableView indexPathForSelectedRow];
editVc.contact = self.contacts[selIndex.row];
editVc.delegate = self;
}
}
- 选中的模型接收数据
// 选中的模型
@property (nonatomic, strong) XMGContact *contact;
- EditViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 控制器之间传值的时候,不能重写set方法给子控件赋值,一般都在viewDidLoad里面给子控件赋值
// 在控制器的view加载完成的时候才去给子控件赋值
// 显示在文本框上
_nameField.text = _contact.name;
_phoneField.text = _contact.phone;
// 及时监听文本框的内容改变
[_nameField addTarget:self action:@selector(textChange) forControlEvents:UIControlEventEditingChanged];
[_phoneField addTarget:self action:@selector(textChange) forControlEvents:UIControlEventEditingChanged];
[self textChange];
}
- 点击编辑,修改标题为取消,保存按钮和键盘框逻辑处理
// 点击编辑的时候调用
- (IBAction)edit:(UIBarButtonItem *)sender {
NSLog(@"%s",__func__);
if ([sender.title isEqualToString:@"取消"]) { // 点击了取消
// 修改标题为编辑
sender.title = @"编辑";
// 让文本框不允许使用
_nameField.enabled = NO;
_phoneField.enabled = NO;
// 让保存隐藏
_saveBtn.hidden = YES;
// 还原文本框的内容
_nameField.text = _contact.name;
_phoneField.text = _contact.phone;
}else{
// 修改标题为取消
sender.title = @"取消";
// 让文本框允许使用
_nameField.enabled = YES;
_phoneField.enabled = YES;
// 弹出电话文本框的键盘
[_phoneField becomeFirstResponder];
// 让保存显示
_saveBtn.hidden = NO;
}
}
- 点击保存按钮,通知联系人控制器刷新数据,通过代理实现
// 点击保存按钮的时候调用
- (IBAction)save:(id)sender {
// 更新模型数据
_contact.name = _nameField.text;
_contact.phone = _phoneField.text;
// 通知联系人控制器刷新数据
if ([_delegate respondsToSelector:@selector(editVcDidUpdateContact:)]) {
// 通知代理刷新数据
[_delegate editVcDidUpdateContact:self];
}
// 回到联系控制器
[self.navigationController popViewControllerAnimated:YES];
}
- 代理协议
@class XMGContact,EditViewController;
@protocol EditViewControllerDelegate <NSObject>
@optional
// 通知代理更新数据
- (void)editVcDidUpdateContact:(EditViewController *)editVc;
@end
九 UITabBarController
- UITabBarController的使用步骤
- 初始化UITabBarController
- 设置UIWindow的rootViewController为UITabBarController
- 根据具体情况,通过addChildViewController方法添加对应个数的子控制器
UITabBarController添加控制器的方式有2种
- 添加单个子控制器
- (void)addChildViewController:(UIViewController *)childController;
- 添加单个子控制器
设置子控制器数组
@property(nonatomic,copy) NSArray *viewControllers;
如果UITabBarController有N个子控制器,那么UITabBar内部就会有N个UITabBarButton作为子控件
- UITabBarButton里面显示什么内容,由对应子控制器的tabBarItem属性决定
// 标题文字
@property(nonatomic,copy) NSString *title;
// 图标
@property(nonatomic,retain) UIImage *image;
// 选中时的图标
@property(nonatomic,retain) UIImage *selectedImage;
// 提醒数字
@property(nonatomic,copy) NSString *badgeValue;
- 代码示例
// 添加子控制器
// 默认tabBarVc显示第0个子控制器的view
UIViewController *vc = [[UIViewController alloc] init];
vc.view.backgroundColor = [UIColor redColor];
// 设置这个控制器对应按钮的标题
vc.tabBarItem.title = @"消息";
// tabBar控制器默认会选中第0个按钮
// 在iOS7之后默认会把UITabBar上的选中的按钮图片给渲染成蓝色
// 设置这个控制器对应按钮的图片
vc.tabBarItem.image = [UIImage imageNamed:@"tab_recent_nor"];
// 设置提醒数字
vc.tabBarItem.badgeValue = @"1000";
[tabBarVc addChildViewController:vc];