文档章节

开发随笔记录

A
 AAAAdler
发布于 2015/10/22 10:24
字数 2720
阅读 98
收藏 2

1.

昨天别人给了同事一个简单的demo,问题是UITableViewController上有内有textfield的cell,在textfield被选中,弹出键盘,界面会自动滚动,就和我们平时做textfield输入时不要被键盘挡住那样的滚动。对方是想要不要这个自动滚动,因为那个demo滚动的位置不对,查了一下,发现不知道什么时候起,UITableViewController自动适配了这个需求,完成不滚动的要求只有两个做法:(1)把UITableViewController换成UIViewController;(2)重载viewWillAppear方法,但不要继承[super viewWillAppear]

 

2.

大家都知道字典类NSDictionary和NSMutableDictionary在写入的时候,value对应的key只要实现copy协议就可以。上次碰到要把一个字典的数据存到沙盒的plist表里,发现写入失败。做了些对比后发现,因为字典类中,有一个key是number类型的,而plist表去查看source code,发现它的key都是同种类型,string的,所以写入失败。

 

3.

在CoreText里获取文本被点击位置的文本索引,一般用的是方法CTLineGetStringIndexForPosition,但多次测试会发现,你点某一个字的前半部分,输出是前一个字的索引,点击后半部分才输出正确,看了下官方文档里的解释,大概意思是这个方法是将点击位置转换为最近的字符插入处(其实就是光标),所以才会造成这样。修正的代码应该是这样的:

CFIndex idx = CTLineGetStringIndexForPosition(line, relativePoint);
CGFloat glyphStart;
CTLineGetOffsetForStringIndex(line, idx, &glyphStart);
if (relativePoint.x < glyphStart && idx) {
   --idx;
}

 

4.

CALayer类有个方法,检测某个点是否是在它的区域中

/* Returns true if the bounds of the layer contains point 'p'. */
- (BOOL)containsPoint:(CGPoint)p;

方法上面是文档里的说明,用的是layer的bounds属性来比对点p是否在其中,我们都知道bounds属性的坐标是原点,所以使用这个方法,需要把点p先用以下方法转换到要检测是layer坐标系中,再进行检测:

- (CGPoint)convertPoint:(CGPoint)p fromLayer:(CALayer *)l;
- (CGPoint)convertPoint:(CGPoint)p toLayer:(CALayer *)l;

 

5.

关于CALayer,网上一直可以看到这样一段描述:

UIView的layer树形在系统内部,被系统维护着三份copy(这段理解有点吃不准)。
第一份,逻辑树,就是代码里可以操纵的,例如更改layer的属性等等就在这一份。
第二份,动画树,这是一个中间层,系统正在这一层上更改属性,进行各种渲染操作。
第三份,显示树,这棵树的内容是当前正被显示在屏幕上的内容。
这三棵树的逻辑结构都是一样的,区别只有各自的属性。

或者是这样一个很简单的解释:

UIView的layer树形在系统内部,被维护着三份copy。分别是逻辑树,这里是代码可以操纵的;动画树,是一个中间层,系统就在这一层上更改属性,进行各种渲染操作;显示树,其内容就是当前正被显示在屏幕上得内容。

具体是怎样的,没明白,自己找资料,发现有这样两个属性:

- (id)presentationLayer;
- (id)modelLayer;

第一个属性是显示树或者呈现树,第二个属性是模型树。叫法有不同,有叫逻辑树,动画树和显示树,也有叫呈现树,模型树和渲染树。对这两个方法获得的layer,修改其属性是无效的。

呈现树是我们在显示在屏幕上所看到的layer,所以如果下面的测试代码在viewDidLoad里输出这一层,会看到是空的,因为这时候还没有显示在屏幕上。

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.
    
    _colorLayer = [[CALayer alloc] init];
    _colorLayer.bounds = CGRectMake(0, 0, 10, 10);
    _colorLayer.position = CGPointMake(_centerView.bounds.size.width / 2, _centerView.bounds.size.height / 2);
    _colorLayer.backgroundColor = [UIColor blueColor].CGColor;
    
    [_centerView.layer addSublayer:_colorLayer];
    
    _timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(showLogOut) userInfo:nil repeats:YES];
    [_timer fire];
    
}

- (void)showLogOut {
    
    CALayer* layer = _colorLayer.presentationLayer;
    CALayer* layer1 = _colorLayer.modelLayer;
    NSLog(@"present layer %@", layer);
    NSLog(@"%f-------%f", layer.position.x, layer1.position.x);
}

- (IBAction)doAnimation:(id)sender {
    
    CGPoint point = CGPointMake(_centerView.bounds.size.width / 2, _centerView.bounds.size.height / 2);
    
    CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:@"position"];
    animation.toValue = [NSValue valueWithCGPoint:CGPointMake(point.x + 100, point.y)];
    animation.duration = 10;
    [_colorLayer addAnimation:animation forKey:nil];
    
}

上面的测试代码是做了一个向右平移100的动画,输出可以看出,presentationLayer的属性跟随动画的变化而变化,记录各个时期的layer的状态,但modelLayer却一直不变。动画结束后,就是layer平移到了指定位置后,会自动返回原始的位置,这就是modelLayer所记录初始状态的作用。

还有一点需要注意,上面的定时器输出还输出了 presentationLayer ,而 presentationLayer 每次都是不一样的。

上面一直没提到的渲染树(或者是动画树)是私有的,我们无法访问,渲染树对呈现树的数据进行渲染,为了不阻塞主线程,渲染树的行为都是在其他线程上进行的。

 

6.NSTimer注意事项

NSTimer会自动retain一次target和userinfo的参数,所以在NSTimer不用的时候,要先invalidate,再把NSTimer置nil,而这两步操作不能在dealloc,否则dealloc永远不会执行,因为无法释放。

NSTimer在滑动视图的时候,是停止执行的,因为runloop让给了UITrackingRunLoopMode,想要执行需要加上这个

[[NSRunLoop currentRunLoop] addTimer:_myTimer forMode:NSRunLoopCommonModes];

NSTimer在非主线程的线程是不执行的,除非加入下面的代码

[[NSRunLoop currentRunLoop] addTimer:_myTimer forMode:NSRunLoopCommonModes];
[[NSRunLoop currentRunLoop] run];

 

7.Block

我们都知道,block内部是不能修改外部变量的,如果要修改,需要加上__block。

上面的描述,其实不对,block是不能修改外部的局部变量,但是对于属性和成员变量,是可以修改的。这是为什么呢?

简单来说,普通的局部外部变量,是分配到栈上的,而__block所修饰的变量,会自动复制到堆上。

参考1:http://chun.tips/blog/2014/11/13/hei-mu-bei-hou-de-blockxiu-shi-fu/

参考2:https://www.zhihu.com/question/39980914

 

8.引用计数相关

retainCount 是引用计数,指的是对某一块内存地址的使用情况,如果为0,代表没有使用了,可以释放。

但是比如说 

MyClass* a = [[MyClass alloc] init];

a 是一个指针对象,即 a 这个内存地址里的内容,是存储另一片内存地址,那么 retainCount 是指那一片内存地址的使用情况?

MyClass* aObj = [[MyClass alloc] init];

MyClass* bObj = [aObj copy];

MyClass* cObj = [aObj retain];

NSLog(@"count %d  %d %d", aObj.retainCount, bObj.retainCount, cObj.retainCount);
NSLog(@"%p  %p  %p", aObj, bObj, cObj);
NSLog(@"%p  %p  %p", &aObj, &bObj, &cObj);

跑完上面的测试代码可以得到结果, &aObj, &bObj, &cObj 三者是对应三个对象的内存地址,各不相同,但 aObj, bObj, cObj 是三者的内容,a 和 c 相同, 和 b 不同,所以 retainCount 是指内容的地址。

还有一个情况

NSString* aStr = @"1";
NSString* bStr = [aStr retain];
NSString* cStr = [aStr copy];
NSString* dStr = [aStr mutableCopy];
    
NSLog(@"%p  %p  %p  %p", aStr, bStr, cStr, dStr);
    
NSLog(@"%d  %d  %d  %d", aStr.retainCount, bStr.retainCount, cStr.retainCount, dStr.retainCount);

输出的结果是,a, b, c 都是指向同一片内存,而 d 不同,引用计数上,a, b, c 都是 -1,d 是 1 。

 

9.tableViewHeaderView的高度(tableFooterView同理)

用纯代码设置tableViewHeaderView的话,是没有啥问题的,但是从xib中加载一个view,再指定这个view作为tableHeaderView的话(代码如下),高度展示上就会不合要求。

MYHeaderView* headerView = [[[NSBundle mainBundle] loadNibNamed:@"MYHeaderView" owner:self options:nil] lastObject];
CGRect frame = headerView.frame;
frame.size.height = 100;
headerView.frame = frame;
tableView.tableHeaderView = headerView;

结果是会挡住开头的cell。原因未知,解决的办法有这两种:

1.在外面代码生成一个view做为容器,把从xib加载的加到这个容器中,把容器做为tableHeaderView

UIView* headerContentView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.height, 100)];

MYHeaderView* headerView = [[[NSBundle mainBundle] loadNibNamed:@"MYHeaderView" owner:self options:nil] lastObject];

[headerContentView addSubview:headerView];

tableView.tableHeaderView = headerContentView;

2.在MYHeaderView(xib对应的类)中,重写layoutSubviews方法,指定这个tableHeaderView的宽高

- (void)layoutSubviews
{
    [super layoutSubviews];
    
    self.bounds = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.height, 100);
}
 
附:这个要配合第一段代码设置好从xib加载出来的view的高度

 

10.textField的KVO监听输入

本来想用KVO来监听UITextField的键盘输入

[textField addObserver:self forKeyPath:@"text" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
    NSLog(@"change");
}

却发现上面的观察,在键盘输入中是不会触发的,除非手动设置textField.text,一时不解是为什么,查了资料,最终得到个提示,用runtime遍历出UITextField的所有成员变量

unsigned int outCount;
Ivar *IvarArray = class_copyIvarList([UITextField class], &outCount);//获取到UITextField中的所有成员变量
for (unsigned int i = 0; i < outCount; i ++) {
    Ivar *ivar = &IvarArray[i];
    NSLog(@"第%d个成员变量:%s,类型是:%s",i,ivar_getName(*ivar),ivar_getTypeEncoding(*ivar));// 依次获取每个成员变量并且打印成员变量名字和类型
}

发现其中没有text对应的成员变量,但是有几个label

第30个成员变量:_displayLabel,类型是:@"UITextFieldLabel"
第31个成员变量:_placeholderLabel,类型是:@"UITextFieldLabel"
第32个成员变量:_dictationLabel,类型是:@"UITextFieldLabel"
第33个成员变量:_suffixLabel,类型是:@"UITextFieldLabel"
第34个成员变量:_prefixLabel,类型是:@"UITextFieldLabel"
第36个成员变量:_label,类型是:@"UILabel"

如果设置过textField的text或者placeholder,用object_getIvar根据成员变量进行输出,会看到_displayLabel_placeholderLabel会有对应的值,但是如果从键盘输入的话,是不会有对应的值的。因此,在不确定被观察者内部构造的情况下,不适宜使用KVO。

 

11.新线程中更新UI

一般我们如果有某些耗时的操作,比如图片合成之类的,我们会把这个操作新开一个线程去做,完成后异步调用主线程去赋值。但如果在不调用主线程去赋值,直接在新线程中赋值,是得不到更新的。这是因为在新线程中,无法读取到当前的图像上下文的原因。

- (void)drawRect:(CGRect)rect {
    dispatch_queue_t colorQueue = dispatch_queue_create("testMyNewColor", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(colorQueue, ^{
        CGContextRef context = UIGraphicsGetCurrentContext();
        CGContextSetFillColorWithColor(context, [UIColor blueColor].CGColor);
        CGContextFillRect(context, self.bounds);
    });
}

上面这段测试代码,在运行后,控制台就会报出一些内容,告诉你invalid context,因为在这里的context是空的,去掉外层的新线程就没问题。

ps:当然,对于UI的某些更新是可以的,比如更新Button的title这样的操作,但是这似乎没太多意义。

 

12.hiddenWhenPush

之前只知道这个属性,设置后,在push的时候,会隐藏tabbar,之后push的vc都是隐藏tabbar的。今天调试一个bug发现,由于我把设置hiddenWhenPush = YES的那个vc从viewControllers的堆栈中移除掉,导致后面再push的vc都显示了tabbar。

也就是说每次push出下一个vc的时候,都会去遍历堆栈中,看是否hiddenWhenPush = YES来决定是否隐藏tabbar。

© 著作权归作者所有

上一篇: GCD
下一篇: AutoLayout学习体验
A
粉丝 3
博文 26
码字总数 23250
作品 0
广州
私信 提问
在GridView列表中使用图片显示记录是否包含附件

在我的前面很多文章中,都介绍过通用附件模块的管理,本篇随笔主要介绍在一些应用模块中的列表展示中,包含附件的记录,在GridView列表界面中使用图标来快速显示是否有附件的情况。 1、通用附...

walb呀
2017/12/04
0
0
结合bootstrap fileinput插件和Bootstrap-table表格插件,实现文件上传、预览、提交的导入Excel数据操作流程

1、bootstrap-fileinpu的简单介绍 在前面的随笔,我介绍了Bootstrap-table表格插件的具体项目应用过程,本篇随笔介绍另外一个Bootstrap FieInput插件的使用,整合两者可以实现我们常规的Web...

walb呀
2017/12/04
0
0
Winform开发框架中工作流模块之申请单草稿处理

在我们开发工作流模块的时候,有时候填写申请单过程中,暂时不想提交审批,那么可以暂存为草稿,以供下次继续填写或者提交处理,那么这个草稿的功能是比较实用的,否则对于一些填写内容比较多...

walb呀
2017/12/04
0
0
手把手教你封装 Vue 组件,并使用 npm 发布

源码地址,如果对你有帮助的话希望不要吝啬你的 Star 本文主要记录一下如何基于 开发组件,并在 npm 上发布。废话不多说,进入正题 Vue 开发插件 开发之前先看看官网的 开发规范 我们开发的之...

KoK
2018/07/11
0
0
用 nodejs 写一个命令行工具 :创建 react 组件的命令行工具

用 nodejs 写一个命令行工具 :创建 react 组件的命令行工具 前言 上周,同事抱怨说 react 怎么不能像 angular 那样,使用命令行工具来生成一个组件。对呀,平时工作时,想要创建一个 reac...

hileix
2018/12/17
0
0

没有更多内容

加载失败,请刷新页面

加载更多

OpenStack 简介和几种安装方式总结

OpenStack :是一个由NASA和Rackspace合作研发并发起的,以Apache许可证授权的自由软件和开放源代码项目。项目目标是提供实施简单、可大规模扩展、丰富、标准统一的云计算管理平台。OpenSta...

小海bug
昨天
6
0
DDD(五)

1、引言 之前学习了解了DDD中实体这一概念,那么接下来需要了解的就是值对象、唯一标识。值对象,值就是数字1、2、3,字符串“1”,“2”,“3”,值时对象的特征,对象是一个事物的具体描述...

MrYuZixian
昨天
6
0
数据库中间件MyCat

什么是MyCat? 查看官网的介绍是这样说的 一个彻底开源的,面向企业应用开发的大数据库集群 支持事务、ACID、可以替代MySQL的加强版数据库 一个可以视为MySQL集群的企业级数据库,用来替代昂贵...

沉浮_
昨天
7
0
解决Mac下VSCode打开zsh乱码

1.乱码问题 iTerm2终端使用Zsh,并且配置Zsh主题,该主题主题需要安装字体来支持箭头效果,在iTerm2中设置这个字体,但是VSCode里这个箭头还是显示乱码。 iTerm2展示如下: VSCode展示如下: 2...

HelloDeveloper
昨天
9
0
常用物流快递单号查询接口种类及对接方法

目前快递查询接口有两种方式可以对接,一是和顺丰、圆通、中通、天天、韵达、德邦这些快递公司一一对接接口,二是和快递鸟这样第三方集成接口一次性对接多家常用快递。第一种耗费时间长,但是...

程序的小猿
昨天
11
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部