No4 私人通讯录

一 搭建界面

  • 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

    • 按住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底层实现步骤为:

    • 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];

十 主流框架

这里写图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章