前言
掌握:
0>图片拉伸:确定可拉伸、平铺的的小矩形inset。确定图片可调整的小矩形--resizableImageWithCapInsets:
1》通知的发布
2》通知的监听
3》通知的移除
零、通知和代理的选择
1》共同点:都能饿按此对象间的通信
2》不同点:代理是一对一关系;通知是多对多的关系(1个对象能告诉N个对象发生了什么事情,1个对象能得知N个对象发生了什么事情)
一、通知中心(NSNotificationCenter)
每个应用程序都有一个通知中心:专门负责协助不同对象间的消息通信
1.任何一个对象都可以向通知中心发布通知(NSNotification),来描述自己在做什么事情。
其他感兴趣的监听器(Observer)可以申请在某个特定通知发布时(或者某个特定的对象发布通知时),收到这个通知。
二、通知(NSNotification)
1.一个完整的通知通常包含三个属性:通知名称name、通知的发布者、一些额外的信息UserInfo
2.初始化一个通知对象
3.发布通知(通知中心NSNotificationCenter 提供了相应的方法来帮助发布通知)
三、注册通知监听器
NSNotificationCenter 提供了相应方法来注册一个监听通知的监听器(observer)
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(NSString *)aName object:(id)anObject;
/**
observer:监听器,即谁要接收这个通知;
aSelector:收到通知后,回调监听器的这个方法,并且把通知对象当做参数传入;
aName:通知的名称。如果为nil,那么无论通知的名称是什么,监听器都能收到这个通知;
anObject:通知的发布者。如果为anObject和aName都为nil,监听器都收到所有的通知
- (id)addObserverForName:(NSString*)name object:(id)obj queue:(NSOperationQueue *)queue usingBlock:(void (^)(NSNotification* note))block;
/**
name:通知的名称;
obj:通知发布者;
block:收到对应的通知时,会回调这个block;
queue:决定了block在哪个操作队列中执行,如果传nil,默认在当前操作队列中同步执行;
四、取消注册通知监听器
通知中心不会retain监听器对象,在通知中心注册过的对象,必须在该对象释放前取消注册;否则,当相应的通知再次出现时,通知中心仍然会向该监听器发送消息,可能会导致应用的崩溃。— 尤其再iOS8,7 系统经常发生
1。NSNotificationCenter 提供了相应的方法来取消注册observer。
- (void)removeObserver:(id)observer;
- (void)removeObserver:(id)observer name:(NSString *)aName object:(id)anObject;
//一般在监听器销毁之前取消注册(如在监听器中加入下列代码):
- (void)dealloc
{
//[super dealloc]; 非ARC中需要调用此句
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
ARC的介绍
/ ARC 是编译器特性,而不是IOS运行时特性,它也不类似于其他语言的垃圾收集器(GC)**
//
// Person.m
// 08-Strong&weak&assign
//
// Created by devzkn on 2/28/16.
// Copyright © 2016 devzkn. All rights reserved.
//
#import "Person.h"
@implementation Person
/** ARC 是编译器特性,而不是IOS运行时特性,它也不类似于其他语言的垃圾收集器(GC)
1)ARC的规则:只要还有一个变量指向对象,对象就会保持在内存中-- 默认所有的实例变量和局部变量都是strong指针,因为它们能保持对象的生命。
2)OC中有强参照strong和弱参照weak。--weak型的指针变量仍然可以指向一个对象,但不属于这个对象的拥有者;weak指针主要用于父子关系,即父亲拥有一个儿子的strong指针,儿子需使用weak指针指向父亲。
典型的例子:
你的ViewControl通过strong指针(self.view)拥有一个UITableView,UITableView的dataSource和delegate都是weak指针,指向你的viewControl
@property(null_resettable, nonatomic,strong) UIView *view;
@property (nonatomic, weak, nullable) id <UITableViewDelegate> delegate;
3)在ARC(代码的一种静态分析工具)中dealloc 主要用于调试,判断对象被释放。可以用来管理一些资源,
*** an implementation of dealloc, do not invoke the superclass’s implementation 不能调用[super dealloc]
*** 在ARC下父类的dealloc同样由编译器自动完成,不能用来释放实例变量;
4)在ARC中考虑的是对象之间的关联,也就是那个对象拥有哪个对象。--无论何时你创建一个对象时,都要考虑谁该拥有它,以及这个对象需要存活多久
5)ARC只能工作于OC。
如果应用了core foundation,malloc()、free(),此时还是需要你来手动管理
*/
- (void) dealloc{
NSLog(@"Person %@被释放 %s ",self.name,__FUNCTION__);
}
@end
五、UIDevice 通知
UIDevice类提高了一个单例对象,它代表着设备;通过它可以获取一些设备相关的信息(batteryLevel、batteryState、model、systemVersion)
获取单例对象 Collapse source
1
•通过[UIDevice currentDevice]可以获取这个单粒对象
1.UIDevice对象会不间断的发布一些通知
六、键盘通知
我们经常需要在键盘弹出或者隐藏的时候做一些特定的操作,因此需要监听键盘的状态
1、键盘状态改变的时候,系统会发出一些特色的通知
//1. 注册Observer
[center addObserver:self selector:@selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil];
2.系统发出键盘通知的时候,会附带一下跟键盘有关的额外字典信息
3. 通知处理示例
/**
处理键盘通知
处理好view的frame和键盘frame的关系--移动view
*/
- (void)keyboardWillChangeFrame: (NSNotification *) notification{
NSLog(@"%@",notification.userInfo);
//修改UIWindow的背景颜色
// UIWindow *keyWindow =[UIApplication sharedApplication].keyWindow;
[self.view.window setBackgroundColor:self.tableView.backgroundColor];
//键盘的弹出完成时的frame UIKeyboardFrameEndUserInfoKey = "NSRect: {{0, 227}, {320, 253}}";
//键盘隐藏结束之后的frame UIKeyboardFrameEndUserInfoKey = "NSRect: {{0, 480}, {320, 253}}";
CGRect keyBoardFrame = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
//2.动画平移view控件
// UIKeyboardAnimationDurationUserInfoKey = "0.25";
//1>获取动画的持续时间
CGFloat keyBoardAnimationDuration = [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] floatValue];
//2>计算平移的y值
CGFloat keyBoardY = keyBoardFrame.origin.y ;//键盘的实时y值
CGFloat y = keyBoardY- KScreenHeight;//平移的y值
[UIView animateWithDuration:keyBoardAnimationDuration animations:^{
[self.view setTransform:CGAffineTransformMakeTranslation(0, y)];//设置平移的x、y值
}];
}
#pragma mark - scrollView 的代理方法
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
NSLog(@"%s",__func__);
//关闭键盘,此时view的frame要还原
[self.view endEditing:YES];
}
正文
一、QQ界面的消息布局
1、消息按钮的背景图片ImageView、titleLabel,以及button本身之间的大小关系处理
#if 1
- (void)setTextViewFrame{
//文本消息的大小
CGRect textFrame = [self.message.text boundingRectWithSize:CGSizeMake(KScreenWidth*0.5, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:KtextFont} context:nil];
textFrame.origin.x = (self.message.type == HSMessageModelKevin) ? ( KScreenWidth-CGRectGetWidth(self.iconViewFrame)-KPading*2- CGRectGetWidth(textFrame)-KButtonPading*2): (KPading*2+CGRectGetWidth(self.iconViewFrame));
textFrame.origin.y =CGRectGetMinY(self.iconViewFrame)+KPading;////加个KPading可以让相同时间的cell更好看
//button 的大小
CGRect buttonFrane = textFrame;
buttonFrane.size.width += KButtonPading*2;
buttonFrane.size.height += KButtonPading*2;
_textViewFrame = buttonFrane;
}
#endif
[_textView setContentEdgeInsets:UIEdgeInsetsMake(KButtonPading, KButtonPading, KButtonPading, KButtonPading)];
#pragma mark - 获取可拉伸图片--确定填充“拉伸之后的空白区域”的小矩形
- (UIImage *)resizableImageWithImage:(UIImage *) image{
CGFloat widthForTopORBottom = image.size.width*0.5f -1;//inset的top、bottom
CGFloat heightForLeftORRight = image.size.height*0.5f -1;//inset的left、right
UIImage *resizableImage = [image resizableImageWithCapInsets:UIEdgeInsetsMake(heightForLeftORRight, widthForTopORBottom, heightForLeftORRight, widthForTopORBottom)];
return resizableImage;
}
二、键盘处理
[center addObserver:self selector:@selector(keyboardDidChangeFrame:) name:UIKeyboardDidChangeFrameNotification object:nil];//监听键盘通知
#pragma mark - scrollView 的代理方法
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
NSLog(@"%s",__func__);
//关闭键盘
[self.view endEditing:YES];
}
/** 键盘弹出的frame
2016-03-28 09:41:27.498 20160525-QQinterface[842:16465] NSConcreteNotification 0x7b274c00 {name = UIKeyboardDidChangeFrameNotification; userInfo = {
UIKeyboardAnimationCurveUserInfoKey = 7;
UIKeyboardAnimationDurationUserInfoKey = "0.25";
UIKeyboardBoundsUserInfoKey = "NSRect: {{0, 0}, {320, 253}}";
UIKeyboardCenterBeginUserInfoKey = "NSPoint: {160, 606.5}";
UIKeyboardCenterEndUserInfoKey = "NSPoint: {160, 353.5}";
UIKeyboardFrameBeginUserInfoKey = "NSRect: {{0, 480}, {320, 253}}";
UIKeyboardFrameEndUserInfoKey = "NSRect: {{0, 227}, {320, 253}}";
UIKeyboardIsLocalUserInfoKey = 1;
}} */
/** 关闭键盘时候的frame信息*/
2016-03-28 09:41:30.775 20160525-QQinterface[842:16465] -[ViewController scrollViewWillBeginDragging:]
2016-03-28 09:41:31.287 20160525-QQinterface[842:16465] NSConcreteNotification 0x7b21cae0 {name = UIKeyboardDidChangeFrameNotification; userInfo = {
UIKeyboardAnimationCurveUserInfoKey = 7;
UIKeyboardAnimationDurationUserInfoKey = "0.25";
UIKeyboardBoundsUserInfoKey = "NSRect: {{0, 0}, {320, 253}}";
UIKeyboardCenterBeginUserInfoKey = "NSPoint: {160, 353.5}";
UIKeyboardCenterEndUserInfoKey = "NSPoint: {160, 606.5}";
UIKeyboardFrameBeginUserInfoKey = "NSRect: {{0, 227}, {320, 253}}";
UIKeyboardFrameEndUserInfoKey = "NSRect: {{0, 480}, {320, 253}}";
UIKeyboardIsLocalUserInfoKey = 1;
}}
b 、处理键盘的属性--谁负责唤起键盘,谁去修改键盘属性信息
UIKeyboardWillChangeFrameNotification object:nil];
//2.处理textFieldView的输入控件
self.textFieldView.leftView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 8, 0)];
self.textFieldView.leftViewMode = UITextFieldViewModeWhileEditing;
//3.键盘信息处理原则:谁触发键盘弹出的,谁去负责处理键盘的属性
[self.textFieldView setReturnKeyType:UIReturnKeySend];
[self.textFieldView setEnablesReturnKeyAutomatically:YES];// indicating whether the Return key is automatically enabled when the user is entering text
三、自动回复的实现
#pragma mark - textFieldView delegate方法
/**
处理文本提交的信息,修改数据模型,刷新tableview
*/
- (BOOL)textFieldShouldReturn:(UITextField *)textField{
NSString *fieldText = textField.text;//保存信息,避免文本控件的信息的清除
//1.修改模型数据
[self addMessageFrames:fieldText type:HSMessageModelKevin];
//2.关闭键盘
// [self.textFieldView endEditing:YES];
//3.设置自动回复--从autoReplay.plist进行匹配,找到对应的回复信息
[self autoReplay:fieldText];
return YES;
}
/** 自动回复*/
- (void)autoReplay: (NSString *) fieldText{
if (fieldText.length == 0) {
return;
}
NSString *repplayText = @"系统正忙。。。。";//默认回复
for (int i =0; i < fieldText.length; i++) {
NSString *subString = [fieldText substringWithRange:NSMakeRange(i, 1)];
NSLog(@"%@",subString);
if (self.autoReplayDict[subString]) {
repplayText = self.autoReplayDict[subString];
break;
}
}
[self addMessageFrames:repplayText type:HSMessageModelLydia];
}
#pragma mark - 数据模型的构建方法
- (void) addMessageFrames:(NSString *)text type:(HSMessageModelType)type{
if (0 == text.length) {
return;
}
//1> 或者上一个frame模型数据
HSMessageFrameModel *previousFrame = [self.messageFrames lastObject];
HSMessageModel *message = [[HSMessageModel alloc]init];
[message setText:text];//消息内容
[message setTime:@"2:00"];//消息发送时间
[message setIsHidenTimeWithPreviousMessage:previousFrame.message currentMessage:message]; //设置是否显示时间属性
[message setType:type];//消息的发送者
[self.messageFrames addObject:[HSMessageFrameModel messageFrameMessage:message]];//添加到frame模型数组
//2.数据刷新
NSIndexPath *indexpath = [NSIndexPath indexPathForItem:(self.messageFrames.count-1) inSection:0];
[self.tableView insertRowsAtIndexPaths:@[indexpath] withRowAnimation:UITableViewRowAnimationAutomatic];
//tableView 进行上滚一行
[self.tableView scrollToRowAtIndexPath:indexpath atScrollPosition:UITableViewScrollPositionTop animated:YES];//UITableViewScrollPosition 滚动方向
//3.清空输入框的内容
[self.textFieldView setText:nil];
}
QQ好友列表的分组数据刷新的实现方式
1、代理
2、通知
(void)viewDidLoad {
[super viewDidLoad];
/**
使用通知实现分组数据的加载
*/
//注册通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(headerViewForclickNotification:) name:@"headerViewForclickNotification" object:nil];
}
- (void)dealloc{
//移除通知
[[NSNotificationCenter defaultCenter] removeObserver:self];
NSLog(@"%s",__func__);
}
#if 1
- (void)headerViewForclickNotification:(NSNotification *)notification{
//刷新分组数据
NSIndexSet *set = [NSIndexSet indexSetWithIndex:[notification.object section]];
[self.tableView reloadSections:(set) withRowAnimation:UITableViewRowAnimationAutomatic];
}
#endif
(void) titleButtonViewClick: (HSHeaderView *) headerView{
//当分组的cell个数为0 的时候,即使合上,设置一个展开、合上标志属性,以便进行操作方法:(CGFloat) tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{
[self.friendsGroup setIsOpen:!self.friendsGroup.isOpen];
NSLog(@"%d",self.friendsGroup.isOpen);
//方式一、通知代理
// //先判断代理对象是否实现了代理方法
// if ([self.delegate respondsToSelector:@selector(headerViewForclick:)]) {
// [self.delegate headerViewForclick:self];
//// }
// //方式二、block
// if (self.block) {
// self.block(self);//执行block
// }
//方式三、通知
//发布通知
[[NSNotificationCenter defaultCenter] postNotificationName:@"headerViewForclickNotification" object:self];
}
3、block
/** 使用typeblock实现消息的传递*/
typedef void(^HSHeaderViewBlockForClick)(id);//定义block,常常使用copy
@property (nonatomic,copy) HSHeaderViewBlockForClick block;//属性定义
/** 执行自己的block*/
#pragma mark - titleButtonViewClick事件的处理
/**
合并cell,展开cell的触发处理
*/
- (void) titleButtonViewClick: (HSHeaderView *) headerView{
//当分组的cell个数为0 的时候,即使合上,设置一个展开、合上标志属性,以便进行操作方法:(CGFloat) tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{
[self.friendsGroup setIsOpen:!self.friendsGroup.isOpen];
//方式一、通知代理
// //先判断代理对象是否实现了代理方法
// if ([self.delegate respondsToSelector:@selector(headerViewForclick:)]) {
// [self.delegate headerViewForclick:self];
// }
//方式二、block
if (self.block) {
self.block(self);//执行block
}
}
#pragma mark - tableView datasource 协议方法
#if 1
/**
自定义UITableViewHeaderFooterView
关于控件没显示的经验小结:
1》父控件的frame
2》当前控件的frame
3》当前控件的hidden属性
4》当前控件的alpha<= 0.01
*/
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{
HSFriendsGroup *friendsGroupModel = self.fridendsGroupArray[section];
HSHeaderView *headerView = [HSHeaderView tableHeaderViewWithFriendsGroup:friendsGroupModel TableView:tableView];
//设置代理对象
// [HeaderView setDelegate:self];
//设置block,定义具体实现
[headerView setBlock:^(HSHeaderView * view ){
//刷新分组数据
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndex:section];
[tableView reloadSections:indexSet withRowAnimation:UITableViewRowAnimationAutomatic];
}];
[headerView setSection:section];
return headerView;
}
#endif
三、QQ好友列表的分组headView的图片平移、旋转处理
#pragma mark - setTransform 处理
//Tells the view that its superview changed. 当前的控件加载到父控件(tableview)的时候调用
- (void)didMoveToSuperview{
NSLog(@"%s",__func__);
//2.对button的imageView的image进行平移、翻滚--先加载数据,在设置平移
CGFloat angle = (self.friendsGroup.isOpen) ? M_PI_2 :0;
[self.titleButtonView.imageView setTransform:CGAffineTransformMakeRotation(angle)];
}
//对imageView进行内容排列对齐方式、是否剪切超出部分进行设置
[_titleButtonView.imageView setContentMode:UIViewContentModeCenter];
[_titleButtonView.imageView setClipsToBounds:NO];//subviews are confined to the bounds of the view. 不剪切超出的部分
总结
零、 调试技巧
1.po 关键字的使用--打印内存对象
(lldb) po notification
NSConcreteNotification 0x10010eb20 {name = zhengaiwang; object = <HSMatchmakingCompany: 0x100206170>; userInfo = {
info = "....";
title = "\U65b0\U6765\U4e86\U4e00\U6279\U7f8e\U5973";
}}
清空cell的颜色-》使用tableView的颜色 Collapse source
1
2
[cell setBackgroundColor:[UIColor clearColor]];
[self.tableView setBackgroundColor:[UIColor colorWithRed:221/255.0 green:221/255.0 blue:221/255.0 alpha:1]];
二、问题分析
1.关于控件没显示的经验小结:
1》父控件的frame
2》当前控件的frame
3》当前控件的hidden属性
4》当前控件的alpha<= 0.01