爬爬爬之路:UI(十二) 单例 UITabBarController 高级Block的应用

来源:互联网 发布:手机知乎怎么修改答案 编辑:程序博客网 时间:2024/06/01 23:30

单例

Sington

从程序开始到结束就一个对象.

单例不需要释放, 在程序运行结束后会自动释放

单例的写法:

+ (MySington *)sharedHandle {    static MySington *handle = nil;    if (handle == nil) {        handle = [[MySington alloc] init];    }    return handle;}

单例的应用: UIApplication NSUserDefaults

应用场景:

  1. 跨类或者跨界面传值(利用了程序运行期间就一个对象的特点)
  2. 可以封装操作类(数据操作), 将所有的数据操作都写在单例内, 方便查找数据的逻辑问题.
  3. 创建应用程序对象的时候使用单例.

UITabBarController

UITabBarController标签视图控制器

标签视图控制器也是用来管理控制器.
标签视图控制器的高度为49
通常应用的框架如下:

1个TabBarController标签试图控制器 -> 对应多个NavigationController(导航试图控制器) -> 多个(ViewController)视图控制器

常用属性:

  1. 获得被TabBarController管理的控制器
    @property(nonatomic,copy) NSArray *viewControllers;

  2. 获得选中的Controller
    @property(nonatomic,assign) UIViewController *selectedViewController;

  3. 获得选中的ViewControllers的下标(或者说是TabBar上选中的下标)
    @property(nonatomic) NSUInteger selectedIndex;

  4. 当被管理的视图控制器(导航视图控制器)数量超过5个的时候, 会自动把第五个标签变成more选项, 然后把第五个开始的视图控制器会被集合在more标签对应的NavigationController里面.获得More标签对应的NavigationController
    @property(nonatomic,readonly) UINavigationController *moreNavigationController;

  5. 获得系统more标签中的控制器
    @property(nonatomic,copy) NSArray *customizableViewControllers;

核心属性

@property(nonatomic,assign) id<UITabBarControllerDelegate> delegate;

常用方法

带动画的set方法

 - (void)setViewControllers:(NSArray *)viewControllers animated:(BOOL)animated;

UITabBarControllerDelegate

  1. 控制某个标签对应的Controller可选择或者不可选择

    - (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController; 
  2. 选中一个viewController的触发方法

    - (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController;

以下是点击more自带的NavigationController中的Edit按键后触发

  1. 将要点击more标签中的控制器

    - (void)tabBarController:(UITabBarController *)tabBarController willBeginCustomizingViewControllers:(NSArray *)viewControllers;
  2. 将要结束点击触发

    - (void)tabBarController:(UITabBarController *)tabBarController willEndCustomizingViewControllers:(NSArray *)viewControllers changed:(BOOL)changed;
  3. 点击结束触发

    - (void)tabBarController:(UITabBarController *)tabBarController didEndCustomizingViewControllers:(NSArray *)viewControllers changed:(BOOL)changed;

其他

在TabBarController中添加了ViewController的类目, 属性如下

@property(nonatomic,retain) UITabBarItem *tabBarItem;
// Automatically created lazily with the view controller’s title if it’s not set explicitly.

@property(nonatomic,readonly,retain) UITabBarController *tabBarController;
// If the view controller has a tab bar controller as its ancestor, return it. Returns nil otherwise.

当UIViewController对象和其子类对象被TabBarController管理的时候, 这两个属性就被自动赋值了.

UITabBarController的使用方法

将视图控制器添加到标签视图控制器中

第一步

先创建一个标签视图控制器UITabBarController的子类RootTabBarController, 并将其设为self.window的根视图控制器

在AppDelegate.m中

// 将tabBarController作为视图的根视图控制器RootTabBarViewController *rootTBVC = [[RootTabBarViewController alloc] init];self.window.rootViewController = rootTBVC;

第二步

然后在RootTabBarViewController.m的viewDidLoad中:

OneViewController *oneVC = [[OneViewController alloc] init];self.viewControllers = @[oneVC];// 或者利用添加子视图控制器的方法.[self addChildViewController:oneVC];

这样就把一个UIViewController的对象或者其子类对象(比如UINavigationController对象)添加为标签视图控制器管理下的视图控制器了.

给控制器在标签视图控制器上的位置设置标题 和背景图片

// 作为对比, 给视图控制器所在的导航试图控制器添加标题的方法用到oneVC.navigtionItem属性oneVC.tabBarItem.title = @"试图一";oneVC.tabBarItem.image = [UIImage imageNamed:@"37-suitcase"];

由于 系统适配的图片是镂空图, 非镂空图只会显示图片的轮廓.
在没有设置图片的显示模板的时候, 会只显示图片的轮廓. 颜色为蓝色(选中) 和灰色(未选中)
我们可以通过设置图片的显示属性让图片能够显示出它原有的样子
用到的方法:

- (UIImage *)imageWithRenderingMode:(UIImageRenderingMode)renderingMode;// UIImageRederingMode为控制图片显示的方法typedef NS_ENUM(NSInteger, UIImageRenderingMode) {    UIImageRenderingModeAutomatic,    UIImageRenderingModeAlwaysOriginal, 以原有的图片作为模板, 可以显示出图片的原本样子    UIImageRenderingModeAlwaysTemplate,  绘制原来图片的轮廓, 不包括颜色 }// 写法如下:oneVC.tabBarItem.image = [[UIImage imageNamed:@"11"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];// 设置图片以原有的图片作为模板. 就可以显示出原图片模样和颜色

以上是设置视图控制器在TabBarController上的正常状态下的标签图片, 可以设置选中状态下的图片:

oneVC.tabBarItem.selectedImage = [UIImage imageNamed:@"37-suitcase"];

在大多数应用中, 通常是一个标签视图控制器 对应多个导航试图控制器 , 每个导航试图控制器再对应多个试图控制器.

因此UITabBarController上的一个标签通常是对应着一个NavigationController
将一个视图控制器设为一个导航试图控制器的根视图控制器, 再将这个导航试图控制器设为被标签视图控制器所直接管理的视图控制器:

// 创建控制器OneViewController *oneVC = [[OneViewController alloc] init];// 创建导航视图控制器, 将试图控制器设为其根视图控制器UINavigationController *navOne = [[UINavigationController alloc] initWithRootViewController:oneVC];navOne.tabBarItem.title = @"视图一";// 显示按钮的标题(tabBarItem)// 显示图片原有的颜色navOne.tabBarItem.image = [[UIImage imageNamed:@"11"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];navOne.tabBarItem.selectedImage = [UIImage imageNamed:@"37-suitcase"];self.viewControllers = @[navOne];

标签视图控制器和导航视图控制器在应用中管理视图控制器的方式有点不相同.

导航视图控制器添加一个试图控制器为自己管理的时候, 只需要在需要跳转到该页面的时候 在一个视图控制器中利用self.navigationController属性调用 push方法, 系统自动会将下一个界面包含进导航试图控制器的管理.

而标签视图控制器则是需要自己手动将需要被自己管理的导航视图控制器或者视图控制器添加到self.viewControllers属性中.

注意, self.viewControllers 的类型是一个不可变数组, 所以只能写成:

self.viewController = @[(导航)视图控制器1, (导航)视图控制器2, ...];// ps.此处的self指的是RootTabBarController 下无强调处同

当然, 两者也都可以使用[self addChildViewController:xxx];的方法. 不过一般UINavigationController都是需要在跳转界面的时候才会选中将一个新的视图控制器添加为它的子视图, 所以用push比较多.

UITabBarController作为一个管理视图控制器的控件, 可以对其自身的一些属性进行定制, 使得被其管理的试图控制器都具有相同的样式

NavigationController中也有相似的设定, 它用到的属性是UINavigationController的NavigationBar属性

而对应的, 标签视图控制器用到的是UITabBarController的tabBar属性

// 设置bar的颜色 (标签栏的颜色)self.tabBar.barTintColor = [UIColor blueColor];// 设置bar的填充色 (标签栏上标签按键的颜色)self.tabBar.tintColor = [UIColor greenColor];// 设置bar的背景图片self.tabBar.backgroundImage = [UIImage imageNamed:@"tabBar"];// 设置默认选中的页面 页面的下标从0开始self.selectedIndex = 2;

UITabBarController还有比较有意思的功能, 比如设置TabBarItem(某个标签按键) 上的红色提示视图 (比如说QQ 微信上的未读消息数, 特别是微信朋友圈有新消息的时候”发现”标签上的未读信息数的提示)

navThree.tabBarItem.badgeValue = @"50";// 提示内容的值为字符串, 甚至可以为中文

高级Block

将Block设置成属性的时候, 得用copy
简单来说, 由于Block中的参数 都是存在栈区的, 而我们此时操作的内容都是在栈区中的, 所以需要把栈区中的值保存到堆区中使用

Block分区全局Block, 堆区Block, 栈区Block. 由于本人才疏学浅还是新手, 这里就没法为大家深刻的介绍Block的具体分类和作用域, 以下内容不谈原理, 只谈应用(汗颜)

Block传值的时候, 需要将Block作为某个对象的属性. 将Block作为属性的时候, 需要将Block用copy属性声明.

// block作为copy属性的setter 方法内部实现- (void)setBlock:(MyBlock)block {    if (_block != block) {        Block_release(_block);        _block = Block_copy(block);    }}// Block_release()和Block_copy()是特殊的专门用于处理Block的系统函数

Block的运行特点

Block的运行阶段是声明->调用->实现
可以看到一个Block的运行流程是 先声明, 然后直接调用, 调用后再去找Block对应的实现. 但是得注意, 这个Block的实现必须在调用之前就已经写好.

Block的循环引用

在这里简单的描述一下循环引用的现象

对象A中存在一个Block 并且在A中某个触发方法中调用这个Block, 对象B 实现了A中的Block, 实现的功能是让B自身完成一个功能.

用代码翻译一下就是:

// A.h :typedef void(^MyBlock)();@property (nonatomic, copy) MyBlock block;A.m- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {    self.block();    NSLog(@"点我了");}// B.mA *a = [[A alloc] init];a.block = ^void () {   [self.navigationController popViewControllerAnimated:YES];};

此时在A的block中 引用了B的对象self
在B中引用了A中的属性block

此时就构成了Block的循环引用的条件

Block循环引用的条件简单来说就是一个对象在另一个对象的Block实现中调用了自身的对象
结果就是两个对象都不会被释放, 导致内存泄漏的问题.

解决Block循环应用的通常做法如下

__block B *b = self;a.block = ^void () {    [b.navigationController popViewControllerAnimated:YES];}

用__block修饰相当于release一次, 被它修饰的对象会自动释放一次. ARC模式下用__weak代替__block

0 0
原创粉丝点击