UI加强(一)之程序启动过程分析和navigationController(私人通讯录)

 

1.程序启动过程细节印象笔记中可以找到

1.1小结一下程序启动过程:

程序运行会首先进入程序的入口,也就是main.m方法,然后main函数中执行了如下图所示的函数,这里要注意一下UIApplicationMain函数中干了几件事,

第一件事是开启了消息循环,用来监测系统事件,把系统事件传递给代理对象去解决,第二件事是包含了四个参数,第一个和第二个不用管,第三个参数指定

这个参数必须是UIApplication类或者其子类,如果为nil默认是UIApplication类

第四个参数指定一个代理类,这里特别注意一下我是可以修改的,如下图的下图所示:

 

上图中顺序是:先创建一个UIWindow,然后创建一个UIViewController控制器,该控制器有一个view属性,要想显示这个view必须把view添加到UIWindow上

这张图要看的是self.window.rootViewController = one;这行代码,这行代码的意思其实就是把控制器的view添加到window上.

重点来了:程序启动过程之UIWindow的显示:

有storyBoard的时候:

程序是什么启动的呢?(根据苹果官方文档翻译可知)

step1:当程序启动的时候,会首先创建一个UIWindow

step2:会加载最主要的storyboard并且创建箭头所指的控制器

step3:把箭头所指的控制器作为window的根控制器,并且让window显示在屏幕上.

三步全部走完说明程序启动完毕,这个时候会调用didFinishLaunching这个方法,可以在这个方法中打印window来验证上述三步,

即证明确实已经实例化出来了window只不过是偷偷地我们没看到罢了.

没有storyboard的时候:

很原始的时候,即很久之前我们写代码是没有storyboard的,那么程序是怎么启动的呢?

step1:程序启动完毕后会调用didFinishLaunching方法

step2:在这个方法中我们就要自己手动创建UIWindow,然后创建UIWindow的根控制器

step3:让window成为主window并可见

 

如下图是最权威的程序启动过程:

 1.2UIView的创建

创建过程如下图:

 

step1:首先会到对应的控制器中找有没有loadView方法,如果有,哪怕没有实现都不会去别的地方加载view,所以优先去loadView方法中创建view

step2:如果没有loadView方法,会找对应的storyboard(注意,这个storyboard可以是自己创建的也可以是系统的main.storyboard.看有没有自定义),有就根据storyboard创建

step3:如果没有storyboard,会看在APPDelegate方法中有没有用xib来创建控制器如下图:有就根据nibname所对应的xib去创建view

然后如上绿色图所示一步步来.

1.3:控制器的生命周期 

首先我想先对viewDidLoad这个方法特别说明一下:

一般理解是viewDidLoad这个方法是view加载完毕后就调用,会第一个调用这个方法(比viewWillAppear早调用)

深层次的理解是:view加载完毕后,还没来得及显示view时会调用这个方法,并且这个方法是属于延迟加载的,这句话是什么意思呢?

延迟加载和懒加载差不多是指用到的时候才加载,当有one控制器和two控制器两个控制器,one控制器是根控制器,并且我们是点击one控制器上的按钮发生界面跳转,

这种情况下,程序启动肯定是只显示one控制器的view的,two控制器并没有显示,也就是two控制器中的viewDidLoad方法没有调用,当点击按钮跳转界面后调用了two控制器中

的viewDidLoad方法,这就属于延迟加载.

1.3.1:控制器接收到内存警告会干什么事:

 

注意:首先出现内存警告的时候系统会最先调用AppDelegate方法中的代理方法,

- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application{

}

代理接收到内存警告紧接着会通知窗口,窗口会通知它的根控制器,根控制器再通知他的子控制器,

子控制器就会调用

#pragma mark -- 接收到内存警告的时候控制器会调用的方法
- (void)didReceiveMemoryWarning{
    [super didReceiveMemoryWarning];
    
}

 

这个方法,就会如上图苹果官方文档所写的那样先判断控制器上是不是创建了一个view,如果创建了会问是否可以release这个view

(这句话很有意思,)那么问题来了当栈里有两个控制器one和two,当前显示的是two控制器的时候,此时one控制器的view是用不着的,当出现内存警告的时候就会

把one控制器的viewrelease掉,当返回one控制器的时候,这个时候我们需要显示one控制器的view,而此时已经被释放掉了,那么one控制器就会重新创建一个view出来.

 

这里要稍微注意一下以前忽略的小问题:

 

one控制器中点击按钮push到two控制器上,我们知道对于控制器来说存在了一个压栈操作,即two压到one上,但是需要注意的是:此时显示的是two控制器的view

那么one控制器的view是被two覆盖在下面了吗?答案:不是的

对于view来说,push操作后,one控制器的view是被暂时搁置一旁了,它和add一个控制器不一样,所以不要理解偏差

它的view层级关系是这样的:window --> 导航控制器的view --> two控制器的view

注意一下:一个已经被废弃掉的方法:

#pragma mark -- view即将被销毁
- (void)viewWillUnload{
    [super viewWillUnload];
}

这个方法是可以拿来清空数据用的,因为如果出现内存警告,这个控制器的view被销毁了,但view上的数据还是保留着的,所以可以用这个来清空数据.

 下面的方法是控制器生命周期方法,了解一下知道什么时候被调用就可以了.

 

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController
#pragma mark -- view加载完毕后调用
- (void)viewDidLoad {
    
    [super viewDidLoad];
}
#pragma mark -- view即将显示到window上
- (void)viewWillAppear:(BOOL)animated{
    
    [super viewWillAppear:YES];
}
#pragma mark -- view已经显示完毕调用(已经显示到窗口)
- (void)viewDidAppear:(BOOL)animated{
    
    [super viewDidAppear:YES];
}
#pragma mark -- view即将消失调用(即将看不见)
- (void)viewWillDisappear:(BOOL)animated{
    
    [super viewWillDisappear:YES];
}
#pragma mark -- view消失完毕后调用(完全看不见)
- (void)viewDidDisappear:(BOOL)animated{
    
    [super viewDidDisappear:YES];
}
#pragma mark -- view即将被销毁
- (void)viewWillUnload{
    [super viewWillUnload];
}
#pragma mark -- view已经被销毁
- (void)viewDidUnload{
    [super viewDidUnload];
}

#pragma mark -- 接收到内存警告的时候控制器会调用的方法
- (void)didReceiveMemoryWarning{
    [super didReceiveMemoryWarning];
    
}
@end

 1.4:有个小问题注意一下,以免以后犯低级错误;

一个view添加了一个oneView,接着又在view上添加了twoView,因为添加的顺序而造成层级关系,即twoView是添加到了oneView上的,注意和push的区别.

 

 

 

2.navigationController

首先什么是navigationController?

navigationController是一个特殊的控制器,是用来管理其他的控制器的

通俗的理解来说就是,navigationController是一个仓库或者说是一个栈的管理者,负责别的控制器的压栈和出栈操作.

常见方法:

常见属性操作:

 导航控制器是如何管理子控制器的?

 

 

如上图所示:控制器push的时候是以压栈的形式把控制器放到一个栈里面去的,记住:控制器是控制器,控制器的view是控制器的view,控制器的view没有所谓的压栈,

如上图,two控制器进栈后,one控制器的view从导航控制器的view上移下来,但并么有销毁,因为one控制器还存在于栈中,two控制器的view就显示出来了,此时如果查看

视图,是看不到one控制器的view的.two控制器此时是栈顶控制器

注意:当栈顶控制器从栈中移除时,系统就会把控制器从内存中销毁,此时two控制器的view也就被销毁了

 

接下来肯定有疑问就是UITabBarController是怎么管理他的子控制器的?

 UITabBarController是没有所谓的压栈操作的,

 

 

 

 如上图所示:我点击UITabBar上不同的UITabBarButton的时候,点击第一个即Vc1控制器,就会显示Vc1的view,点击第二个VC2的控制器,会先把VC1的控制器的view移下去,

但并没有销毁,因为控制器没有销毁,除非出现内存警告,再把VC2的view显示出来.

 

 

 

 

 

私人通讯录小结:

1.插曲:实时监听textfield变化的两种方法:

1)注意:UItextField继承自UIcontrol,UIcontrol是可以添加按钮监听点击事件的即(addtarget事件),而按钮监听点击事件又可以实时监听UItextField的变化

故在设置用户名和密码的时候,可以用到.

2)可以用通知,这个通知是系统发出的,只需要添加监听者对象即可,注意通知名:

UITextFieldTextDidChangeNotification
- (void)setupAddUI{
    //1.关闭自动联想
    [_nameTextField setAutocorrectionType:UITextAutocorrectionTypeNo];
    
//    //2.实时监听textfield的变化,不能用代理,要用添加监听事件的方法
//    [_nameTextField addTarget:self action:@selector(textChanged:) forControlEvents:UIControlEventEditingChanged];
//    [_phoneNumTextField addTarget:self action:@selector(textChanged:) forControlEvents:UIControlEventEditingChanged];
    //2.也可以用通知来实时监听textfield的变化
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(valueChanged) name:UITextFieldTextDidChangeNotification object:nil];
    
    
}
//- (void)textChanged:(UITextField *)textField{
//    _saveBtn.enabled = _nameTextField.text.length != 0 && _phoneNumTextField.text.length==11;
//}
- (void)valueChanged{
    _saveBtn.enabled = _nameTextField.text.length != 0 && _phoneNumTextField.text.length != 0;
}

 

注意:[self.view endEditing:YES];该方法可以将键盘隐藏,即失去第一响应者的身份.

注意:界面跳转有手动segue和自动segue操作:

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

这个方法.

自动segue就是按住control键,点击控件,连线push

手动segue就是按住control键,点击控制器(最上面的黄点).连线push

区别是:自动segue无论什么条件都会执行跳转界面操作

手动segue必须在满足一定条件后再push到目标控制器

如下代码所示:当我需要判断用户名和密码都正确时,才执行界面跳转操作时,我就必须写手动segue操作.

注意:手动跳转代码的时候必须要用方法:[self performSegueWithIdentifier:@"LoginToList" sender:nil];

不能用push方法,因为手动segue,是根据segue的identifier标识符执行目标控制器的识别跳转的,这个segue是

连接两个控制器的桥梁.

- (IBAction)didClickLoginBtn:(id)sender {
    //让键盘失去响应(隐藏键盘)
    [self.view endEditing:YES];
    //执行跳转代码(手动segue)
    if ([_nameField.text isEqualToString:@"a"] && [_passwordField.text isEqualToString:@"a"]) {
        
        [self performSegueWithIdentifier:@"LoginToList" sender:nil];
    }
}

坑爹了吧:tableview没有认真练,导致一个问题没有解决:就是什么时候需要注册一个class类或者注册一个nib?

答:小结:注意下面这个方法的作用:

//2.获取重用cell

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseId forIndexPath:indexPath]; 

苹果官方推荐用这个带有indexPath参数的方法来加载cell,而这个方法必须要注册一个class类或者注册一个nib,或者当我们加载原型cell的时候

给原型cell设置重用标识符,反过来,当我们使用原型cell的时候必须用这个方法,不能用不带indexPath参数的方法,因为系统只会走这个方法,如果不写就会崩溃,

当我们需要使用xib加载cell时,我们可以用带indexPath也可以不用,但注意:当我们用这个方法时,必须要注册nib,否则运行时会提示我们注册nib.

#pragma mark -- 数据源代理方法
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
    return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    return self.dataArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    //1.定义重用标识符
    static NSString *reuseId = @"list";
    //2.获取重用cell
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseId forIndexPath:indexPath];
}

3.关于界面跳转的传值                                     

                         

 

如上面两张图所示:当我点击保存按钮,想把数据传到右边的tableview上面的cell上,

思路:这里我用到的是代理的方法,如代码所示:

在左边的控制器(这里我命名为AddViewController)的.h文件中设置协议,在.m文件中实现代理方法,

//
//  AddViewController.h
//  通讯录小练习第一波
//
//  Created by 曹魏 on 2016/11/20.
//  Copyright © 2016年 itcast. All rights reserved.
//

#import <UIKit/UIKit.h>
@class AddViewController,ListModel;
#pragma mark -- 协议
@protocol AddViewControllerDelegate <NSObject>
//代理方法
- (void)addViewController:(AddViewController *)addController addFinishAdd:(ListModel *)listModel;
@end
@interface AddViewController : UIViewController
//id类型的成员属性
@property (nonatomic,weak)id<AddViewControllerDelegate>delegate;
@end
- (IBAction)saveBtn:(id)sender {
    //1.让键盘失去响应
    [self.view endEditing:YES];
    //2.回到上一个控制器
    [self.navigationController popViewControllerAnimated:YES];
    //3.传递数据
    ListModel *listModel = [[ListModel alloc]init];
    listModel.name = _nameTextField.text;
    listModel.number = _phoneNumTextField.text;
    //4.代理方法
    if ([self.delegate respondsToSelector:@selector(addViewController:addFinishAdd:)]) {
        [self.delegate addViewController:self addFinishAdd:listModel];
    }
    
    
    
}

注意,我需要设置代理对象为右边的控制器(这里我称之为ListViewController),但在哪个方法里设置,由于我要想设置AddViewController 的代理对象,

我必须要先获取到AddViewController,而此时有个很好的方法:

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

这个方法是界面跳转时,无论是手动segue还是自动segue跳转都会调用的一个方法,通过segue获取目标控制器,如下代码所示设置代理对象:

#pragma mark -- 无论是手动segue还是自动segue都会调用这个方法,目的是获取目标控制器
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
    AddViewController *addController = segue.destinationViewController;
    //设置目标控制器的代理对象
    addController.delegate = self;
}

注意思路的过度:我该怎样实现代理方法呢,才能让cell展现出来我所传递的数据?

#pragma mark -- 数据源代理方法
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
    return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    return self.dataArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    //1.定义重用标识符
//    static NSString *reuseId = @"list";
    //2.获取重用cell
    ListTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseId forIndexPath:indexPath];
    cell.listModel = self.dataArray[indexPath.row];
    
    
    return cell;
}

#pragma mark -- 实现添加控制器的代理方法
- (void)addViewController:(AddViewController *)addController addFinishAdd:(ListModel *)listModel{
    //添加模型到可变数组中
    [self.dataArray addObject:listModel];
    //刷新tableview
    [self.tableView reloadData];
}

由于,在AddViewController中我已经给模型中的属性赋值了,这里我把模型存到数组中,然后刷新tableview后,就会重新走一下tableview的数据源方法,这个时候

模型数组是有值的,就会显示出我们想要看到的cell数据了.

4.有一个很屌的发现:

//执行跳转代码,这个方法是手动跳转下一个控制器的方法,(和自动segue的push操作等价,但不能用push,别犯迷)

这个方法有个参数sender,那么是干嘛用的呢?这个方法其实和另一个方法是相关联的,确切的说是有着脐带关系,

就是这个方法出现下一个方法肯定有用

    [self performSegueWithIdentifier:@"ListToEdit" sender:listModel];

 //没错,就是下面这个方法,上面已经提到过,无论是手动segue还是自动segue都会调用这个方法,而sender这个参数

如果不为nil,就会传递到下面这个方法中的sender.

#pragma mark -- 无论是手动segue还是自动segue都会调用这个方法,目的是获取目标控制器

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

4.1.小发现:智能显示tableView的分割线的方法:

 

// 干掉不该显示的分割线
    self.tableView.tableFooterView = [UIView new];

 

虽然不理解为什么可以这样,但确实解决了我们的需求,同样的我们可能还会想到设置分割线的样式

[self.tableView setSeparatorStyle:UITableViewCellSeparatorStyleNone];

但这种是直接把分割线给清除掉了,并不能在我们需要的时候显示出来,所以第一种方式神奇的出现了.

更多问题可以参考博客:

 

http://blog.csdn.net/u013705509/article/details/51087097

 4.2:MBProgressHUD的使用

这个是除了masonry之外的第二个第三方框架,所以要会用,这个是干嘛用的呢?什么时候会用到?

一般我们写一些登录界面的时候会有动画,这个框架就很全面的包含一些基本的动画.

 

 5.插曲:顺传和逆传

顺传:从源控制器向目标控制器传递数据,只需要在目标控制器定义一个属性,一般就是model(因为一般都是用模型存储数据),就可以传递.

逆传:从目标控制器向源控制器传递数据,这样可以有多种选择:代理,通知和block.

 接下来看看这个block是怎么操作的:

上代码:

#pragma Mark -- 点击保存按钮
- (IBAction)didClickSaveBtn:(id)sender {
    //1.修改model中的数据
    _listModel.name = _nameText.text;
    _listModel.number = _phoneText.text;
    //2.点击保存按钮回到联系人列表界面
    [self.navigationController popViewControllerAnimated:YES];
    //3.传递数据,进行刷新,block传值来了
    /*
     谁要传值,谁就创建block
     
     
     */
    if (_tempBlock) {
        _tempBlock();
    }
    
}

点击保存按钮,目的是修改输入框的内容,即把修改过的内容赋值给model,改变model的值,但是,我的目的是让list控制器显示出来我编辑后的内容的,所以,我就必须

想办法点击保存按钮执行刷新tableview的操作,而显然我在当前控制器是做不到的,所以我想到了用block存一个代码块:[self.tableview reloadData];

#pragma mark -- 无论是手动segue还是自动segue都会调用这个方法,目的是获取目标控制器
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
    if ([segue.identifier isEqualToString:@"ListToAdd"]) {
        //跳入添加联系人界面
        AddViewController *addController = segue.destinationViewController;
        //设置目标控制器的代理对象
        addController.delegate = self;
    }else{
        //跳入编辑界面
        EditViewController *editController = segue.destinationViewController;
        //传递数据到编辑控制器
        editController.listModel = sender;
        //为block赋值(注意,当界面跳转时,下面这行代码是不执行的,只有_tempBlock();执行时才会执行赋值代码块的操作)
      注意注意:我说的不执行的代码是block里面的这行代码:
[self.tableView reloadData];
      而不是说没有给block赋值,这里确实是给block赋值了,但是里面的代码是没有执行的,仅仅是赋值哦😯!!!
        editController.tempBlock = ^{
            [self.tableView reloadData];
        };
    }
}

 

 

posted @ 2016-11-18 16:12  忆缘晨风  阅读(183)  评论(0编辑  收藏  举报