iOS手势事件分发原理

HitTest的主要目的就是找到对于UIEvent的响应者,本文实现代码是根据apple文档描述的一种猜测实现,帮助大家理解原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
- (UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event{

//apple文档描述,不接受事件的情况
if (self.userInteractionEnabled == NO || self.isHidden == YES || self.alpha < 0.01) {
return nil;
}

//如果当前View包含此Point
if ([self pointInside:point withEvent:event]) {

//遍历子View,这里注意要从后往前遍历,因为后面的是越靠近用户的
for (NSInteger i=self.subviews.count-1; i>=0; i--) {

UIView* subView = [self.subviews objectAtIndex:i];

//将父View的Point转换成子View坐标系的Point
CGPoint pointInSubView = [subView convertPoint:point fromView:self];

//递归子View调用HitTest:
UIView* resultView = [subView hitTest:pointInSubView withEvent:event];

//找到了子View可以响应
if (resultView) {
return resultView;
}

}

//没有找到可以响应的子View,返回自己
return self;
}

//返回nil,告诉上一级自己无法响应此事件
return nil;
}

流程图总结

image.png

tweak心得

1 关于ssh

一般形式 ssh root@192.168.2.17或者ssh mobile@192.168.2.17

root和mobile分别为iOS上默认用户,alpine是默认密码

可以通过ssh-gen 分别在Mac和iOS上生成密钥对,然后将Mac上的公钥拷贝到手机上,这样配之后,每次ssh不会再提示输入密码

2 scp source dest

一般为

scp ~/123.txt mobile@192.168.2.17:/usr/bin

3 Makefile

可以配置手机的IP,framework,arch等参数

THEOS_DEVICE_IP = 192.168.31.202
ARCHS = armv7 arm64
TARGET = iphone:latest:8.0

iOSREGreetings_FRAMEWORKS = UIKit

4 关于bundleID

.plist中的bundle就是你想hook的程序的bundleID

tweak环境搭建

0 设置环境变量 export THEOS=/opt/theos

可以设置~/.zshrc中添加,修改后用source命令重新加载

1下载theos(~/jailbreak目录下已经下载过),放在/opt/theos下

2 下载ldid,放到/opt/theos/bin下

sudo chmod 777 /opt/theos/bin/ldid

3 配置CydiaSubstrate

在Cydia中安装CydiaSbustrate,然后scp 讲iPhone上的 /Library/Frameworks/CydiaSubstrate.framework/CydiaSubstrate 拷贝到Mac /opt/theos/lib/下,并重命名为libsubstrate.dylib

并将头文件substrate.h也scp 到/opt/theos/include下

sudo /opt/theos/bin/bootstrap.sh substrate

4 将dm.pl重命名为dpkg-deb,cp到/opt/bin/

suodo chmod 777 /opt/bin/dpkg-deb

基本就搭建完成,可以练习创建工程

/opt/theos/bin/nic.pl

然后进行make package

make package install

纹理贴图

1 VertexShader

1
2
3
4
5
6
7
8
attribute vec2 TexCoordIn; // New
varying vec2 TexCoordOut; // New

void main(void) {
DestinationColor = SourceColor;
gl_Position = Projection * Modelview * Position;
TexCoordOut = TexCoordIn; // New
}

2 FragmentShader

1
2
3
4
5
6
7
varying lowp vec4 DestinationColor;
varying lowp vec2 TexCoordOut; // New
uniform sampler2D Texture; // New

void main(void) {
gl_FragColor = DestinationColor * texture2D(Texture, TexCoordOut); // New
}

3 将图片资源转换成位图数据,绑定到对应纹理ID中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
+ (GLuint)createTextureWithImage:(UIImage *)image{

//转换为CGImage,获取图片基本参数
CGImageRef cgImageRef = [image CGImage];
GLuint width = (GLuint)CGImageGetWidth(cgImageRef);
GLuint height = (GLuint)CGImageGetHeight(cgImageRef);
CGRect rect = CGRectMake(0, 0, width, height);

//绘制图片
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
void *imageData = malloc(width * height * 4);
CGContextRef context = CGBitmapContextCreate(imageData, width, height, 8, width * 4, colorSpace,kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGContextTranslateCTM(context, 0, height);
CGContextScaleCTM(context, 1.0f, -1.0f);
CGColorSpaceRelease(colorSpace);
CGContextClearRect(context, rect);
CGContextDrawImage(context, rect, cgImageRef);

GLuint textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);

//纹理一些设置,可有可无
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);

glBindTexture(GL_TEXTURE_2D, 0);

//释放内存
CGContextRelease(context);
free(imageData);

return textureID;
}

4 绘制时使用纹理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
glActiveTexture(GL_TEXTURE0);
//载入纹理
glBindTexture(GL_TEXTURE_2D, _textTureId);

glUniform1i(_textureSlot, 0);

const GLfloat texCoords[] = {
0, 0,//左下
1, 0,//右下
0, 1,//左上
1, 1,//右上
};
glVertexAttribPointer(_textureCoordsSlot, 2, GL_FLOAT, GL_FALSE, 0, texCoords);
glEnableVertexAttribArray(_textureCoordsSlot);

5 关于纹理坐标

光照学习

基本概念

1 发射光(emission):物体本身发的光,如果物体不发光,一般无此属性

2 环境光(ambient):在环境中充分散射的光,光线在物体表面各个方向均匀泛射在openGL中,全局光强度为(0.2,0.2,0.2,1.0)

3 漫反射光(diffuse):关于来自某个方向,但是在物体表面向各个方向反射

4 镜面高光:光线来自某一个特定的方向,然后在物体表面,以一个特定方向反射出去,在OpenGL中,镜面反射的强度,可以通过光泽度(shiness)来调节

光的计算:

1 发射光计算:发射颜色=物体的发射材质颜色

2 环境光计算

环境颜色 = 光源的环境光颜色*物体的环境材质颜色

3 漫反射计算:

漫反射颜色=光源的漫反射光颜色 * 物体的漫反射材质颜色 * DiffuseFactor

其中DiffuseFactor = max(0,dot(N,L))

dot表示两个向量夹角的cos

4 镜面反射:

镜面反射颜色 = 光源的镜面光颜色 * 物体的镜面材质颜色 * SpecularFactor

SpecularFactor = power(max(0, dot(N,H)),shininess)

H = normalise(L+E)

VBO

VBO的目的:

主要是为了提高效率,减少在CPU向GPU中传输数据,直接在GPU上申请内存空间

两种target分别是GL_ARRAY_BUFFER和GL_ELEMENT_ARRAY_BUFFER

分别对应顶点数据和索引

使用glVertexAttribPointer和glDrawElements的流程和以前大致保持一致,区别在于,最后一个参数不是传指针了,而是传bindBuffer的偏移量

VBO使用的大概流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//获取一个操作句柄
glGenBuffers(1, &_vertexBuffer);
//设置缓存对象类型,数据缓存对象,还是元素缓存对象,通俗的说就是数组还是索引
glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
//分配内存空间
glBufferData(GL_ARRAY_BUFFER, 7*3*sizeof(GLfloat), vertices, GL_STATIC_DRAW);

在绘制代码Draw时,可以使用,绘制代码与正常并无冥想差别
glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
glVertexAttribPointer(_positionSlot, 3, GL_FLOAT, GL_FALSE, 7*sizeof(float), 0);
glEnableVertexAttribArray(_positionSlot);

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _triangleIndexBuffer);
glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_BYTE, 0);

//可以在清理时,释放VBO
glDeleteBuffers(1, &_vertexBuffer);
_vertexBuffer = 0;

glDeleteBuffers(1, &_triangleIndexBuffer);
_triangleIndexBuffer = 0;

uniform和attribute区别

简单对比一下

attribute,可以理解为顶点的属性,表示顶点的输入数据,只在顶点着色器中使用

uniform,可以简单理解为向着色器中传递matrix等数据,这个是在着色器中是只读的,并且在两个着色器中都可以使用

使用上的区别:

1 获取指针,分别使用glGetAttribLocation和glGetUniformLocation方法

2 设置值时,分别使用glVertexAttribPointer和glUniformMatrix4fv,注意使用glVertexAttribPointer和glEnableVertexAttribArray需要配合使用,而glUniformMatrix4fv无此限制

3 使用矩阵时,一般先LoadIdentity,然后进行平移,旋转和缩放,glDrawElements与顶点着色器可以理解为一一对应,也就是说,可以设置matrix1,传入shader的modelView中,然后glDrawElements,接下来可以再设置matrix2,再传入modelView中,再进行glDrawElements

opengl iOS创建OpenGL环境绘制一个简单三角形

EAGLView创建要点

1 EAGLView的layer为CAEAGLLayer,设置kEAGLDrawablePropertyRetainedBacking和kEAGLDrawablePropertyColorFormat属性

1
2
3
4
5
6
7
8
9
- (void)setupLayer{

CAEAGLLayer* layer = (CAEAGLLayer*)self.layer;

layer.opaque = YES;

layer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:@(NO), kEAGLDrawablePropertyRetainedBacking,
kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];
}

2 创建EAGLContext

1
_context = [[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES2];

3 加载着着色器程序,获取到着色器变量的索引,此过程,大致可分为:

a获取着色器源文件,创建shader,编译

b创建program,attachShader,link,useProgram

c从program,通过名字获取着色器中变量的索引(后续可以向着色器中传递参数)

这里有一点需要注意,此过程的前提,一定是已经设置了EAGLContext的currentContext

4 每一帧绘制流程

在外面使用CADisplayLink来控制播放帧率,每一帧的绘制流程就是

1
2
3
4
5
6
7
8
- (void)drawFrame{

[_eaglView setFramebuffer];

[_eaglView draw];

[_eaglView presentFramebuffer];
}

5 关于frameBuffetObject的创建和释放

前提:context确保设置

流程大致是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
- (void)createBuffer{

[self checkContext];

glGenRenderbuffers(1, &_colorRenderBuffer);

glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderBuffer);

[_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];

glGenFramebuffers(1, &_frameBuffer);

glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);

glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_RENDERBUFFER, _colorRenderBuffer);
}

- (void)deleteBuffer{

[self checkContext];

glDeleteRenderbuffers(1, &_colorRenderBuffer);

_colorRenderBuffer = 0;

glDeleteFramebuffers(1, &_frameBuffer);

_frameBuffer = 0;
}

- (void)setFramebuffer
{
if ([self checkContext])
{
if (!_frameBuffer){
[self createBuffer];
}
glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
}
}

- (BOOL)presentFramebuffer
{
BOOL success = FALSE;

if ([self checkContext])
{
glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderBuffer);

success = [_context presentRenderbuffer:GL_RENDERBUFFER];
}
return success;
}

6 关于绘制三角形

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (void)draw{
//设置背景颜色为绿色
glClearColor(0, 1.0, 0,1.0);
glClear(GL_COLOR_BUFFER_BIT);
//设置管区域大小
glViewport(0, 0, self.frame.size.width, self.frame.size.height);

GLfloat vertices[] = {
0.0f, 0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f };
//设置着色器中的vPositon
glVertexAttribPointer(_positionSlot, 3, GL_FLOAT, GL_FALSE, 0, vertices);
//使上一步的设置生效
glEnableVertexAttribArray(_positionSlot);
//绘制三角形
glDrawArrays(GL_TRIANGLES, 0, 3);
}

CALayer opaque

颜色合成公式

R = S + D * ( 1 – Sa )

20130819170332968

其中,R表示混合结果的颜色,S是源颜色(位于上层的红色图层一),D是目标颜色(位于下层的绿色图层二),Sa是源颜色的alpha值,即透明度。公式中所有的S和D颜色都假定已经预先乘以了他们的透明度。

设置opaque相当于是设置了Sa=1,此时R = S,省去了GPU的计算

注意:设置opaque为YES时,要确保alpha为1.0f,否则结果不可预期

CALayer属性positon和anchor

CALayer 的frame,bounds, position和anchor

frame描述的是在父layer上的坐标和尺寸

bounds是类似于View的bounds

anchor描述的是做动画时,比如旋转时的中心点,以某个点旋转,默认时(0.5,0.5)左上点是(0,0),右下点是(1,1)

position描述的是anchor的在父layer的坐标

举例来说,一个View的Frame是(40,40,100,100)

那么layer的frame是(40,40,100,100)

bound是(0,0,100,100)

position是:(140,140)

position计算:

pos.x = origin.x + anchor.x*size.with

pos.y = origin.y + anchor.y*size.height

如果想修改anchor,一定要同时修改position,才能保证位置不变,否则,单独修改position或者anchor,根据上面公式会导致layer位置发生变化

如果想修改anchor,而不影响layer移动,只需修改完成后,再设置一次layer的frame即可

CGRect oldFrame = _redView.frame;
_redView.layer.anchorPoint = CGPointMake(0.5, 1);
_redView.frame = oldFrame;

注意,position并不适用这一点